1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
|
---
title: Описание текстового формата WebAssembly
slug: WebAssembly/Understanding_the_text_format
translation_of: WebAssembly/Understanding_the_text_format
---
<div>{{WebAssemblySidebar}}</div>
<p class="summary">Чтобы люди могли читать и редактировать код WebAssembly, существует текстовое представление двоичного формата wasm. Это промежуточная форма, предназначенная для отображения в текстовых редакторах, средствах разработки браузеров и т. д. В этой статье объясняется, как работает этот текстовый формат с точки зрения синтаксиса, как он связан с байт-кодом, который он представляет и оболочками объектов wasm в JavaScript.</p>
<div class="note">
<p><strong>Примечание</strong>: Ознакомление с данной статьёй может оказаться излишним, если вы веб-разработчик, который просто хочет загрузить модуль wasm на страницу и использовать его в своём коде (см. <a href="/ru/docs/WebAssembly/Using_the_JavaScript_API">Использование WebAssembly JavaScript API</a>). Эта статья будет наиболее полезной, если вы хотите написать несколько модулей wasm для оптимизации производительности вашей библиотеки JavaScript или создать свой собственный компилятор WebAssembly.</p>
</div>
<h2 id="S-выражения">S-выражения</h2>
<p>Как в двоичном, так и в текстовом форматах основным блоком кода в WebAssembly является модуль. В текстовом формате модуль представлен как одно большое S-выражение. S-выражения - это очень старый и очень простой текстовый формат для представления деревьев. И поэтому мы можем думать о модуле как о дереве узлов, которые описывают структуру модуля и его код. В отличие от абстрактного синтаксического дерева в языке программирования, дерево WebAssembly довольно плоское и состоит в основном из списков инструкций.</p>
<p>Во-первых, давайте посмотрим, как выглядит S-выражение. Каждый узел дерева входит в пару круглых скобок - <code>( ... )</code>. Первая метка в скобках сообщает вам, какой это тип узла, за ним следует разделённый пробелами список атрибутов или дочерних узлов. Давайте рассмотрим, что означает следующее S-выражение WebAssembly:</p>
<pre>(module (memory 1) (func))</pre>
<p>Это выражение представляет дерево с корневым узлом «module» и двумя дочерними узлами - узлом «memory» с атрибутом «1» и узлом «func». Мы вскоре увидим, что на самом деле означают эти узлы.</p>
<h3 id="Самый_простой_модуль">Самый простой модуль</h3>
<p>Давайте начнём с самого простого модуля wasm.</p>
<pre>(module)</pre>
<p>Этот модуль полностью пуст, но является допустимым.</p>
<p>Если мы сейчас преобразуем наш модуль в двоичный формат (см. <a href="/en-US/docs/WebAssembly/Text_format_to_wasm">Перевод текстового формата WebAssembly в wasm</a>), мы увидим только 8-байтовый заголовок модуля, описанный в <a href="http://webassembly.org/docs/binary-encoding/#high-level-structure">двоичном формате</a>:</p>
<pre>0000000: 0061 736d ; WASM_BINARY_MAGIC
0000004: 0100 0000 ; WASM_BINARY_VERSION</pre>
<h3 id="Добавление_функциональности_в_ваш_модуль">Добавление функциональности в ваш модуль</h3>
<p>Хорошо, это не очень интересно, давайте добавим немного исполняемого кода в этот модуль.</p>
<p>Весь код в модуле сгруппирован в функции, которые имеют следующую структуру псевдокода:</p>
<pre>( func <signature> <locals> <body> )</pre>
<ul>
<li><strong>signature</strong> объявляет, что функция принимает (параметры) и возвращает (возвращаемые значения).</li>
<li><strong>locals</strong> похожи на переменные в JavaScript, но с определёнными явными типами.</li>
<li><strong>body</strong> - это просто линейный список низкоуровневых инструкций.</li>
</ul>
<p>Несмотря на то, что это S-выражение, оно очень напоминает функцию в других языках.</p>
<h2 id="Сигнатуры_и_параметры">Сигнатуры и параметры</h2>
<p>Сигнатура - это последовательность объявлений типов параметров, за которыми следует список объявлений возвращаемых типов. Здесь стоит отметить, что:</p>
<ul>
<li>
<p class="syntaxbox"> Отсутствие возвращаемого типа <code>(result)</code> означает, что функция ничего не возвращает. </p>
</li>
<li>
<p class="syntaxbox">В текущей версии WebAssembly может быть не более 1 возвращаемого типа, но <a href="https://webassembly.org/docs/future-features#multiple-return">позже это значение будет изменено</a> на любое число.</p>
</li>
</ul>
<p> Каждый параметр имеет явно объявленный тип; у wasm в настоящее время есть четыре доступных типа:</p>
<ul>
<li> <code>i32</code>: 32-разрядное целое число</li>
<li> <code>i64</code>: 64-разрядное целое число</li>
<li> <code>f32</code>: 32-разрядное число с плавающей точкой</li>
<li> <code>f64</code>: 64-разрядное число с плавающей точкой</li>
</ul>
<p>Один параметр можно записать как <code>(param i32)</code>, а тип возвращаемого значения как <code>(result i32)</code>. Двоичная функция, которая принимает два 32-разрядных целых числа и возвращает 64-разрядное число с плавающей запятой, будет записана следующим образом:</p>
<pre>(func (param i32) (param i32) (result f64) ... )</pre>
<p>После сигнатуры перечисляются локальные переменные с указанием типа, например <code>(local i32)</code>. Параметры в сигнатуре приравниваются к локальным переменным, которые инициализируются значением соответствующего аргумента, переданного вызывающей стороной.</p>
<h2 id="Получение_и_установка_локальных_переменных_и_параметров_функции">Получение и установка локальных переменных и параметров функции</h2>
<p>И параметры и локальные переменные могут быть прочитаны и записаны в теле функции с помощью инструкций <code>get_local</code> и <code>set_local</code>.</p>
<p>Инструкции <code>get_local</code> и <code>set_local</code> ссылаются по индексу на параметр, который должен быть получен или установлен: сначала считаются параметры, а затем локальные переменные в порядке их объявления. Объясним это на примере следующей функции:</p>
<pre>(func (param i32) (param f32) (local f64)
get_local 0
get_local 1
get_local 2)</pre>
<p>Инструкция <code>get_local 0</code> получит параметр i32, <code>get_local 1</code> получит параметр f32, а get_local 2 получит локальную переменную local f64.</p>
<p>Использование числовых индексов для ссылки на элементы может сбивать с толку и раздражать, поэтому текстовый формат позволяет присваивать имена параметрам, локальным переменным и большинству других элементов. Для этого нужно просто добавить имя с префиксом символа доллара (<code>$</code>) непосредственно перед объявлением типа.</p>
<p>Таким образом, можно переписать нашу сигнатуру так:</p>
<pre>(func (param $p1 i32) (param $p2 f32) (local $loc f64) …)</pre>
<p>После чего можно было бы написать инструкцию получения <code>get_local $p1</code> вместо <code>get_local 0</code> и т.д. (Обратите внимание, что, когда этот текст преобразуется в двоичный файл, двоичный файл будет содержать только индексы.)</p>
<h2 id="Стековые_машины">Стековые машины</h2>
<p>Прежде чем мы сможем написать тело функции, мы должны поговорить ещё о <strong>стековых машинах</strong>. Хотя браузер компилирует wasm-код во что-то более эффективное, выполнение его определяется в терминах стековой машины, где основная идея заключается в том, что каждый тип инструкции получает или помещает определённое количество значений <code>i32</code> / <code>i64</code> / <code>f32</code> / <code>f64</code> в стек или из стека.</p>
<p>Например, инструкция <code>get_local</code> предназначена для помещения значения локальной переменной, которое она считала, в стек. А инструкция <code>i32.add</code> получает два значения <code>i32</code> (неявно получает два предыдущих значения, помещённых в стек), вычисляет их сумму и помещает назад в стек результат вычисления <code>i32</code>.</p>
<p>Когда вызывается функция, для неё выделяется пустой стек, который постепенно заполняется и очищается при выполнении инструкций в теле функции. Так, например, после выполнения следующей функции:</p>
<pre>(func (param $p i32)
get_local $p
get_local $p
i32.add)</pre>
<p>Стек будет содержать ровно одно значение <code>i32</code> - результат выполнения выражения ($p + $p), которое обработалось инструкцией <code>i32.add</code>. Возвращаемое значение функции - это последнее значение, оставленное в стеке.</p>
<p>Правила валидации WebAssembly гарантируют, выполнение следующего: если вы объявляете тип возвращаемого значения функции как <code>(result f32)</code>, то стек должен содержать ровно одно значение типа <code>f32</code> в конце. Если тип результата отсутствует, стек должен быть пустым.</p>
<h2 id="Тело_функции">Тело функции</h2>
<p>Как упоминалось ранее, тело функции - это просто список инструкций, которые выполняются при вызове функции. Объединяя это с тем, что мы уже изучили, мы можем наконец определить модуль, содержащий простую функцию:</p>
<pre>(module
(func (param $lhs i32) (param $rhs i32) (result i32)
get_local $lhs
get_local $rhs
i32.add))</pre>
<p>Эта функция получает два параметра, складывает их вместе и возвращает результат.</p>
<p>Есть ещё много инструкций, которые можно поместить в тело функции. Сейчас мы начнём с простых, а далее вы увидите гораздо больше примеров по мере продвижения. Полный список доступных инструкций смотрите в справочнике по <a href="http://webassembly.org/docs/semantics/">семантике webassembly.org</a>.</p>
<h3 id="Вызов_функции">Вызов функции</h3>
<p>Определение нашей функции само по себе почти ничего не делает - теперь нам нужно её вызвать. Как мы это сделаем? Как и в модуле ES2015, функции wasm должны быть явно экспортированы инструкцией <code>export</code> внутри модуля.</p>
<p>Как и локальные переменные, функции идентифицируются индексом по умолчанию, но для удобства им можно присвоить имя. Давайте это сделаем: сначала добавим имя, которому предшествует знак доллара, сразу после ключевого слова <code>func</code>:</p>
<pre>(func $add … )</pre>
<p>Теперь нам нужно добавить объявление экспорта:</p>
<pre>(export "add" (func $add))</pre>
<p>Здесь <code>add</code> - это имя, по которому функция будет идентифицироваться в коде JavaScript, а <code>$add</code> определяет, какая функция внутри модуля WebAssembly будет экспортироваться.</p>
<p>Итак, наш последний вариант модуля (на данный момент) выглядит так:</p>
<pre>(module
(func $add (param $lhs i32) (param $rhs i32) (result i32)
get_local $lhs
get_local $rhs
i32.add)
(export "add" (func $add))
)</pre>
<p>Если вы хотите собственноручно скомпилировать пример, сохраните ранее написанный модуль в файле с именем <code>add.wat</code>, а затем преобразуйте его в двоичный файл с именем <code>add.wasm</code>, используя wabt (подробности смотрите в разделе <a href="/en-US/docs/WebAssembly/Text_format_to_wasm">Перевод текстового формата WebAssembly в wasm</a>).</p>
<p>Затем мы загрузим наш двоичный файл, скомпилируем, создадим его экземпляр и выполним нашу функцию <code>add</code> в коде JavaScript (теперь нам доступна функция <code>add()</code> в свойстве <code><a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Instance/exports">exports</a></code> экземпляра модуля):</p>
<pre class="brush: js">WebAssembly.instantiateStreaming(fetch('add.wasm'))
.then(obj => {
console.log(obj.instance.exports.add(1, 2)); // "3"
});</pre>
<div class="note">
<p><strong>Примечание</strong>: Вы можете найти этот пример на GitHub в файле <a href="https://github.com/mdn/webassembly-examples/blob/master/understanding-text-format/add.html">add.html</a> (смотрите также это <a href="https://mdn.github.io/webassembly-examples/understanding-text-format/add.html">вживую</a>). Также смотрите {{jsxref("WebAssembly.instantiateStreaming()")}} для получения более подробной информации о функции создания экземпляра модуля.</p>
</div>
<h2 id="Изучение_основ">Изучение основ</h2>
<p>Теперь, когда мы рассмотрели простейшие примеры, давайте перейдём к рассмотрению некоторых более сложных возможностей.</p>
<h3 id="Вызов_функций_из_других_функций_в_том_же_модуле">Вызов функций из других функций в том же модуле</h3>
<p>Для вызова функции по индексу или имени используется инструкция <code>call</code>. Например, следующий модуль содержит две функции - первая просто возвращает значение <code>42</code>, вторая возвращает сумму результата вызова первой функции и единицы:</p>
<pre>(module
(func $getAnswer (result i32)
i32.const 42)
(func (export "getAnswerPlus1") (result i32)
call $getAnswer
i32.const 1
i32.add))</pre>
<div class="note">
<p><strong>Примечание</strong>: Инструкция <code>i32.const</code> создаёт 32-разрядное целое число и помещает его в стек. Вы можете поменять <code>i32</code> на любой другой доступный тип данных и изменить значение на любое другое (здесь мы установили значение <code>42</code>).</p>
</div>
<p>В этом примере обратите внимание на секцию объявления экспорта <code>(export “getAnswerPlus1”)</code>, которая находится сразу после объявления второй функции <code>func</code>. Это сокращённый способ объявления, совмещённый с именем функции, которую мы хотим экспортировать.</p>
<p>Функционально это эквивалентно включению отдельного объявления экспорта функции без функции, в любом месте модуля, например:</p>
<pre>(export "getAnswerPlus1" (func $functionName))</pre>
<p>Код JavaScript для вызова экспортируемой функции из нашего модуля выглядит так:</p>
<pre class="brush: js">WebAssembly.instantiateStreaming(fetch('call.wasm'))
.then(obj => {
console.log(obj.instance.exports.getAnswerPlus1()); // "43"
});</pre>
<div class="note">
<p><strong>Примечание</strong>: Вы можете найти этот пример на GitHub как <a href="https://github.com/mdn/webassembly-examples/blob/master/understanding-text-format/call.html">call.html</a> (смотрите также <a href="https://mdn.github.io/webassembly-examples/understanding-text-format/call.html">вживую</a>). Ещё посмотрите <a href="https://github.com/mdn/webassembly-examples/blob/master/wasm-utils.js">wasm-utils.js</a> для метода <code>fetchAndInstantiate()</code>.</p>
</div>
<h3 id="Импорт_функций_из_JavaScript">Импорт функций из JavaScript</h3>
<p>Мы уже видели JavaScript, вызывающий экспортируемые функции модуля WebAssembly, но как насчёт WebAssembly модуля, вызывающего функции JavaScript? WebAssembly не имеет каких либо знаний о внешнем коде JavaScript, но у него есть способ импорта, который может принимать функции из JavaScript или wasm. Давайте посмотрим на пример:</p>
<pre>(module
(import "console" "log" (func $log (param i32)))
(func (export "logIt")
i32.const 13
call $log))</pre>
<p>В инструкции импорта в модуль WebAssembly определено двухуровневое пространство имён, в котором мы указали импортировать функцию <code>log</code> из модуля <code>console</code>. Вы также можете видеть, что экспортируемая функция <code>logIt</code> вызывает импортированную функцию, используя инструкцию <code>call</code>, о которой мы говорили ранее.</p>
<p>Импортируемые функции аналогичны обычным функциям: они имеют сигнатуру, которую WebAssembly проверяет статически, им присваивается индекс (в место которого можно присвоить имя) и их можно вызвать обычным способом.</p>
<p>Функции JavaScript не имеют понятия сигнатуры, поэтому любую функцию JavaScript можно передать независимо от объявленной сигнатуры импорта. Если модуль объявляет импорт, вызывающая сторона (например метод {{jsxref("WebAssembly.instantiate()")}}) должна передать объект импорта, который должен иметь соответствующее свойство.</p>
<p>Для иллюстрации вышесказанного нам нужен объект (назовём его <code>importObject</code>), в котором конечное свойство <code>importObject.console.log</code> должно содержать функцию JavaScript.</p>
<p>Код будет выглядеть следующим образом:</p>
<pre class="brush: js">var importObject = {
console: {
log: function(arg) {
console.log(arg);
}
}
};
WebAssembly.instantiateStreaming(fetch('logger.wasm'), importObject)
.then(obj => {
obj.instance.exports.logIt();
});</pre>
<div class="note">
<p><strong>Примечание</strong>: Этот пример можно найти на GitHub в файле <a href="https://github.com/mdn/webassembly-examples/blob/master/understanding-text-format/logger.html">logger.html</a> (смотрите также <a href="https://mdn.github.io/webassembly-examples/understanding-text-format/logger.html">вживую</a>).</p>
</div>
<h3 id="Определение_глобальных_переменных_WebAssembly">Определение глобальных переменных WebAssembly</h3>
<p>WebAssembly имеет возможность создавать экземпляры глобальных переменных. Они доступны как в коде JavaScript, так и через импорт / экспорт для одного и более экземпляров {{jsxref("WebAssembly.Module")}}. Это очень полезная возможность в плане динамического связывания нескольких модулей.</p>
<p>В текстовом формате WebAssembly это выглядит примерно так (смотрите файл <a href="https://mdn.github.io/webassembly-examples/js-api-examples/global.html">global.html</a> в нашем репозитории на GitHub; смотрите также <a href="https://mdn.github.io/webassembly-examples/js-api-examples/global.html">вживую</a>):</p>
<pre>(module
(global $g (import "js" "global") (mut i32))
(func (export "getGlobal") (result i32)
(get_global $g))
(func (export "incGlobal")
(set_global $g
(i32.add (get_global $g) (i32.const 1))))
)</pre>
<p>Это похоже на то, что мы делали раньше, за исключением того, что мы указываем глобальную переменную с помощью ключевого слова <code>global</code>. Также мы указываем ключевое слово <code>mut</code> вместе с типом данных значения (если хотим, чтобы глобальная переменная была изменяемой).</p>
<p>Чтобы создать эквивалентный код с помощью JavaScript, вы должны использовать конструктор {{jsxref("WebAssembly.Global()")}}:</p>
<pre class="brush: js">const global = new WebAssembly.Global({value:'i32', mutable:true}, 0);</pre>
<h3 id="Память_WebAssembly">Память WebAssembly</h3>
<p>Приведённый выше пример - довольно ужасная функция ведения журнала: она печатает только одно целое число! Что если мы хотим записать текстовую строку? Для работы со строками и другими более сложными типами данных WebAssembly предоставляет <strong>линейную память</strong>. Согласно технологии WebAssembly, линейная память - это просто большой массив байтов, который со временем может увеличиваться. WebAssembly код содержит ряд инструкций, наподобие <code>i32.load</code> и <code>i32.store</code> для чтения и записи значений из <a href="http://webassembly.org/docs/semantics/#linear-memory">линейной памяти</a>.</p>
<p>Со стороны JavaScript, линейная память как будто находится внутри одного большого (расширяющегося) объекта {{domxref("ArrayBuffer")}}.</p>
<p>Таким образом, строка - это просто последовательность байтов где-то внутри этой линейной памяти. Давайте предположим, что мы записали нужную строку байтов в память; как мы передадим эту строку в JavaScript?<br>
Ключевым моментом является то, что JavaScript может создавать экземпляры(объекты) линейной памяти WebAssembly через конструктор {{jsxref("WebAssembly.Memory()")}} и получать доступ к существующему экземпляру памяти (в настоящее время вы можете иметь только один экземпляр памяти на экземпляр модуля), используя соответствующие методы экземпляра модуля. Экземпляр памяти имеет свойство <code><a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Memory/buffer">buffer</a></code>, которое возвращает объект <code>ArrayBuffer</code>, предоставляя всю линейную память модуля.</p>
<p>Объекты памяти могут расширятся с помощью метода <code><a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Memory/grow">Memory.grow()</a></code> из JavaScript. Когда происходит расширение, текущий объект <code>ArrayBuffer</code> не может изменить размер и он отсоединяется. Вместо него создаётся новый объект <code>ArrayBuffer</code>, указывающий на новую, увеличенную память. Пользуясь этими возможностями можно передать строку в JavaScript, её начальный индекс и её длину в линейной памяти.</p>
<p>Хотя есть много разных способов кодировать длину строки в самой строке (например, как в строках в C); для простоты здесь мы просто передаём смещение и длину в качестве параметров:</p>
<pre>(import "console" "log" (func $log (param i32) (param i32)))</pre>
<p>На стороне JavaScript, мы можем использовать <a href="/en-US/docs/Web/API/TextDecoder">TextDecoder API</a>, чтобы легко декодировать наши байты в строку JavaScript. (Мы указываем кодировку utf8, хотя поддерживаются и другие кодировки.)</p>
<pre class="brush: js">function consoleLogString(offset, length) {
var bytes = new Uint8Array(memory.buffer, offset, length);
var string = new TextDecoder('utf8').decode(bytes);
console.log(string);
}</pre>
<p>Последний недостающий фрагмент головоломки - это место, где функция <code>consoleLogString</code> получает доступ к памяти (<code>memory</code>) WebAssembly. WebAssembly даёт нам здесь много гибкости: либо мы можем создать объект <code><a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Memory">Memory</a></code> в коде JavaScript и импортировать его в модуль WebAssembly, или мы можем создать его в модуле WebAssembly и затем экспортировать в JavaScript.</p>
<p>Для простоты, давайте создадим объект памяти в JavaScript и импортируем его в WebAssembly модуль. Напишем следующее объявление импорта <code>(import</code>):</p>
<pre>(import "js" "mem" (memory 1))</pre>
<p>Число <code>1</code> указывает, что импортируемая память должна иметь по крайней мере 1 страницу памяти (WebAssembly определяет страницу как фиксированный блок памяти в 64КБ.)</p>
<p>Давайте взглянем на наш последний вариант модуля, который выводит слово “Hi”. В обычной C программе, мы бы вызывали функцию для выделения памяти для строки. Но так как мы пишем собственную сборку и у нас есть собственная импортируемая память, то мы просто пишем содержание строки в линейную память, используя секцию <code>data</code>. Data-секция во время создания записывает строку байт, начиная с указанного отступа. И она действует также как и <code>.data</code> секция в “родных” форматах для исполнения.</p>
<p>Наш последний вариант модуля выглядит так:</p>
<pre>(module
(import "console" "log" (func $log (param i32 i32)))
(import "js" "mem" (memory 1))
(data (i32.const 0) "Hi")
(func (export "writeHi")
i32.const 0 ;; pass offset 0 to log
i32.const 2 ;; pass length 2 to log
call $log))</pre>
<div class="note">
<p><strong>Примечание</strong>: Обратите внимание, что двойная точка с запятой (<code>;;</code>) позволяет оставлять комментарии в файлах WebAssembly.</p>
</div>
<p>Теперь из JavaScript мы можем создать и передать объект памяти размером в 1 страницу. Результатом работы этого кода будет вывод “Hi” в консоль:</p>
<pre class="brush: js">var memory = new WebAssembly.Memory({initial:1});
var importObject = { console: { log: consoleLogString }, js: { mem: memory } };
WebAssembly.instantiateStreaming(fetch('logger2.wasm'), importObject)
.then(obj => {
obj.instance.exports.writeHi();
});</pre>
<div class="note">
<p><strong>Примечание</strong>: Вы можете найти полный исходный код на GitHub в файле <a href="https://github.com/mdn/webassembly-examples/blob/master/understanding-text-format/logger2.html">logger2.html</a> (также смотрите это <a href="https://mdn.github.io/webassembly-examples/understanding-text-format/logger2.html">вживую</a>).</p>
</div>
<h3 id="Таблицы_WebAssembly">Таблицы WebAssembly</h3>
<p>Чтобы завершить обзор текстового формата WebAssembly, давайте рассмотрим самую сложную и запутанную часть WebAssembly - <strong>таблицы</strong>. Таблицы - это массивы ссылок изменяемого размера, доступ к которым можно получить по индексу из кода WebAssembly.</p>
<p>Чтобы понять, зачем нужны таблицы, нам нужно сначала обратить внимание, что инструкция <code>call</code>, которую мы видели ранее (см. {{anch("Вызов функций из других функций в том же модуле")}}), принимает статический индекс функции и может вызывать только определённую функцию. Но что, если вызываемый элемент будет значением, установленным во время выполнения?</p>
<ul>
<li> В JavaScript это делается постоянно: функции являются ссылочными значениями.</li>
<li> В C/C++ это делается с помощью указателей на функции.</li>
<li> В C++ это делается с помощью виртуальных функций.</li>
</ul>
<p>Для того чтобы сделать это в WebAssembly нужен был отдельный тип инструкции вызова. Поэтому мы создали инструкцию <code>call_indirect</code>, которая принимает операнд динамической функции. Проблема в том, что типы данных, которые мы должны использовать в операндах в WebAssembly, в настоящее время такие: <code>i32</code> / <code>i64</code> / <code>f32</code> / <code>f64</code>.</p>
<p>Для WebAssembly можно было бы создать тип инструкции вызова <code>anyfunc</code> («любой», потому что эта инструкция смогла вызвать функции любой сигнатуры), но, к сожалению, операнд этого типа не может быть сохранён в линейной памяти по соображениям безопасности. Линейная память представляет содержимое хранимых значений в виде незащищённых байтов, и это позволяет содержимому wasm произвольно читать и изменять незащищённые адреса функций, что недопустимо для веб.</p>
<p>Решением стало следующее. Хранить ссылки на функции в таблице и передавать вместо них индексы таблицы, которые являются просто значениями <code>i32</code>. Поэтому операндом инструкции <code>call_indirect</code> может выступить простое значение индекса <code>i32</code>.</p>
<h4 id="Определение_таблицы_в_wasm">Определение таблицы в wasm</h4>
<p>Так как же разместить функции wasm в нашей таблице? Подобно тому, как секции <code>data</code> могут использоваться для инициализации областей линейной памяти байтами, секции <code>elem</code> могут использоваться для инициализации областей таблиц с функциями:</p>
<pre>(module
(table 2 anyfunc)
(elem (i32.const 0) $f1 $f2)
(func $f1 (result i32)
i32.const 42)
(func $f2 (result i32)
i32.const 13)
...
)</pre>
<ul>
<li>
<p>В <code>(table 2 anyfunc)</code>, 2 - это начальный размер таблицы (это означает, что она будет хранить две ссылки), а объявление <code>anyfunc</code> означает, что типом элемента этих ссылок является «функция с любой сигнатурой». В текущей версии WebAssembly, это единственный допустимый тип атрибута, но в будущем будет добавлено больше.</p>
</li>
<li>
<p>Секции функций <code>(func) </code>- это обычные объявления функций модуля wasm. Это те функции, на которые мы будем ссылаться в нашей таблице (каждая из них просто возвращает постоянное значение). Обратите внимание, что порядок объявления секций не имеет значения - вы можете объявить свои функции где угодно и по-прежнему ссылаться на них в секции <code>elem</code>.</p>
</li>
<li>
<p>Секция <code>elem</code> - это список функций, на которые ссылается таблица, в том порядке, в котором они указаны. Здесь можно перечислить любое количество функций, включая их дубликаты.</p>
</li>
<li>
<p>Значение <code>(i32.const 0)</code> внутри секции <code>elem</code> является смещением - его необходимо объявить в начале секции и указать, по какому индексу в таблице ссылок начинают заполняться ссылки на функции. Здесь мы указали 0, а размер таблицы указали как 2 (см. выше), поэтому мы можем заполнить две ссылки на индексы 0 и 1. Если бы мы захотели записать наши ссылки со смещением в 1, то нам нужно было бы написать <code>(i32.const 1)</code>, а размер таблицы должен был быть равен 3.</p>
</li>
</ul>
<div class="note">
<p><strong>Примечание</strong>: Неинициализированным элементам присваивается значение вызова по умолчанию.</p>
</div>
<p>В JavaScript эквивалентный код для создания такого экземпляра таблицы ссылок будет выглядеть примерно так:</p>
<pre class="brush: js">function() {
// table section
var tbl = new WebAssembly.Table({initial:2, element:"anyfunc"});
// function sections:
var f1 = function() { … }
var f2 = function() { … }
// elem section
tbl.set(0, f1);
tbl.set(1, f2);
};</pre>
<h4 id="Использование_таблицы">Использование таблицы</h4>
<p>Мы определили таблицу, которую нам нужно как-то использовать. Для этого добавим следующую секцию кода:</p>
<pre>(type $return_i32 (func (result i32))) ;; if this was f32, type checking would fail
(func (export "callByIndex") (param $i i32) (result i32)
get_local $i
call_indirect (type $return_i32))</pre>
<ul>
<li>Секция <code>(type $return_i32 (func (result i32)))</code> определяет тип с заданным именем <code>$return_i32</code>. Этот тип используется при выполнении проверки сигнатуры функции в таблице функций. Здесь мы указываем, что ссылки должны быть функциями, возвращаемое значение которых должно быть с типом <code>i32</code>.</li>
<li>Далее мы определяем экспортируемую функцию с именем <code>callByIndex</code>. Для единственного параметра функции задан тип <code>i32</code>, которому присвоено имя <code>$i</code>.</li>
<li>Внутри функции мы помещаем одно значение в стек - любое значение, переданное в качестве параметра <code>$i</code> экспортируемой функции.</li>
<li>Наконец, мы используем инструкцию <code>call_indirect</code> для вызова функции из таблицы - она неявно получает значение <code>$i</code> из стека. Конечным результатом будет вызов функции из таблицы с индексом, указанным в <code>$i</code>.</li>
</ul>
<p>Вы также можете объявить параметр <code>call_indirect</code> явно во время вызова инструкции, а не до него (неявным получением из стека), например так:</p>
<pre>(call_indirect (type $return_i32) (get_local $i))</pre>
<p>На языке высокого уровня, таком как JavaScript эти же действия вы можете представить в виде манипуляций с массивом (или, скорее, с объектом), содержащим функции. Псевдокод будет выглядеть примерно так: <code>tbl[i]()</code>.</p>
<p>Итак, вернёмся к проверке типов. Так как в коде WebAssembly проверяются типы, а атрибут <code>anyfunc</code> означает “сигнатура любой функции", мы должны предоставить предполагаемую сигнатуру в месте вызова, поэтому мы включаем тип с именем <code>$return_i32</code>, чтобы сообщить программе, что ожидается функция, возвращающая значение с типом <code>i32</code>. Если вызываемая функция не имеет соответствующей сигнатуры (скажем, вместо неё возвращается <code>f32</code>), выбросится исключение {{jsxref("WebAssembly.RuntimeError")}}.</p>
<p>Так как инструкция <code>call_indirect</code> связывается с таблицей, с которой мы вызываем функцию? Ответ заключается в том, что на данный момент для каждого экземпляра модуля разрешена только одна таблица. Поэтому инструкция <code>call_indirect</code> выполняет неявный вызов именно из этой таблицы. В будущем, когда будет разрешено использование нескольких таблиц, нам нужно будет указать идентификатор таблицы, например так:</p>
<pre>call_indirect $my_spicy_table (type $i32_to_void)</pre>
<p>Весь модуль в целом выглядит следующим образом и может быть найден в нашем примере файла <a href="https://github.com/mdn/webassembly-examples/blob/master/understanding-text-format/wasm-table.wat">wasm-table.wat</a>:</p>
<pre>(module
(table 2 anyfunc)
(func $f1 (result i32)
i32.const 42)
(func $f2 (result i32)
i32.const 13)
(elem (i32.const 0) $f1 $f2)
(type $return_i32 (func (result i32)))
(func (export "callByIndex") (param $i i32) (result i32)
get_local $i
call_indirect (type $return_i32))
)</pre>
<p>Загрузка модуля и использование экспортируемой функции в коде JavaScript будет выглядеть так:</p>
<pre class="brush: js">WebAssembly.instantiateStreaming(fetch('wasm-table.wasm'))
.then(obj => {
console.log(obj.instance.exports.callByIndex(0)); // returns 42
console.log(obj.instance.exports.callByIndex(1)); // returns 13
console.log(obj.instance.exports.callByIndex(2)); // returns an error, because there is no index position 2 in the table
});</pre>
<div class="note">
<p><strong>Примечание</strong>: Этот пример можно найти на GitHub в файле <a href="https://github.com/mdn/webassembly-examples/blob/master/understanding-text-format/wasm-table.html">wasm-table.html</a> (смотрите это также <a href="https://mdn.github.io/webassembly-examples/understanding-text-format/wasm-table.html">вживую</a>)</p>
</div>
<div class="note">
<p><strong>Примечание</strong>: Как и в случае с памятью, таблицы также можно создавать из кода JavaScript (см. <code><a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Table">WebAssembly.Table()</a></code>).</p>
</div>
<h3 id="Изменяющиеся_таблицы_и_динамическое_связывание">Изменяющиеся таблицы и динамическое связывание</h3>
<p>Поскольку JavaScript имеет полный доступ к ссылкам на функции, объект таблицы может быть изменён из кода JavaScript с помощью методов <code><a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Table/grow">grow()</a></code>, <code><a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Table/get">get()</a></code> и <code><a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Table/set">set()</a></code>. Когда WebAssembly получит <a href="http://webassembly.org/docs/gc/">ссылочные типы</a>, код WebAssembly сможет изменять таблицы самостоятельно с помощью инструкций <code>get_elem</code> / <code>set_elem</code>.</p>
<p>Поскольку таблицы являются изменяемыми, их можно использовать для реализации сложных схем <a href="http://webassembly.org/docs/dynamic-linking">динамического связывания</a> во время загрузки и во время выполнения. Когда программа динамически связана, несколько экземпляров могут совместно использовать линейную память и таблицу ссылок. Это похоже на поведение в обычном приложении где несколько скомпилированных <code>.dll</code> совместно используют адресное пространство одного процесса.</p>
<p>Чтобы увидеть это в действии, мы создадим один объект импорта, содержащий объект памяти и объект таблицы. Далее мы передадим этот объект импорта при создании нескольких модулей с помощью метода <code><a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/instantiate">instantiate()</a></code>.</p>
<p>Наши примеры файлов <code>.wat</code> выглядят так:</p>
<p><code>shared0.wat</code>:</p>
<pre>(module
(import "js" "memory" (memory 1))
(import "js" "table" (table 1 anyfunc))
(elem (i32.const 0) $shared0func)
(func $shared0func (result i32)
i32.const 0
i32.load)
)</pre>
<p><code>shared1.wat</code>:</p>
<pre>(module
(import "js" "memory" (memory 1))
(import "js" "table" (table 1 anyfunc))
(type $void_to_i32 (func (result i32)))
(func (export "doIt") (result i32)
i32.const 0
i32.const 42
i32.store ;; store 42 at address 0
i32.const 0
call_indirect (type $void_to_i32))
)</pre>
<p>Они работают следующим образом:</p>
<ol>
<li>Функция <code>shared0func</code> определена в <code>shared0.wat</code> и сохраняется в нашей импортированной таблице.</li>
<li>Эта функция создаёт константу, содержащую значение <code>0</code>, затем инструкция <code>i32.load</code> получает значение из импортированной памяти по предоставленному константой индексу. Предоставленный индекс равен <code>0</code>. Как и другие подобные инструкции, <code>i32.load</code> неявно получает предоставленное значение из стека. Итак, <code>shared0func</code> загружает и возвращает значение, хранящееся в индексе памяти <code>0</code>.</li>
<li>В <code>shared1.wat</code> мы экспортируем функцию с именем <code>doIt</code> - эта функция размещает в стеке две константы, содержащие значения <code>0</code> и <code>42</code>. Затем она вызывает инструкцию <code>i32.store</code> для сохранения предоставленного значения по предоставленному индексу в импортированной памяти. Опять же, инструкция неявно получает эти значения из стека. Поэтому в результате <code>doIt</code> сохраняет значение <code>42</code> в индексе памяти <code>0</code>.</li>
<li>В последней части функции создаётся константа со значением <code>0</code>, затем вызывается функция с этим индексом (<code>0</code>) из таблицы. Это будет функция <code>shared0func</code> модуля <code>shared0.wat</code>, которая ранее была размещена там с помощью секции <code>elem</code>.</li>
<li>При вызове shared0func загружает число <code>42</code>, которые мы сохранили в памяти, с помощью ранее указанной инструкции <code>i32.store</code> в модуле <code>shared1.wat</code>.</li>
</ol>
<div class="note">
<p><strong>Примечание</strong>: Вышеприведённые выражения неявно извлекают значения из стека, но вместо этого вы можете объявить их явно в вызовах инструкций, например:</p>
<pre>(i32.store (i32.const 0) (i32.const 42))
(call_indirect (type $void_to_i32) (i32.const 0))</pre>
</div>
<p>После преобразования текста в модули мы используем файлы <code>shared0.wasm</code> и <code>shared1.wasm</code> в JavaScript с помощью следующего кода:</p>
<pre class="brush: js">var importObj = {
js: {
memory : new WebAssembly.Memory({ initial: 1 }),
table : new WebAssembly.Table({ initial: 1, element: "anyfunc" })
}
};
Promise.all([
WebAssembly.instantiateStreaming(fetch('shared0.wasm'), importObj),
WebAssembly.instantiateStreaming(fetch('shared1.wasm'), importObj)
]).then(function(results) {
console.log(results[1].instance.exports.doIt()); // prints 42
});</pre>
<p>Каждый из компилируемых модулей может импортировать общие объекты памяти и таблицы. Таким образом, они могут совместно использовать одну и ту же линейную память и таблицу ссылок.</p>
<div class="note">
<p><strong>Примечание</strong>: Этот пример можно найти на GitHub в файле <a href="https://github.com/mdn/webassembly-examples/blob/master/understanding-text-format/shared-address-space.html">shared-address-space.html</a> (смотрите это также <a href="https://mdn.github.io/webassembly-examples/understanding-text-format/shared-address-space.html">вживую</a>).</p>
</div>
<h2 id="Резюме">Резюме</h2>
<p>На этом мы завершаем обзор основных компонентов текстового формата WebAssembly и того, как они отображены в WebAssembly JS API.</p>
<h2 id="Смотрите_также">Смотрите также</h2>
<ul>
<li><a href="http://webassembly.org/docs/semantics">Семантика WebAssembly</a> для информации по всем возможным инструкциям.</li>
<li><a href="https://github.com/WebAssembly/spec/blob/master/interpreter/README.md#s-expression-syntax">Грамматика текстового формата</a>, который реализован в интерпретаторе спецификации.</li>
</ul>
|