Таблица и косвенный вызов

23rd March 2025 at 11:30am

Механизм косвенного вызова функций Wasm и инструкции.

Инструкция call_indirect. call инструкция принимает немедленный аргумент, который указывает индекс вызываемой функции. Перед тем, как Wasm выполнит callинструкцию, необходимо убедиться, что параметры, которые будут переданы вызываемой функции, уже находятся в стеке, а порядок и тип параметров должны точно соответствовать сигнатуре вызываемой функции. После выполнения инструкции вызова параметры извлекаются из вершины стека, и возвращаемое значение функции (если таковое имеется) появится на вершине стека. Инструкции в основном используются для реализации указателей функций в таких языках, как C/C++ и Rust. Как следует из названия, call_indirect инструкции вводят слой косвенности в вызовы функций. Они имеют тот же двоичный формат, что и call инструкция, но довольно сильно отличаются по семантике выполнения. Во-первых, вызываемая функция не находится напрямую через индекс функции, сохраненный в непосредственном аргументе, а находится косвенно с помощью таблицы. Индекс таблицы размещается с параметрами наверху стека операндов, над всеми параметрами. Во-вторых, поскольку во время компиляции мы не знаем, какая функция должна быть вызвана, до времени выполнения нет способа получить сигнатуру функции через индекс функции, как мы делаем в call инструкции. К счастью, сигнатура вызываемой функции уже известна во время компиляции, поэтому индекс сигнатуры функции можно поместить в непосредственный аргумент. модуль Wasm может определять или импортировать таблицу, а начальные данные таблицы помещаются в сегменты элементов. Спецификация Wasm 1.0 накладывает много ограничений на таблицы. Во-первых, каждый модуль Wasm может импортировать или определять не более одной таблицы. Во-вторых, таблица поддерживает только один элемент, который является funcref (ссылкой на функцию или адресом функции). call_indirect инструкция сначала получает индекс элемента в соответствии с верхним операндом, затем получает ссылку на функцию через индекс элемента и, наконец, вызывает функцию через ссылку на функцию. После обнаружения конкретной функции реализация Wasm проверяет сигнатуру фактической функции, чтобы убедиться, что она соответствует сигнатуре, указанной непосредственным аргументом инструкции.

Управление

27th March 2025 at 8:45am

Всего существует 11 инструкций управления WebAssembly (называемых Wasm) , из которых unreachable инструкция (opcode 0x00) и инструкция nop (opcode 0x01) относительно просты. Инструкция call и call_indirect см. здесь. Рассматриваются 7 инструкций, то есть block (opcode 0x02), loop (opcode 0x03), if (opcode 0x04), br (opcode 0x0C), br_if (opcode 0x0D), br_table (opcode 0x0E), and return (opcode 0x0F).

block. Эффект block инструкции эквивалентен вызову встроенной функции без параметров. Тип возвращаемого значения функции, который является типом результата block инструкции, кодируется в первом непосредственном аргументе инструкции. Инструкции функции (возможно, многие) кодируются и сохраняются во втором непосредственном аргументе. Инструкция блока должна заканчиваться end инструкцией (opcode 0x0B). Поскольку end инструкция и else инструкция (opcode 0x05), которые служат только маркером и не имеют эффекта выполнения, эти две инструкции не учитываются в инструкциях управления. Спецификация Wasm l.0 предусматривает, что block инструкция не может иметь более одного результата, поэтому rtможет быть представлена ​​одним байтом: 0x40 указывает на отсутствие результата, 0x7F указывает i32 тип, 0x7E указывает i64 тип, 0x7D указывает f32 тип и 0x7C указывает f64 тип. Когда block инструкция выполняется, она не использует операнд, который уже существует в стеке. После завершения выполнения операнд может остаться на вершине стека.

loop. loop инструкции очень похожи на block инструкции. Единственное отличие в том, как выпрыгнуть из блока управления, что будет обсуждаться далее во введении к br инструкциям.

if. Как и block инструкции, if инструкции похожи на встроенную функцию. Есть два основных отличия. Во-первых, if «встроенная функция» принимает i32 параметр типа. Во-вторых, if «встроенная функция» имеет два фрагмента кода (две ветви), разделенные инструкцией else. Когда if инструкция выполняется, i32 параметр типа сначала извлекается из вершины стека. Если значение параметра не равно 0, выполняется код первой ветви; в противном случае выполняется код второй ветви. Вы также можете опустить else ветвь инструкции if, но в этом случае if инструкция не может иметь никаких результатов.

br. br инструкции (которые можно понимать как break или br anch) могут выполнять безусловный переход. В отличие от JUMP инструкции на традиционном языке ассемблера, br инструкция не может перейти в произвольную позицию, а может только выпрыгнуть ( инструкции break for block и и continue for , на чем не будет акцентироваться далее.) из блоков управления, сгенерированных другими инструкциями управления. инструкции принимают непосредственный аргумент типа (32-битное целое число без знака), который указывает количество слоев для перехода: 0 означает выход из текущего блока, 1 означает выход из блока "выше" и т. д.

br_if. Инструкция br_if выталкивает операнд типа i32 из вершины стека. Если значение операнда равно 0, переход не выполняется; в противном случае выполняется br логика.

br_table. Будь то инструкция br или br_if, есть только один немедленный аргумент, который может указывать глубину перехода. br_table нарушает это ограничение. Она может принимать N + 1 немедленных аргументов и указывать N + 1 глубин перехода. Когда инструкция br_table выполняется, операнд n типа i32 извлекается из вершины стека. Если n меньше или равно N, он переходит на n-ную глубину; в противном случае он переходит на последнюю глубину.

return. Инструкцию return можно считать особой формой инструкции br: она напрямую выскакивает из самого внешнего цикла (то есть всей функции) и не имеет непосредственного аргумента.

Переменные и вызов функции

23rd March 2025 at 11:42am

Глобальные переменные

Модули Wasm могут определять или импортировать глобальные переменные. При импорте или определении вы можете ограничить тип и изменчивость глобальных переменных. В определении вы также можете указать начальные значения. Существует пять переменных инструкций, две из которых связаны с глобальными переменными.

global.get. global.get инструкция (opcode 0x23) помещает значение глобальной переменной на вершину стека. Индекс глобальной переменной задается непосредственным аргументом (32-битным целым числом без знака) инструкции.

local. Область действия глобальных переменных — весь модуль Wasm, а область действия локальных переменных — вся функция. Параметры функции по сути являются локальными переменными.

local.get. local.get инструкция (opcode 0x20) похожа на global.get инструкцию, за исключением того, что первая считывает локальные переменные.

local.set. local.set инструкция (opcode 0x21) похожа на global.set инструкцию, за исключением того, что первая записывает локальные переменные.

local.tee. local.tee инструкция (opcode 0x22) похожа на local.set инструкцию, за исключением того, что первая оставляет операнд на вершине стека.

Вызов функции

Инструкции по вызову функций относятся к инструкциям по управлению . Существует две инструкции по вызову функций: call и call_indirect.

call. call инструкция (opcode 0x10) вызывает функцию, а индекс функции указывается непосредственным аргументом (32-битным целым числом без знака) инструкции. Перед выполнением этой инструкции параметры функции должны быть подготовлены и помещены на вершину стека по порядку (самый левый параметр находится внизу). После выполнения инструкции параметры извлекаются из вершины стека и заменяются возвращаемым значением функции (если таковое имеется). Спецификация Wasm l.0 определяет, что возвращаемое значение функции не должно превышать единицу, что может быть смягчено в последующих версиях.

Память

22nd March 2025 at 10:17pm

Каждый модуль Wasm может определять или импортировать одну память. Единицей памяти является страница, а размер страницы составляет 64 Кб . При определении памяти необходимо указать нижний предел количества страниц памяти. Максимальное количество страниц является необязательным и может быть указано или нет. Начальные данные памяти могут быть указаны в разделах данных.

Существует 25 инструкций, связанных с памятью:

Инструкция memory.size (opcode 0x3F) помещает текущий номер страницы памяти на вершину стека как i32. memory.size инструкция принимает 1-байтовый непосредственный аргумент, который указывает, какая память обрабатывается. Поскольку спецификация Wasm l.0 предусматривает, что может быть не более одной памяти, непосредственный аргумент в настоящее время может быть только 0.

Инструкция memory.grow (opcode 0x40) увеличивает память на nстраницы, где n— целое число типа i32 и выталкивается из вершины стека. Если операция выполнена успешно, поместить число страниц до роста на вершину стека как i32 тип; в противном случае поместить -1на вершину стека. Как и memory.sizeинструкция, memory. growинструкция также несет 1-байтовый непосредственный аргумент, который должен быть равен 0.

Инструкции load считывают данные из памяти и помещают их на вершину стека. Сколько байт данных считывается и какие типы данных интерпретируются, зависит от инструкции. Wasm использует метод адресации памяти «непосредственные аргументы + операнды». Все инструкции загрузки содержат два непосредственных аргумента в u32 типе (32-битные беззнаковые целые числа в кодировке LEB28 i32), один для выравнивания, а другой для смещения памяти. Инструкции загрузки также должны извлечь операнд -type из вершины стека. Непосредственный аргумент и операнд добавляются для получения фактического начального адреса памяти для считывания. Выравнивание служит только подсказкой и не влияет на фактическую операцию. Существует 14 инструкций загрузки.

Инструкции store извлекают операнд из вершины стека и записывают его в память. То, как интерпретировать операнд и сколько байтов записывается, варьируется от инструкции к инструкции. Все инструкции сохранения также содержат два непосредственных аргумента, которые имеют то же значение, что и инструкции загрузки. В отличие от инструкций загрузки, инструкции сохранения извлекают два операнда из вершины стека. Один из них вычисляет адрес памяти, а другой — данные для записи. Всего имеется 9 инструкций сохранения.

Числовые инструкции

22nd March 2025 at 10:16am

Числовые инструкции можно разделить на четыре группы i32, i64, f32, и f64 в соответствии с типом операнда. Каждая группа инструкций может быть далее разделена на группы в соответствии с операцией.

  • Постоянные инструкции
  • Инструкции по тестированию
  • Инструкции по сравнению
  • Арифметические инструкции
  • Унарные арифметические инструкции
  • Двоичные арифметические инструкции
  • Инструкции по конвертации

За исключением константных (постоянныйх) инструкций, все остальные числовые инструкции не имеют непосредственных аргументов.

Постоянные инструкции. Константная инструкция помещает аргумент на вершину стека. Существует четыре константных инструкции, а остальные три: i64.const (opcode x42), f32.const (opcode x43) и f64.const (opcode 0x44). Нетрудно найти правило именования мнемоники опкода Wasm: если тип верхнего элемента стека после выполнения инструкции — t, то мнемоника начинается с t.

Инструкции по тестированию. Тестовая инструкция извлекает операнд из вершины стека и проверяет, равен ли он 0. Если это так, она помещает 1 (как i32) в стек; в противном случае она помещает 0 (как i32) в стек. Существует всего две тестовых инструкции: i32.eqz (opcode 0x45) и i64.eqz (opcode 0x50). Результатом тестовой инструкции на самом деле является логическое значение, но Wasm не определяет bool тип и вместо этого использует i32 тип для его представления (1 означает истина, а 0 означает ложь).

Инструкции по сравнению. Инструкция сравнения выталкивает два операнда одного типа из вершины стека, сравнивает их, а затем помещает результат в стек. Как и инструкция теста, результат инструкции сравнения также является логическим значением (т.е. i32 типом). В дополнение к «меньше чем» (le) существуют инструкции для таких сравнений, как «равно» (eq), «не равно» (ne), «больше чем» (gt), «меньше или равно» (le) и «больше или равно» (ge). Инструкции здесь не вводятся по одной. Следует отметить, что для некоторых инструкций, работающих с целыми числами, необходимо четко указать, как интерпретировать операнды: является ли это числом со знаком (мнемоника заканчивается на _s) или числом без знака (мнемоника заканчивается на _u). Эти инструкции обычно появляются парами.

Унарные арифметические инструкции. Унарные арифметические инструкции выталкивают операнд из вершины стека, выполняют вычисления, а затем помещают результат на вершину стека.

Двоичные арифметические инструкции. Двоичные арифметические инструкции выталкивают два операнда из вершины стека, выполняют вычисления, а затем помещают результат на вершину стека.

Инструкция по конвертации. Инструкция преобразования выталкивает операнд из вершины стека, выполняет преобразование типа, а затем помещает результат на вершину стека. Если тип операнда до преобразования — t, тип после преобразования — t', а операция преобразования — conv, то мнемоника инструкции — t'.conv_t.

Классификатор инструкций WASM

22nd March 2025 at 9:58am

Инструкции WebAssembly

Как и настоящий машинный код , код в двоичном файле Wasm также состоит из инструкций. Аналогично инструкция Wasm также содержит две части информации: код операции и операнды . Код операции Wasm фиксирован в один байт, поэтому он может представлять до 256 инструкций, что совпадает с байт-кодом Java . Спецификация Wasm l.0 определяет в общей сложности 172 инструкции. Эти инструкции можно разделить на 5 категорий по функциям:

  • Инструкции по контролю , всего 13.
  • Параметрические инструкции, всего 2.
  • Переменные инструкции, всего 5.
  • Инструкции по запоминанию , всего 25.
  • Числовые инструкции, всего 127.

Для удобства человеческого письма и понимания спецификация Wasm также определяет мнемонику для каждого опкода. Ниже представлена ​​схематическая диаграмма распределения опкодов определенных инструкций (серая область представляет неопределенные опкоды):

Некоторые инструкции должны нести информацию, которая кодируется сразу после кода операции, называемые статическими непосредственными аргументами.

Подобно стековым машинам , таким как JVM , большинство инструкций Wasm также используют стек операндов (далее именуемый стеком). Эти инструкции извлекают одно или несколько чисел из вершины стека, выполняют вычисления, а затем помещают результаты на вершину стека. Верхние элементы стека, которыми управляют инструкции, называются динамическими операндами.

Параметрические и числовые инструкции работают только со стеком.

Параметрические инструкции

22nd March 2025 at 9:56am

Существует две параметрические инструкции: drop (чей код операции 0x1A) и select (чей код операции 0x1B).

Инструкция drop выталкивает операнд любого типа из вершины стека. Для инструкции нет непосредственных аргументов drop.

Инструкция select последовательно выталкивает 3 операнда из вершины стека. Если первый выталкиваемый операнд равен нулю, то в стек помещается второй выталкиваемый операнд, в противном случае в стек помещается третий выталкиваемый операнд. Инструкция select не имеет непосредственных аргументов.

Операнд наверху стека должен иметь тип i32, а оставшиеся два операнда должны иметь одинаковый тип. Когда нам нужно подчеркнуть конкретный тип операнда, мы отметим тип скобками на схеме. drop и select являются двумя специальными инструкциями, поскольку только эти две инструкции не ограничивают полностью тип операндов. Для других инструкций все типы операндов полностью квалифицированы.

Модуль

22nd March 2025 at 7:50am

Структура верхнего уровня Wasm представляет собой модуль, и каждый двоичный файл Wasm соответствует модулю. Модуль начинается с 4-байтового магического числа , за которым следует 4-байтовый номер версии, а остальное — данные модуля. Конкретные данные модуля делятся на разные типы и размещаются в разных разделах. Каждый раздел идентифицируется уникальным идентификатором раздела. За исключением пользовательского раздела (который будет описан позже), все остальные разделы могут появляться только один раз и должны появляться в порядке увеличения идентификатора раздела. Спецификация Wasm определяет всего 12 разделов с идентификаторами от 0 до 11. За исключением пользовательских разделов, структура других разделов определена. Поскольку содержимое пользовательского раздела может быть неизвестно, фактическое количество байтов раздела сохраняется после идентификатора раздела, так что реализация Wasm может пропускать пользовательские разделы, которые она не распознает. Чтобы сделать двоичные файлы Wasm более компактными, целые числа сохраняются после кодирования в формате LEB128 .

Раздел типа

Раздел Type используется для хранения информации о типе (или сигнатуре ) функции. Раздел Code хранит информацию о локальных переменных и байт-код функции. Раздел Function связывает раздел code с разделом type. Поскольку вам нужно хранить несколько типов функций, вам нужно сначала сохранить количество типов функций. Это стандартная практика для хранения векторных данных в двоичном формате Wasm.

Раздел импорта

Информация об импорте включает имя модуля импорта, имя импорта и описание импорта. Wasm поддерживает четыре типа импорта: функции, таблицы, память и глобальные переменные. Чтобы отличить конкретный импорт, описание импорта должно начинаться с однобайтового тега, а конкретная информация об описании различается в зависимости от тега. В двоичном файле Wasm строка символов также хранится в виде вектора (длина+содержимое), а содержимое представляет собой массив байтов, закодированных UTF-8.

Раздел функций

Хранит индекс сигнатуры каждой внутренней функции в разделе типов по порядку.

Раздел таблиц и элементов

Раздел памяти

Информация о памяти дает нижний предел (который должен быть указан) и верхний предел (необязательно) количества страниц, необходимых для работы модуля (64 КБ для страницы).

Раздел глобальных переменных

Информация о глобальных переменных включает тип переменной, является ли она доступной только для чтения и байт-код, используемый для инициализации глобальной переменной.

Раздел экспорта

Как и информация об импорте, информация об экспорте также делится на четыре типа: функции, таблицы, память и глобальные переменные. Экспортировать информацию немного проще, так как она не включает имя модуля, и независимо от того, что экспортируется, вам нужно только указать соответствующий индекс.

Стартовый раздел

Содержит только один индекс начальной функции.

Раздел кода

Информация о локальных переменных и байт-код внутренней функции хранятся в разделе кода. Для параллельной обработки (например, проверки, анализа, компиляции AOT и т. д.) каждый элемент в разделе кода поставляется с несколькими байтами. Таким образом, сначала можно извлечь информацию о локальных переменных и данные байт-кода каждой внутренней функции, а затем можно одновременно обрабатывать несколько функций.

Раздел данных

Раздел данных хранит информацию об инициализации памяти. Каждый фрагмент информации об инициализации памяти включает индекс памяти (который в настоящее время может быть только 0), начальную позицию (которая является байт-кодом и должна быть константным выражением ) и начальные данные.

Пользовательский раздел

Пользовательские разделы могут хранить отладочную информацию, информацию о сторонних расширениях и т. д. Эта информация не является необходимой для запуска модуля и может быть безопасно проигнорирована. Кроме того, в отличие от других разделов (которые должны появляться в строгом порядке и могут появляться не более одного раза), пользовательские разделы могут появляться свободно до и после других разделов, а также могут появляться несколько раз подряд. Содержимое пользовательского раздела начинается со строки, что еще больше отличает пользовательский раздел.

20250320

23rd March 2025 at 11:03am

Краткое изложение дополнений в версии 2.0:

Векторные инструкции : с огромным количеством в 236 новых инструкций — больше, чем общее количество инструкций Wasm, — теперь он поддерживает 128-битную функциональность SIMD (одна инструкция, несколько данных) современных процессоров, таких как SSE от Intel или SVE от ARM. Это помогает ускорить определенные классы приложений с интенсивными вычислениями, такие как аудио/видеокодеки, машинное обучение и некоторые виды криптографии.

Инструкции по работе с большой памятью : набор новых инструкций позволяет быстрее копировать и инициализировать области памяти или диапазоны таблиц.

Результаты с несколькими значениями : инструкции, блоки и функции теперь могут возвращать более одного результирующего значения, иногда поддерживая более быстрые соглашения о вызовах и избегая косвенности. Кроме того, инструкции блоков теперь также могут иметь входы, что позволяет проводить новые преобразования программ.

Типы ссылок : Ссылки на функции или указатели на внешние объекты (например, значения JavaScript) становятся доступными как непрозрачные значения первого класса. Таблицы перепрофилируются в общее хранилище для таких значений ссылок, а новые инструкции позволяют получать доступ к таблицам и изменять их в коде Wasm. Кроме того, модули теперь могут определять несколько таблиц разных типов.

Преобразования без перехвата : дополнительные инструкции позволяют преобразовывать типы с плавающей точкой в ​​целые числа без риска неожиданного перехвата.

Инструкции по расширению знака : Новая группа инструкций позволяет напрямую расширять ширину знакового целого значения. Ранее это было возможно только при чтении из памяти.

Wasm 2.0 полностью обратно совместим с 1.0, то есть каждая корректная программа остается корректной и сохраняет свое поведение.

[[В будущем посте мы рассмотрим Wasm 3.0, выход которого уже не за горами!| https://webassembly.org/news/2025-03-20-wasm-2.0]]

Текстовый формат

23rd March 2025 at 10:57am

Общая структура

(модуль 
  ;; поля 
  (тип ... ) 
  (импорт ... ) 
  (функция ... ) 
  (таблица ... ) 
  (память ... ) 
  (глобалы ... ) 
  (экспорт ... ) 
  (старт ... ) 
  (элемент ... ) 
  (данные ... ) 
)

Текстовый формат — это еще одно представление двоичного формата. Помимо очевидных различий в форме представления, эти два формата имеют следующие различия в структуре:

  • В двоичном формате данные организованы в разделы, а в текстовом формате содержимое организовано в поля. Компилятору WAT необходимо собрать поля одного типа и объединить их в двоичные разделы.
  • В двоичном формате все разделы, за исключением пользовательского раздела, могут появляться не более одного раза и должны появляться в порядке возрастания идентификатора раздела. Такое ограничение не распространяется на текстовый формат или порядок полей. Однако поля импорта должны появляться перед полями функций, таблиц, памяти и глобальных полей. Кроме того, в текстовом формате нет пользовательского поля, поэтому нет способа выразить пользовательские разделы.
  • Поля и разделы в основном соответствуют один к одному, но отдельного поля кода нет. Поле кода и поле функции объединены вместе.
  • Текстовый формат предоставляет несколько встроенных форм для удобства написания. Например:
    • Функции, таблицы, поля памяти и глобальные поля могут содержать встроенные поля импорта и экспорта.
    • Поле таблицы может содержать встроенные поля элементов.
    • Поле памяти может содержать встроенные поля данных.
    • Поля функций и импорта могут содержать встроенные поля типа.

Базисный синтаксис WAT

Type Field. Поле типа определяет тип функции. Мы можем назначить идентификатор типу функции в качестве его имени, так что на тип функции можно будет ссылаться по имени в другом месте, без необходимости передавать индекс напрямую. module, type, func, param, result являются ключевыми словами языка WAT. Идентификаторы должны начинаться со знака $, за которым следует одна или несколько цифр или букв. Параметры типа функции также могут быть сокращены в том же (param).

Import & Export Field. Модуль Wasm может импортировать или экспортировать четыре типа элементов: функцию, таблицу, память и глобальную переменную. Соответственно, поля импорта и экспорта также записываются четырьмя способами. В поле импорта необходимо указать имя модуля, имя элемента и конкретный тип импортируемого элемента. Имена представлены строками и должны быть заключены в двойные кавычки. Полю импорта также можно присвоить идентификатор, как и полю типа, чтобы к импортируемому элементу можно было позже обратиться по имени. WAT поддерживает два типа комментариев: однострочные комментарии, которые начинаются с , ;; и межстрочные комментарии, которые начинаются с ( ;;, заканчиваются на ;; ). По сравнению с полем импорта, поле экспорта писать проще. Для поля экспорта требуется только указать имя экспорта и индекс конкретного элемента. Обратите внимание, что имя экспорта должно быть уникальным в пределах всего модуля. Импортные и экспортные поля могут быть встроены в функции, таблицы, памяти и глобальные поля.

Function Field. Поле функции объявляет локальные переменные функции и дает инструкции для функции. Компилятор разделит поле функции, поместит индекс типа в раздел функции, а информацию о локальной переменной и байт-код в раздел кода. Фактически параметры функции также являются обычными локальными переменными и вместе с локальными переменными, объявленными в поле функции, составляют локальное переменное пространство, индекс которого увеличивается с 0. Мы можем сделать тип функции встроенным с полем функции и разбить (param)на несколько для именования параметров. Таким же образом (local)можно разбить на несколько для именования локальных переменных. Давая имена параметрам и локальным переменным, мы можем находить параметры или локальные переменные по имени, а не по индексу в инструкциях переменных, что помогает улучшить читаемость кода.

Table & Element Field. Поскольку в спецификации Wasm l.0 указано, что модуль может иметь не более одной таблицы, поле таблицы может появляться не более одного раза. Поле элемента может появляться несколько раз, что может указывать на несколько индексов функций и смещение таблицы, соответствующее первому индексу функции. Мы также можем сделать поле элемента встроенным в поле таблицы, но мы не можем указать границы таблицы таким образом, а заставим компилятор спекулировать в соответствии с встроенным элементом. Также невозможно указать начальное смещение элемента, которое должно начинаться с 0.

Memory & Data Field. Подобно таблице, поскольку в спецификации Wasm l.0 указано, что модуль может иметь максимум одну память, поле памяти также может появляться максимум один раз. Поле данных может появляться несколько раз, и требуется константное выражение для указания начального смещения памяти (адреса) и строка для указания начального значения памяти. Поле данных также может быть встроенным в поле памяти, но мы не можем указать пределы страницы памяти таким образом, а заставим компилятор спекулировать в соответствии с встроенными данными. Также невозможно указать начальное смещение памяти, которое может начинаться только с 0. Кроме того, начальные данные могут быть записаны в виде нескольких строк. С помощью экранирующих символов мы можем легко встраивать специальные символы, такие как новые строки, шестнадцатеричные байты и кодовые точки Unicode в строки.

Global Field. В глобальном поле мы можем указать идентификатор, тип, изменчивость и начальное значение глобальных переменных.

Start Field. Поле start является самым простым и используется для указания индекса начальной функции.

Ознакомившись с общей структурой WAT и написанием различных полей, теперь о том, как писать различные инструкции.

Plain Instruction. Простая инструкция довольно проста. Для большинства инструкций это код операции, за которым следуют непосредственные аргументы (если таковые имеются). Немедленные аргументы являются необязательными и должны быть указаны явно (значение следует за знаком равенства). Три структурированные инструкции управления, block, loop и if, могут указывать необязательные типы результатов и должны заканчиваться ключевым словом end. if инструкцию также можно разделить на две ветви с ключевым словом else. Метки пишутся одна за другой, разделяются пробелом и следуют за меткой по умолчанию.

Folded Instruction. В дополнение к простому формату, инструкции могут быть записаны более сжатым способом. Три шага для преобразования обычного формата в свернутый: во-первых, добавьте скобки к инструкции; во-вторых, удалите ключевое слово, end если это инструкция block, loop или if. Если инструкция и предшествующие инструкции могут рассматриваться как одна группа операций логически, предыдущие инструкции могут быть свернуты в инструкцию. Свернутая инструкция фактически выражает дерево инструкций. Компилятор WAT расширит свернутую инструкцию в соответствии с обходом в обратном порядке.