diff --git a/files/ru/webassembly/c_to_wasm/index.html b/files/ru/webassembly/c_to_wasm/index.html deleted file mode 100644 index 83503987b337e3..00000000000000 --- a/files/ru/webassembly/c_to_wasm/index.html +++ /dev/null @@ -1,185 +0,0 @@ ---- -title: Компиляция кода C/C++ в WebAssembly -slug: WebAssembly/C_to_wasm -translation_of: WebAssembly/C_to_wasm ---- -
{{WebAssemblySidebar}}
- -

После того как вы написали код на C / C ++, вы можете скомпилировать его в WebAssembly, например, с помощью инструмента Emscripten. Давайте посмотрим, как это работает.

- -

Подготовка рабочей среды для Emscripten

- -

Первым делом установим компоненты для дальнейшей работы.

- -

Необходимые компоненты

- -

Установите Emscripten SDK и настройте рабочее окружение используя инструкции: https://kripken.github.io/emscripten-site/docs/getting_started/downloads.html

- -

Компиляция примера

- -

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

- - - -

Мы рассмотрим оба способа ниже.

- -

Создание HTML и JavaScript

- -

Это самый простой способ, который мы рассмотрим. С его помощью вы сможете использовать Emscripten для создания всего что нужно, чтобы ваш код работал в браузере как модуль WebAssembly.

- -
    -
  1. Нам понадобится простой пример для компиляции. Скопируйте следующий код программы на Си и сохраните его в файле hello.c в новой папке на вашем локальном диске: - -
    #include <stdio.h>
    -
    -int main(int argc, char ** argv) {
    -  printf("Hello World\n");
    -}
    -
  2. -
  3. Теперь, используя терминал, перейдите в каталог, в котором находится ваш файл hello.c, и выполните следующую команду: -
    emcc hello.c -s WASM=1 -o hello.html
    -
  4. -
- -

Рассмотрим параметры, которые мы передали компилятору:

- - - -

На этом этапе в вашем каталоге должны находится:

- - - -

Запуск вашего примера

- -

Теперь, всё что нужно чтобы запустить полученный hello.html в браузере, это поддержка WebAssembly. Он включён по умолчанию в Firefox 52+, Chrome 57+ и последних версиях Opera. Также вы можете использовать модули WebAssembly в Firefox 47+, включив флаг javascript.options.wasm в about:config, или в Chrome 51+ и Opera 38+ перейдя в chrome://flags и включив флаг Experimental WebAssembly.

- -

Если все работает как планировалось, вы должны увидеть надпись "Hello world" на открывшейся веб-странице и в JavaScript консоли вашего браузера. Поздравляем, вы только что скомпилировали программу на Си в WebAssembly и запустили её в своём браузере!

- -
-

Примечание: На самом деле, если просто открыть полученный hello.html, то ничего работать не будет. Подразумевается что все файлы находятся на веб-сервере и вы запускаете страницу через localhost/hello.html. Для этих целей можно использовать отладочный веб-сервер Emscripten. Чтобы его запустить, откройте терминал, перейдите в каталог, в котором находятся ваши файлы и выполните команду emrun hello.html

-
- -

Использование собственного HTML шаблона

- -

Вы можете использовать собственный шаблон HTML. Давайте посмотрим, как это сделать:

- -
    -
  1. -

    Прежде всего, сохраните следующий код в файле hello2.c в новом каталоге:

    - -
    #include <stdio.h>
    -
    -int main(int argc, char ** argv) {
    -    printf("Hello World\n");
    -
    -}
    -
  2. -
  3. -

    Найдите файл shell_minimal.html в вашем репозитории emsdk. Скопируйте его в подкаталог html_template внутри вашего нового каталога.

    -
  4. -
  5. -

    Теперь, используя терминал, перейдите в ваш новый каталог и выполните следующую команду:

    - -
    emcc -o hello2.html hello2.c -O3 -s WASM=1 --shell-file html_template/shell_minimal.html
    - -

    В этот раз мы использовали немного другие параметры компиляции:

    - - -
  6. -
  7. -

    Теперь давайте запустим этот пример. Команда, указанная выше, сгенерирует файл hello2.html, который будет иметь тоже содержание что и шаблон, но с некоторым кодом, добавленным в процесс загрузки сгенерированного wasm, запускающим его и т.д. Откройте его в своём браузере, и вы увидите тот же результат, что и прошлом примере.

    -
  8. -
- -
-

Примечание: вы можете указать компилятору создавать только JavaScript-кода, без HTML, используя внутри флага -o, .js вместо .html для формата выходного файла, например emcc -o hello2.js hello2.c -O3 -s WASM=1. После этого вы должны создать свой собственный HTML файл с нуля. Однако так делать не рекомендуется — Emscripten требуется большое количество связывающего кода для обработки операций выделения памяти, утечек памяти и других проблем, которые уже включены в предоставляемый шаблон. Намного легче использовать уже готовое решение, чем создавать свои собственные версии самому.

-
- -

Вызов пользовательской функции, определённой в Си

- -

Если у вас есть функция определённая в коде на Си, которую вы хотите по необходимости вызывать из JavaScript, то вы можете использовать для этого функцию ccall() из Emscripten, и объявление EMSCRIPTEN_KEEPALIVE которое добавит вашу функцию в список экспортируемых функций (см. Почему функции в моем коде исчезают после компиляции и/или я получаю сообщение «Нет функций для обработки»). Давайте посмотрим, как это работает.

- -
    -
  1. -

    Для начала сохраните следующий код в файле hello3.c в новом каталоге:

    - -
    #include <stdio.h>
    -#include <emscripten/emscripten.h>
    -
    -int main(int argc, char ** argv) {
    -    printf("Hello World\n");
    -}
    -
    -#ifdef __cplusplus
    -extern "C" {
    -#endif
    -
    -void EMSCRIPTEN_KEEPALIVE myFunction(int argc, char ** argv) {
    -  printf("MyFunction Called\n");
    -}
    -
    -#ifdef __cplusplus
    -}
    -#endif
    - -

    По умолчанию, код созданный Emscripten, всегда просто вызывает функцию main() , а остальные неиспользуемые функции удаляются. Добавьте определение EMSCRIPTEN_KEEPALIVE перед именем функции чтобы этого не происходило. Также вы должны подключить библиотеку emscripten.h для использования EMSCRIPTEN_KEEPALIVE.

    - -
    -

    Примечание: Мы используем блоки #ifdef чтобы, пример оставался рабочим если вы попытаетесь использовать C++ код. Из за различия в правилах преобразования имён между Си и Си++, этот код может сломаться, но мы написали его так, что функция будет рассматриваться как функция Си даже если вы будете использовать Си++.

    -
    -
  2. -
  3. -

    Теперь добавьте html_template/shell_minimal.html в ваш новый каталог, просто для удобства. В настоящем проекте стоит размещать его в специально определённый каталог.

    -
  4. -
  5. -

    Теперь снова займёмся этапом компиляции. Внутри вашего последнего каталога, используя терминал, скомпилируйте ваш Си код следующей командой. (Обратите внимание что при компиляции обязательно нужно использовать опцию NO_EXIT_RUNTIME, иначе после выполнения функции main(), рабочий цикл будет завершён. Это приведёт, например, к вызову функции atexits и дальше будет невозможно использовать наш скомпилированный код. Другими словами это необходимо для правильной эмуляции Си.)

    - -
    emcc -o hello3.html hello3.c -O3 -s WASM=1 --shell-file html_template/shell_minimal.html -s NO_EXIT_RUNTIME=1  -s EXTRA_EXPORTED_RUNTIME_METHODS='["ccall"]'
    -
  6. -
  7. -

    Если вы снова запустите пример в своём браузере, вы увидите тоже самое что и до этого!

    -
  8. -
  9. -

    Теперь нам нужно вызвать нашу новую функцию myFunction() из JavaScript. Прежде всего, добавьте {{htmlelement("button")}} как показано ниже, чуть выше первого открывающего тега <script type='text/javascript'>.

    - -
    <button class="mybutton">Run myFunction</button>
    -
  10. -
  11. -

    Теперь добавьте следующий код в конце первого элемента {{htmlelement("script")}} (чуть выше закрывающего тега </script>):

    - -
    document.querySelector('.mybutton').addEventListener('click', function(){
    -  alert('check console');
    -  var result = Module.ccall('myFunction', // name of C function
    -                             null, // return type
    -                             null, // argument types
    -                             null); // arguments
    -});
    -
  12. -
- -

Это показывает как использовать ccall() для вызова экспортируемой функции.

- -

Смотрите также

- - diff --git a/files/ru/webassembly/c_to_wasm/index.md b/files/ru/webassembly/c_to_wasm/index.md new file mode 100644 index 00000000000000..61f0b28d2fb8af --- /dev/null +++ b/files/ru/webassembly/c_to_wasm/index.md @@ -0,0 +1,162 @@ +--- +title: Компиляция кода C/C++ в WebAssembly +slug: WebAssembly/C_to_wasm +translation_of: WebAssembly/C_to_wasm +--- +{{WebAssemblySidebar}} + +После того как вы написали код на C / C ++, вы можете скомпилировать его в WebAssembly, например, с помощью инструмента [Emscripten](/ru/docs/Mozilla/Projects/Emscripten). Давайте посмотрим, как это работает. + +## Подготовка рабочей среды для Emscripten + +Первым делом установим компоненты для дальнейшей работы. + +### Необходимые компоненты + +Установите Emscripten SDK и настройте рабочее окружение используя инструкции: + +## Компиляция примера + +Когда рабочее окружение подготовлено, попробуем собрать пример кода на языке Си при помощи Emscripten. Вам доступно большое количество опций для настройки компиляции, но мы рассмотрим только два основных сценария компиляции с использованием Emscripten: + +- Компиляция в wasm и создание HTML-страницы для запуска вашего кода, а также JavaScript-кода, необходимого для работы wasm модуля в веб-среде. +- Просто компиляция в wasm и создание JavaScript-кода. + +Мы рассмотрим оба способа ниже. + +### Создание HTML и JavaScript + +Это самый простой способ, который мы рассмотрим. С его помощью вы сможете использовать Emscripten для создания всего что нужно, чтобы ваш код работал в браузере как модуль WebAssembly. + +1. Нам понадобится простой пример для компиляции. Скопируйте следующий код программы на Си и сохраните его в файле `hello.c` в новой папке на вашем локальном диске: + + ```cpp + #include + + int main(int argc, char ** argv) { + printf("Hello World\n"); + } + ``` + +2. Теперь, используя терминал, перейдите в каталог, в котором находится ваш файл hello.c, и выполните следующую команду: + + ```bash + emcc hello.c -s WASM=1 -o hello.html + ``` + +Рассмотрим параметры, которые мы передали компилятору: + +- `-s WASM=1` — Указывает, что мы хотим получить wasm модуль. Если не использовать этот параметр, по умолчанию Emscripten просто создаёт [asm.js](http://asmjs.org/); +- `-o hello.html` — Указывает, что мы хотим, чтобы Emscripten сгенерировал HTML-страницу `hello.html` запускающую наш код, а также сам модуль wasm и код JavaScript который позволит использовать модуль в веб-среде. + +На этом этапе в вашем каталоге должны находится: + +- Бинарный код модуля wasm (`hello.wasm`) +- Файл JavaScript, содержащий код связывающий нативные функции Си и JavaScript/wasm (`hello.js`) +- HTML-страница для загрузки, компиляции и инициализации wasm модуля, и отображающий его вывод в браузере (`hello.html`) + +### Запуск вашего примера + +Теперь, всё что нужно чтобы запустить полученный `hello.html` в браузере, это поддержка WebAssembly. Он включён по умолчанию в Firefox 52+, Chrome 57+ и последних версиях Opera. Также вы можете использовать модули WebAssembly в Firefox 47+, включив флаг `javascript.options.wasm` в _about:config_, или в Chrome 51+ и Opera 38+ перейдя в _chrome://flags_ и включив флаг _Experimental WebAssembly._ + +Если все работает как планировалось, вы должны увидеть надпись "Hello world" на открывшейся веб-странице и в JavaScript консоли вашего браузера. Поздравляем, вы только что скомпилировали программу на Си в WebAssembly и запустили её в своём браузере! + +> **Примечание:** На самом деле, если просто открыть полученный `hello.html`, то ничего работать не будет. Подразумевается что все файлы находятся на веб-сервере и вы запускаете страницу через `localhost/hello.html`. Для этих целей можно использовать отладочный веб-сервер Emscripten. Чтобы его запустить, откройте терминал, перейдите в каталог, в котором находятся ваши файлы и выполните команду `emrun hello.html` + +### Использование собственного HTML шаблона + +Вы можете использовать собственный шаблон HTML. Давайте посмотрим, как это сделать: + +1. Прежде всего, сохраните следующий код в файле hello2.c в новом каталоге: + + ```cpp + #include + + int main(int argc, char ** argv) { + printf("Hello World\n"); + + } + ``` + +2. Найдите файл `shell_minimal.html` в вашем репозитории emsdk. Скопируйте его в подкаталог `html_template` внутри вашего нового каталога. +3. Теперь, используя терминал, перейдите в ваш новый каталог и выполните следующую команду: + + ```bash + emcc -o hello2.html hello2.c -O3 -s WASM=1 --shell-file html_template/shell_minimal.html + ``` + + В этот раз мы использовали немного другие параметры компиляции: + + - Мы указали `-o hello2.html`, чтобы компилятор по прежнему генерировал необходимый JavaScript-код и `.html` файл. + - Также, мы указали `--shell-file html_template/shell_minimal.html `чтобы компилятор использовал ваш шаблон для создания HTML страницы запускающей этот пример. + +4. Теперь давайте запустим этот пример. Команда, указанная выше, сгенерирует файл `hello2.html`, который будет иметь тоже содержание что и шаблон, но с некоторым кодом, добавленным в процесс загрузки сгенерированного wasm, запускающим его и т.д. Откройте его в своём браузере, и вы увидите тот же результат, что и прошлом примере. + +> **Примечание:** вы можете указать компилятору создавать только JavaScript-кода, без HTML, используя внутри флага `-o`, `.js` вместо `.html` для формата выходного файла, например `emcc -o hello2.js hello2.c -O3 -s WASM=1`. После этого вы должны создать свой собственный HTML файл с нуля. Однако так делать не рекомендуется — Emscripten требуется большое количество связывающего кода для обработки операций выделения памяти, утечек памяти и других проблем, которые уже включены в предоставляемый шаблон. Намного легче использовать уже готовое решение, чем создавать свои собственные версии самому. + +### Вызов пользовательской функции, определённой в Си + +Если у вас есть функция определённая в коде на Си, которую вы хотите по необходимости вызывать из JavaScript, то вы можете использовать для этого функцию `ccall()` из Emscripten, и объявление `EMSCRIPTEN_KEEPALIVE` которое добавит вашу функцию в список экспортируемых функций (см. [Почему функции в моем коде исчезают после компиляции и/или я получаю сообщение «Нет функций для обработки»](https://kripken.github.io/emscripten-site/docs/getting_started/FAQ.html#why-do-functions-in-my-c-c-source-code-vanish-when-i-compile-to-javascript-and-or-i-get-no-functions-to-process)). Давайте посмотрим, как это работает. + +1. Для начала сохраните следующий код в файле `hello3.c` в новом каталоге: + + ```cpp + #include + #include + + int main(int argc, char ** argv) { + printf("Hello World\n"); + } + + #ifdef __cplusplus + extern "C" { + #endif + + void EMSCRIPTEN_KEEPALIVE myFunction(int argc, char ** argv) { + printf("MyFunction Called\n"); + } + + #ifdef __cplusplus + } + #endif + ``` + + По умолчанию, код созданный Emscripten, всегда просто вызывает функцию `main()` , а остальные неиспользуемые функции удаляются. Добавьте определение `EMSCRIPTEN_KEEPALIVE` перед именем функции чтобы этого не происходило. Также вы должны подключить библиотеку `emscripten.h` для использования `EMSCRIPTEN_KEEPALIVE`. + + > **Примечание:** Мы используем блоки `#ifdef` чтобы, пример оставался рабочим если вы попытаетесь использовать C++ код. Из за различия в правилах преобразования имён между Си и Си++, этот код может сломаться, но мы написали его так, что функция будет рассматриваться как функция Си даже если вы будете использовать Си++. + +2. Теперь добавьте `html_template/shell_minimal.html` в ваш новый каталог, просто для удобства. В настоящем проекте стоит размещать его в специально определённый каталог. +3. Теперь снова займёмся этапом компиляции. Внутри вашего последнего каталога, используя терминал, скомпилируйте ваш Си код следующей командой. (Обратите внимание что при компиляции обязательно нужно использовать опцию NO_EXIT_RUNTIME, иначе после выполнения функции `main()`, рабочий цикл будет завершён. Это приведёт, например, к вызову функции atexits и дальше будет невозможно использовать наш скомпилированный код. Другими словами это необходимо для правильной эмуляции Си.) + + ```bash + emcc -o hello3.html hello3.c -O3 -s WASM=1 --shell-file html_template/shell_minimal.html -s NO_EXIT_RUNTIME=1 -s EXTRA_EXPORTED_RUNTIME_METHODS='["ccall"]' + ``` + +4. Если вы снова запустите пример в своём браузере, вы увидите тоже самое что и до этого! +5. Теперь нам нужно вызвать нашу новую функцию `myFunction()` из JavaScript. Прежде всего, добавьте {{htmlelement("button")}} как показано ниже, чуть выше первого открывающего тега ``): + + ```js + document.querySelector('.mybutton').addEventListener('click', function(){ + alert('check console'); + var result = Module.ccall('myFunction', // name of C function + null, // return type + null, // argument types + null); // arguments + }); + ``` + +Это показывает как использовать `ccall()` для вызова экспортируемой функции. + +## Смотрите также + +- [emscripten.org](http://emscripten.org/) — узнайте больше об Emscripten и разнообразии его настроек. +- [Calling compiled C functions from JavaScript using ccall/cwrap](https://kripken.github.io/emscripten-site/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.html#calling-compiled-c-functions-from-javascript-using-ccall-cwrap) +- [Why do functions in my C/C++ source code vanish when I compile to JavaScript, and/or I get No functions to process?](https://kripken.github.io/emscripten-site/docs/getting_started/FAQ.html#why-do-functions-in-my-c-c-source-code-vanish-when-i-compile-to-javascript-and-or-i-get-no-functions-to-process) +- [WebAssembly on Mozilla Research](https://research.mozilla.org/webassembly/) +- [Compiling an Existing C Module to WebAssembly](/ru/docs/WebAssembly/existing_C_to_wasm) diff --git a/files/ru/webassembly/concepts/index.html b/files/ru/webassembly/concepts/index.html deleted file mode 100644 index 87977649c3b98d..00000000000000 --- a/files/ru/webassembly/concepts/index.html +++ /dev/null @@ -1,147 +0,0 @@ ---- -title: Основы WebAssembly -slug: WebAssembly/Concepts -translation_of: WebAssembly/Concepts ---- -
{{WebAssemblySidebar}}
- -

В этой статье объясняются концепции, лежащие в основе работы технологии WebAssembly, включая её цели, проблемы, которые она решает, и то, как она работает в движке рендеринга веб-браузера.

- -

Что такое WebAssembly?

- -

WebAssembly - это технология, предоставляющая новый тип кода, который можно запускать в современных веб-браузерах, что обеспечивает новые функции и значительное повышение производительности. Код для WebAssembly не предназначен для написания вручную, скорее он спроектирован для эффективной компиляции из низкоуровневых исходных языков, таких как C, C++, Rust и.т.д.

- -

WebAssembly ​​позволяет запускать код, написанный на различных языках в web-приложениях почти c естественной скоростью. Это имеет огромное значение для веб-платформы, так как ранее это нельзя было сделать.

- -

Более того, вам даже не нужно знать, как создавать код WebAssembly, чтобы его использовать. Модули WebAssembly можно импортировать в веб-приложение (или Node.js), и экспортировать из них функции для использования через JavaScript. JavaScript-фреймворки могут использовать модули WebAssembly для получения огромных преимуществ в производительности и новых функций, в то же время делая их функциональность легко доступной для веб-разработчиков.

- -

Цели WebAssembly

- -

Технология WebAssembly создаётся как открытый стандарт внутри W3C WebAssembly Community Group со следующими целями:

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

Примечание: Технология WebAssembly также будет иметь возможность использования за пределами веб и JavaScript-сред (см. Встраивание вне Web).

-
- -

Как WebAssembly встраивается в веб-платформу?

- -

Веб-платформа может рассматриваться как состоящая из двух частей:

- -
    -
  • Виртуальная машина (ВМ), на которой выполняется код веб-приложения, например код JavaScript, который обеспечивает работу ваших приложений.
  • -
  • Набор Web API, которые веб-приложение может вызывать для управления функциональными возможностями веб-браузера / устройства и выполнения задач (DOM, CSSOM, WebGL, IndexedDB, Web Audio API, и т.д.)
  • -
- -

Исторически ВМ могла загружать только JavaScript. Раньше нас это вполне устраивало, поскольку JavaScript достаточно мощный, чтобы решать большинство проблем, с которыми мы сталкивались в интернете. Однако мы столкнулись с проблемами производительности, когда попытались использовать JavaScript для более нагруженных сценариев использования, таких как 3D-игры, виртуальная и дополненная реальность, компьютерное зрение, редактирование изображений / видео и в ряде других применений, которые требуют повышенной производительности (см. варианты использования WebAssembly где описано больше идей).

- -

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

- -

Язык WebAssembly отличается от языка JavaScript, но он не предназначен для его замены. Он предназначен для дополнения и работы вместе с JavaScript, что позволяет веб-разработчикам использовать преимущества обоих языков:

- -
    -
  • JavaScript - это язык высокого уровня, гибкий и достаточно выразительный для написания веб-приложений. Он имеет много преимуществ - он динамически типизирован, не требует этапа компиляции и обладает огромной экосистемой, которая предоставляет мощные фреймворки, библиотеки и другие инструменты.
  • -
  • WebAssembly - это низкоуровневый язык, похожий на ассемблер, с компактным двоичным форматом, который работает с почти естественной производительностью и предоставляет низкоуровневые модели памяти для таких языков как C++ и Rust, с целью компиляции, чтобы они могли работать в интернете. (Обратите внимание, что у WebAssembly на будущее есть высокоуровневая цель по поддержке языков со сборщиками мусора в модели памяти).
  • -
- -

С появлением WebAssembly в браузерах виртуальная машина, о которой мы говорили ранее, теперь будет загружать и запускать два типа кода - JavaScript и WebAssembly.

- -

Различные типы кода могут вызывать друг друга по мере необходимости - WebAssembly JavaScript API оборачивает экспортированный код WebAssembly в функции JavaScript, которые можно вызывать обычным способом. А в код WebAssembly можно импортировать и синхронно вызывать обычные функции JavaScript. Фактически, базовая единица кода WebAssembly называется модулем, а модули WebAssembly во многом схожи с модулями ES2015.

- -

Ключевые понятия WebAssembly

- -

Есть несколько ключевых понятий, необходимых для понимания того, как WebAssembly работает в браузере. Все эти понятия отражены 1:1 в WebAssembly JavaScript API.

- -
    -
  • Модуль: Представляет двоичный файл WebAssembly, который был скомпилирован браузером в исполняемый машинный код. Модуль не имеет состояния и, таким образом, как Blob, может быть явным образом разделён между windows и workers (через postMessage()). В модуле есть объявление импорта и экспорта точно такое же, как и в модуле ES2015.
  • -
  • Память: Массив ArrayBuffer изменяемого размера, который содержит линейный массив байтов, считываемых и записываемых инструкциями низкоуровневого доступа к памяти в WebAssembly.
  • -
  • Таблица: Типизированный массив ссылок изменяемого размера (например, для функций), которые не могут быть размещены в памяти в виде байтов (по соображениям безопасности и переносимости).
  • -
  • Экземпляр: Модуль в паре со своим состоянием, которое он использует во время выполнения, включая память, таблицу и набор импортируемых значений. Экземпляр модуля похож на модуль ES2015, который был загружен в определённую глобальную переменную с определённым набором импортов.
  • -
- -

JavaScript API предоставляет разработчикам возможность создавать модули, объекты памяти, таблицы и экземпляры модулей. Получив экземпляр модуля WebAssembly, код JavaScript может синхронно вызывать его экспорты, которые представляются как обычные функции JavaScript. Любые функции JavaScript также могут синхронно вызываться кодом WebAssembly путём передачи этих функций в качестве импорта в экземпляр модуля WebAssembly.

- -

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

- -

В будущем модули WebAssembly будут загружаться так же, как и модули ES2015 (с использованием <script type='module'>), что означает, что JavaScript сможет извлекать, компилировать и импортировать модуль WebAssembly так же легко, как модуль ES2015.

- -

Как я могу использовать WebAssembly в своём приложении?

- -

Выше мы говорили о низкоуровневых примитивах, которые WebAssembly добавляет к веб-платформе: двоичный формат для кода и API для его загрузки и запуска. Теперь давайте поговорим о том, как мы можем использовать эти примитивы на практике.

- -

Экосистема WebAssembly находится на начальной стадии; больше инструментов, несомненно, появится в будущем. На данный момент есть три основные отправные точки:

- -
    -
  • Портирование приложения C/C++ с Emscripten.
  • -
  • Написание или генерация WebAssembly кода непосредственно на уровне сборки.
  • -
  • Написание приложения Rust и выбор WebAssembly в качестве типа вывода при построении.
  • -
- -

Давайте поговорим об этих вариантах:

- -

Портирование из C/C++

- -

Из множества вариантов создания кода WASM есть два наиболее популярных - это онлайн-сборщик wasm или Emscripten. Существует ещё несколько вариантов сборки WASM, таких как:

- - - -

Это отличные ресурсы для людей, которые хотят начать, но у них нет инструментов и возможностей Emscripten.

- -

Инструмент Emscripten способен взять практически любой исходный код C/C++ и скомпилировать его в модуль .wasm, добавив необходимый "связующий" JavaScript-код для загрузки и запуска модуля в HTML-документе и отображения результатов работы.

- -

- -

В двух словах, процесс работает следующим образом:

- -
    -
  1. Сначала Emscripten передаёт код C/C++ в clang + LLVM - набор инструментов компилятора C/C++ с открытым исходным кодом, который поставляется, например, как часть XCode для OSX.
  2. -
  3. Emscripten преобразует скомпилированный результат clang + LLVM в двоичный файл .wasm.
  4. -
  5. Сам по себе код WebAssembly в настоящее время не может напрямую обращаться к DOM; он может вызывать только JavaScript, передавая целочисленные и числа с плавающей точкой примитивные типы данных. Таким образом, чтобы получить доступ к любому Web-API, WebAssembly модуль должен обращаться к JavaScript, который затем вызывает Web-API. Поэтому Emscripten создаёт необходимый для этого связующий код HTML и JavaScript.
  6. -
- -
-

Примечание: В будущем планируется позволить WebAssembly напрямую вызывать Web API.

-
- -

Связующий код JavaScript не так прост, как вы можете себе представить. Прежде всего Emscripten предоставляет популярные библиотеки C/C++, такие как SDL, OpenGL, OpenAL и части POSIX. Эти библиотеки реализованы с точки зрения Web-API, и поэтому каждой из них требуется некоторый код JavaScript для соединения WebAssembly модуля с базовым Web-API.

- -

Таким образом, часть связующего кода предоставляет функциональность каждой библиотеки, используемой кодом C/C++. Связующий код также содержит логику для вызова вышеупомянутых WebAssembly JavaScript API для извлечения, загрузки и запуска файла .wasm.

- -

Сгенерированный HTML-документ загружает связующий файл JavaScript и может записать stdout в {{htmlelement("textarea")}}. Если приложение использует OpenGL, HTML документ будет содержать элемент {{htmlelement("canvas")}}, который будет использоваться для рендеринга. Очень легко изменить тип вывода компиляции Emscripten на любое веб-приложение, которое вам нужно.

- -

Вы можете найти полную документацию по Emscripten на emscripten.org, а также руководство по созданию набора инструментов и компиляции вашего собственного приложения на C/C++ в wasm в разделе Компилирование из C/C++ в WebAssembly.

- -

Написание WebAssembly напрямую

- -

Хотите создать свой собственный компилятор, собственные инструменты или библиотеку JavaScript, которая генерирует WebAssembly во время выполнения?

- -

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

- -

Простое руководство о том, как это сделать, смотрите в нашей статье Перевод из текстового формата WebAssembly в wasm.

- -

Написание приложения Rust с компиляцией в WebAssembly

- -

Благодаря неустанной работе рабочей группы Rust WebAssembly, стало возможно писать Rust код и компилировать его в WebAssembly. В нашей статье Компиляция из Rust в WebAssembly описан процесс установки необходимого набора инструментов, компиляция примера программы Rust в пакет npm для WebAssembly с целью использования его в качестве примера веб-приложения.

- -

Резюме

- -

Эта статья дала вам объяснение того, что такое технология WebAssembly, чем она полезная, как она вписывается в веб и как вы можете её использовать.

- -

Смотрите также

- - diff --git a/files/ru/webassembly/concepts/index.md b/files/ru/webassembly/concepts/index.md new file mode 100644 index 00000000000000..2aeb5671bb74a7 --- /dev/null +++ b/files/ru/webassembly/concepts/index.md @@ -0,0 +1,127 @@ +--- +title: Основы WebAssembly +slug: WebAssembly/Concepts +translation_of: WebAssembly/Concepts +--- +{{WebAssemblySidebar}} + +В этой статье объясняются концепции, лежащие в основе работы технологии WebAssembly, включая её цели, проблемы, которые она решает, и то, как она работает в движке рендеринга веб-браузера. + +## Что такое WebAssembly? + +WebAssembly - это технология, предоставляющая новый тип кода, который можно запускать в современных веб-браузерах, что обеспечивает новые функции и значительное повышение производительности. Код для WebAssembly не предназначен для написания вручную, скорее он спроектирован для эффективной компиляции из низкоуровневых исходных языков, таких как C, C++, Rust и.т.д. + +WebAssembly ​​позволяет запускать код, написанный на различных языках в web-приложениях почти c естественной скоростью. Это имеет огромное значение для веб-платформы, так как ранее это нельзя было сделать. + +Более того, вам даже не нужно знать, как создавать код WebAssembly, чтобы его использовать. Модули WebAssembly можно импортировать в веб-приложение (или Node.js), и экспортировать из них функции для использования через JavaScript. JavaScript-фреймворки могут использовать модули WebAssembly для получения огромных преимуществ в производительности и новых функций, в то же время делая их функциональность легко доступной для веб-разработчиков. + +## Цели WebAssembly + +Технология WebAssembly создаётся как открытый стандарт внутри [W3C WebAssembly Community Group](https://www.w3.org/community/webassembly/) со следующими целями: + +- Быть быстрым, эффективным и переносимым - код WebAssembly может выполняться практически на естественной скорости на разных платформах, используя преимущества [аппаратных возможностей](http://webassembly.org/docs/portability/#assumptions-for-efficient-execution). +- Быть читаемым и отлаживаемым - WebAssembly - это низкоуровневый ассемблерный язык, но он имеет читабельный текстовый формат (спецификация для которого ещё дорабатывается), который позволяет писать, просматривать и отлаживать код вручную. +- Поддерживать безопасность - код WebAssembly предназначен для запуска в безопасной, изолированной среде выполнения. Как и другой веб-код, он будет соблюдать политики безопасности браузера. +- Не разрушать текущий веб - технология WebAssembly разработана так, что она прекрасно сочетается с другими веб-технологиями и поддерживает обратную совместимость. + +> **Примечание:** Технология WebAssembly также будет иметь возможность использования за пределами веб и JavaScript-сред (см. [Встраивание вне Web](http://webassembly.org/docs/non-web/)). + +## Как WebAssembly встраивается в веб-платформу? + +Веб-платформа может рассматриваться как состоящая из двух частей: + +- Виртуальная машина (ВМ), на которой выполняется код веб-приложения, например код JavaScript, который обеспечивает работу ваших приложений. +- Набор [Web API](/ru/docs/Web/API), которые веб-приложение может вызывать для управления функциональными возможностями веб-браузера / устройства и выполнения задач ([DOM](/ru/docs/Web/API/Document_Object_Model), [CSSOM](/ru/docs/Web/API/CSS_Object_Model), [WebGL](/ru/docs/Web/API/WebGL_API), [IndexedDB](/ru/docs/Web/API/IndexedDB_API), [Web Audio API](/ru/docs/Web/API/Web_Audio_API), и т.д.) + +Исторически ВМ могла загружать только JavaScript. Раньше нас это вполне устраивало, поскольку JavaScript достаточно мощный, чтобы решать большинство проблем, с которыми мы сталкивались в интернете. Однако мы столкнулись с проблемами производительности, когда попытались использовать JavaScript для более нагруженных сценариев использования, таких как 3D-игры, виртуальная и дополненная реальность, компьютерное зрение, редактирование изображений / видео и в ряде других применений, которые требуют повышенной производительности (см. [варианты использования WebAssembly](http://webassembly.org/docs/use-cases/) где описано больше идей). + +Кроме того, длительность загрузки, анализа и компиляции очень больших приложений JavaScript может быть непомерно высокой. Мобильные и другие ограниченные в ресурсах платформы могут ещё более понизить производительность. + +Язык WebAssembly отличается от языка JavaScript, но он не предназначен для его замены. Он предназначен для дополнения и работы вместе с JavaScript, что позволяет веб-разработчикам использовать преимущества обоих языков: + +- JavaScript - это язык высокого уровня, гибкий и достаточно выразительный для написания веб-приложений. Он имеет много преимуществ - он динамически типизирован, не требует этапа компиляции и обладает огромной экосистемой, которая предоставляет мощные фреймворки, библиотеки и другие инструменты. +- WebAssembly - это низкоуровневый язык, похожий на ассемблер, с компактным двоичным форматом, который работает с почти естественной производительностью и предоставляет низкоуровневые модели памяти для таких языков как C++ и Rust, с целью компиляции, чтобы они могли работать в интернете. (Обратите внимание, что у WebAssembly на будущее есть [высокоуровневая цель](http://webassembly.org/docs/high-level-goals/) по поддержке языков со сборщиками мусора в модели памяти). + +С появлением WebAssembly в браузерах виртуальная машина, о которой мы говорили ранее, теперь будет загружать и запускать два типа кода - JavaScript и WebAssembly. + +Различные типы кода могут вызывать друг друга по мере необходимости - [WebAssembly JavaScript API](/ru/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly) оборачивает экспортированный код WebAssembly в функции JavaScript, которые можно вызывать обычным способом. А в код WebAssembly можно импортировать и синхронно вызывать обычные функции JavaScript. Фактически, базовая единица кода WebAssembly называется модулем, а модули WebAssembly во многом схожи с модулями ES2015. + +### Ключевые понятия WebAssembly + +Есть несколько ключевых понятий, необходимых для понимания того, как WebAssembly работает в браузере. Все эти понятия отражены 1:1 в [WebAssembly JavaScript API](/ru/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly). + +- **Модуль**: Представляет двоичный файл WebAssembly, который был скомпилирован браузером в исполняемый машинный код. Модуль не имеет состояния и, таким образом, как [Blob](/ru/docs/Web/API/Blob), может быть явным образом разделён между windows и workers (через [`postMessage()`](/en-US/docs/Web/API/MessagePort/postMessage)). В модуле есть объявление импорта и экспорта точно такое же, как и в модуле ES2015. +- **Память**: Массив ArrayBuffer изменяемого размера, который содержит линейный массив байтов, считываемых и записываемых инструкциями низкоуровневого доступа к памяти в WebAssembly. +- **Таблица**: Типизированный массив ссылок изменяемого размера (например, для функций), которые не могут быть размещены в памяти в виде байтов (по соображениям безопасности и переносимости). +- **Экземпляр**: Модуль в паре со своим состоянием, которое он использует во время выполнения, включая память, таблицу и набор импортируемых значений. Экземпляр модуля похож на модуль ES2015, который был загружен в определённую глобальную переменную с определённым набором импортов. + +JavaScript API предоставляет разработчикам возможность создавать модули, объекты памяти, таблицы и экземпляры модулей. Получив экземпляр модуля WebAssembly, код JavaScript может синхронно вызывать его экспорты, которые представляются как обычные функции JavaScript. Любые функции JavaScript также могут синхронно вызываться кодом WebAssembly путём передачи этих функций в качестве импорта в экземпляр модуля WebAssembly. + +Поскольку JavaScript полностью контролирует загрузку, компиляцию и запуск кода WebAssembly, разработчики JavaScript могут рассматривать технологию WebAssembly как расширение JavaScript для эффективной генерации высокопроизводительных функций. + +В будущем модули WebAssembly будут загружаться так же, как и [модули ES2015](https://github.com/WebAssembly/design/issues/1087) (с использованием ` + + +``` + +Наконец, создайте `index.js`, на который мы сослались в HTML-файле, и вставьте: + +```js +const js = import("./node_modules/@yournpmusername/hello-wasm/hello_wasm.js"); +js.then(js => { + js.greet("WebAssembly"); +}); +``` + +Заметьте, что вам нужно будет снова ввести ваше имя для npm. + +Так мы импортируем наш модуль из папки `node_modules`. Это не считается лучшей практикой, но это пример, так что пока сойдёт. Как только файл загрузится, он вызовет функцию `greet` из этого модуля, передав `"WebAssembly"`, как строку. Обратите внимание, что здесь нет ничего особенного, и всё же мы вызываем код на Rust! Насколько JavaScript-код может судить, это просто обычный модуль. + +Мы закончили! Давайте попробуем: + +```bash +$ npm install +$ npm run serve +``` + +Так мы запустим сервер. Откройте и вы увидите алерт с надписью `Hello, WebAssembly!` в нем! Мы успешно обратились из JavaScript в Rust и из Rust в JavaScript. + +## Заключение + +На этом руководство заканчивается, мы надеемся, что вы сочли его для себя полезным. + +В этом направлении кипит бурная и при этом очень интересная деятельность, так что если вы бы хотели помочь что-то улучшить, то загляните в [the Rust Webassembly Working Group](http://fitzgeraldnick.com/2018/02/27/wasm-domain-working-group.html). diff --git a/files/ru/webassembly/understanding_the_text_format/index.html b/files/ru/webassembly/understanding_the_text_format/index.html deleted file mode 100644 index e70d4ccdafd13f..00000000000000 --- a/files/ru/webassembly/understanding_the_text_format/index.html +++ /dev/null @@ -1,533 +0,0 @@ ---- -title: Описание текстового формата WebAssembly -slug: WebAssembly/Understanding_the_text_format -translation_of: WebAssembly/Understanding_the_text_format ---- -
{{WebAssemblySidebar}}
- -

Чтобы люди могли читать и редактировать код WebAssembly, существует текстовое представление двоичного формата wasm. Это промежуточная форма, предназначенная для отображения в текстовых редакторах, средствах разработки браузеров и т. д. В этой статье объясняется, как работает этот текстовый формат с точки зрения синтаксиса, как он связан с байт-кодом, который он представляет и оболочками объектов wasm в JavaScript.

- -
-

Примечание: Ознакомление с данной статьёй может оказаться излишним, если вы веб-разработчик, который просто хочет загрузить модуль wasm на страницу и использовать его в своём коде (см. Использование WebAssembly JavaScript API). Эта статья будет наиболее полезной, если вы хотите написать несколько модулей wasm для оптимизации производительности вашей библиотеки JavaScript или создать свой собственный компилятор WebAssembly.

-
- -

S-выражения

- -

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

- -

Во-первых, давайте посмотрим, как выглядит S-выражение. Каждый узел дерева входит в пару круглых скобок - ( ... ). Первая метка в скобках сообщает вам, какой это тип узла, за ним следует разделённый пробелами список атрибутов или дочерних узлов. Давайте рассмотрим, что означает следующее S-выражение WebAssembly:

- -
(module (memory 1) (func))
- -

Это выражение представляет дерево с корневым узлом «module» и двумя дочерними узлами - узлом «memory» с атрибутом «1» и узлом «func». Мы вскоре увидим, что на самом деле означают эти узлы.

- -

Самый простой модуль

- -

Давайте начнём с самого простого модуля wasm.

- -
(module)
- -

Этот модуль полностью пуст, но является допустимым.

- -

Если мы сейчас преобразуем наш модуль в двоичный формат (см. Перевод текстового формата WebAssembly в wasm), мы увидим только 8-байтовый заголовок модуля, описанный в двоичном формате:

- -
0000000: 0061 736d              ; WASM_BINARY_MAGIC
-0000004: 0100 0000              ; WASM_BINARY_VERSION
- -

Добавление функциональности в ваш модуль

- -

Хорошо, это не очень интересно, давайте добавим немного исполняемого кода в этот модуль.

- -

Весь код в модуле сгруппирован в функции, которые имеют следующую структуру псевдокода:

- -
( func <signature> <locals> <body> )
- -
    -
  • signature объявляет, что функция принимает (параметры) и возвращает (возвращаемые значения).
  • -
  • locals похожи на переменные в JavaScript, но с определёнными явными типами.
  • -
  • body - это просто линейный список низкоуровневых инструкций.
  • -
- -

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

- -

Сигнатуры и параметры

- -

Сигнатура - это последовательность объявлений типов параметров, за которыми следует список объявлений возвращаемых типов. Здесь стоит отметить, что:

- -
    -
  • Отсутствие возвращаемого типа (result) означает, что функция ничего не возвращает.
  • -
  • В текущей версии WebAssembly может быть не более 1 возвращаемого типа, но позже это значение будет изменено на любое число.
  • -
- -

Каждый параметр имеет явно объявленный тип; у wasm в настоящее время есть четыре доступных типа:

- -
    -
  • i32: 32-разрядное целое число
  • -
  • i64: 64-разрядное целое число
  • -
  • f32: 32-разрядное число с плавающей точкой
  • -
  • f64: 64-разрядное число с плавающей точкой
  • -
- -

Один параметр можно записать как (param i32), а тип возвращаемого значения как (result i32). Двоичная функция, которая принимает два 32-разрядных целых числа и возвращает 64-разрядное число с плавающей запятой, будет записана следующим образом:

- -
(func (param i32) (param i32) (result f64) ... )
- -

После сигнатуры перечисляются локальные переменные с указанием типа, например (local i32). Параметры в сигнатуре приравниваются к локальным переменным, которые инициализируются значением соответствующего аргумента, переданного вызывающей стороной.

- -

Получение и установка локальных переменных и параметров функции

- -

И параметры и локальные переменные могут быть прочитаны и записаны в теле функции с помощью инструкций get_local и set_local.

- -

Инструкции get_local и set_local ссылаются по индексу на параметр, который должен быть получен или установлен: сначала считаются параметры, а затем локальные переменные в порядке их объявления. Объясним это на примере следующей функции:

- -
(func (param i32) (param f32) (local f64)
-  get_local 0
-  get_local 1
-  get_local 2)
- -

Инструкция get_local 0 получит параметр i32, get_local 1 получит параметр f32, а get_local 2 получит локальную переменную local f64.

- -

Использование числовых индексов для ссылки на элементы может сбивать с толку и раздражать, поэтому текстовый формат позволяет присваивать имена параметрам, локальным переменным и большинству других элементов. Для этого нужно просто добавить имя с префиксом символа доллара ($) непосредственно перед объявлением типа.

- -

Таким образом, можно переписать нашу сигнатуру так:

- -
(func (param $p1 i32) (param $p2 f32) (local $loc f64) …)
- -

После чего можно было бы написать инструкцию получения get_local $p1 вместо get_local 0 и т.д. (Обратите внимание, что, когда этот текст преобразуется в двоичный файл, двоичный файл будет содержать только индексы.)

- -

Стековые машины

- -

Прежде чем мы сможем написать тело функции, мы должны поговорить ещё о стековых машинах. Хотя браузер компилирует wasm-код во что-то более эффективное, выполнение его определяется в терминах стековой машины, где основная идея заключается в том, что каждый тип инструкции получает или помещает определённое количество значений i32 / i64 / f32 / f64 в стек или из стека.

- -

Например, инструкция get_local предназначена для помещения значения локальной переменной, которое она считала, в стек. А инструкция i32.add получает два значения i32 (неявно получает два предыдущих значения, помещённых в стек), вычисляет их сумму и помещает назад в стек результат вычисления i32.

- -

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

- -
(func (param $p i32)
-  get_local $p
-  get_local $p
-  i32.add)
- -

Стек будет содержать ровно одно значение i32 - результат выполнения выражения ($p + $p), которое обработалось инструкцией i32.add. Возвращаемое значение функции - это последнее значение, оставленное в стеке.

- -

Правила валидации WebAssembly гарантируют, выполнение следующего: если вы объявляете тип возвращаемого значения функции как (result f32), то стек должен содержать ровно одно значение типа f32 в конце. Если тип результата отсутствует, стек должен быть пустым.

- -

Тело функции

- -

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

- -
(module
-  (func (param $lhs i32) (param $rhs i32) (result i32)
-    get_local $lhs
-    get_local $rhs
-    i32.add))
- -

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

- -

Есть ещё много инструкций, которые можно поместить в тело функции. Сейчас мы начнём с простых, а далее вы увидите гораздо больше примеров по мере продвижения. Полный список доступных инструкций смотрите в справочнике по семантике webassembly.org.

- -

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

- -

Определение нашей функции само по себе почти ничего не делает - теперь нам нужно её вызвать. Как мы это сделаем? Как и в модуле ES2015, функции wasm должны быть явно экспортированы инструкцией export внутри модуля.

- -

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

- -
(func $add … )
- -

Теперь нам нужно добавить объявление экспорта:

- -
(export "add" (func $add))
- -

Здесь add - это имя, по которому функция будет идентифицироваться в коде JavaScript, а $add определяет, какая функция внутри модуля WebAssembly будет экспортироваться.

- -

Итак, наш последний вариант модуля (на данный момент) выглядит так:

- -
(module
-  (func $add (param $lhs i32) (param $rhs i32) (result i32)
-    get_local $lhs
-    get_local $rhs
-    i32.add)
-  (export "add" (func $add))
-)
- -

Если вы хотите собственноручно скомпилировать пример, сохраните ранее написанный модуль в файле с именем add.wat, а затем преобразуйте его в двоичный файл с именем add.wasm, используя wabt (подробности смотрите в разделе Перевод текстового формата WebAssembly в wasm).

- -

Затем мы загрузим наш двоичный файл, скомпилируем, создадим его экземпляр и выполним нашу функцию add в коде JavaScript (теперь нам доступна функция add() в свойстве exports экземпляра модуля):

- -
WebAssembly.instantiateStreaming(fetch('add.wasm'))
-.then(obj => {
-   console.log(obj.instance.exports.add(1, 2));  // "3"
-});
- -
-

Примечание: вы можете найти этот пример на GitHub в файле add.html (смотрите также это вживую). Также смотрите {{jsxref("WebAssembly.instantiateStreaming()")}} для получения более подробной информации о функции создания экземпляра модуля.

-
- -

Изучение основ

- -

Теперь, когда мы рассмотрели простейшие примеры, давайте перейдём к рассмотрению некоторых более сложных возможностей.

- -

Вызов функций из других функций в том же модуле

- -

Для вызова функции по индексу или имени используется инструкция call. Например, следующий модуль содержит две функции - первая просто возвращает значение 42, вторая возвращает сумму результата вызова первой функции и единицы:

- -
(module
-  (func $getAnswer (result i32)
-    i32.const 42)
-  (func (export "getAnswerPlus1") (result i32)
-    call $getAnswer
-    i32.const 1
-    i32.add))
- -
-

Примечание: Инструкция i32.const создаёт 32-разрядное целое число и помещает его в стек. Вы можете поменять i32 на любой другой доступный тип данных и изменить значение на любое другое (здесь мы установили значение 42).

-
- -

В этом примере обратите внимание на секцию объявления экспорта (export “getAnswerPlus1”), которая находится сразу после объявления второй функции func. Это сокращённый способ объявления, совмещённый с именем функции, которую мы хотим экспортировать.

- -

Функционально это эквивалентно включению отдельного объявления экспорта функции без функции, в любом месте модуля, например:

- -
(export "getAnswerPlus1" (func $functionName))
- -

Код JavaScript для вызова экспортируемой функции из нашего модуля выглядит так:

- -
WebAssembly.instantiateStreaming(fetch('call.wasm'))
-.then(obj => {
-   console.log(obj.instance.exports.getAnswerPlus1());  // "43"
-});
- -
-

Примечание: вы можете найти этот пример на GitHub как call.html (смотрите также вживую). Ещё посмотрите wasm-utils.js для метода fetchAndInstantiate().

-
- -

Импорт функций из JavaScript

- -

Мы уже видели JavaScript, вызывающий экспортируемые функции модуля WebAssembly, но как насчёт WebAssembly модуля, вызывающего функции JavaScript? WebAssembly не имеет каких либо знаний о внешнем коде JavaScript, но у него есть способ импорта, который может принимать функции из JavaScript или wasm. Давайте посмотрим на пример:

- -
(module
-  (import "console" "log" (func $log (param i32)))
-  (func (export "logIt")
-    i32.const 13
-    call $log))
- -

В инструкции импорта в модуль WebAssembly определено двухуровневое пространство имён, в котором мы указали импортировать функцию log из модуля console. Вы также можете видеть, что экспортируемая функция logIt вызывает импортированную функцию, используя инструкцию call, о которой мы говорили ранее.

- -

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

- -

Функции JavaScript не имеют понятия сигнатуры, поэтому любую функцию JavaScript можно передать независимо от объявленной сигнатуры импорта. Если модуль объявляет импорт, вызывающая сторона (например метод {{jsxref("WebAssembly.instantiate()")}}) должна передать объект импорта, который должен иметь соответствующее свойство.

- -

Для иллюстрации вышесказанного нам нужен объект (назовём его importObject), в котором конечное свойство importObject.console.log должно содержать функцию JavaScript.

- -

Код будет выглядеть следующим образом:

- -
var importObject = {
-  console: {
-    log: function(arg) {
-      console.log(arg);
-    }
-  }
-};
-
-WebAssembly.instantiateStreaming(fetch('logger.wasm'), importObject)
-.then(obj => {
-  obj.instance.exports.logIt();
-});
- -
-

Примечание: Этот пример можно найти на GitHub в файле logger.html (смотрите также вживую).

-
- -

Определение глобальных переменных WebAssembly

- -

WebAssembly имеет возможность создавать экземпляры глобальных переменных. Они доступны как в коде JavaScript, так и через импорт / экспорт для одного и более экземпляров {{jsxref("WebAssembly.Module")}}. Это очень полезная возможность в плане динамического связывания нескольких модулей.

- -

В текстовом формате WebAssembly это выглядит примерно так (смотрите файл global.html в нашем репозитории на GitHub; смотрите также вживую):

- -
(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))))
-)
- -

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

- -

Чтобы создать эквивалентный код с помощью JavaScript, вы должны использовать конструктор {{jsxref("WebAssembly.Global()")}}:

- -
const global = new WebAssembly.Global({value:'i32', mutable:true}, 0);
- -

Память WebAssembly

- -

Приведённый выше пример - довольно ужасная функция ведения журнала: она печатает только одно целое число! Что если мы хотим записать текстовую строку? Для работы со строками и другими более сложными типами данных WebAssembly предоставляет линейную память. Согласно технологии WebAssembly, линейная память - это просто большой массив байтов, который со временем может увеличиваться. WebAssembly код содержит ряд инструкций, наподобие i32.load и i32.store для чтения и записи значений из линейной памяти.

- -

Со стороны JavaScript, линейная память как будто находится внутри одного большого (расширяющегося) объекта {{domxref("ArrayBuffer")}}.

- -

Таким образом, строка - это просто последовательность байтов где-то внутри этой линейной памяти. Давайте предположим, что мы записали нужную строку байтов в память; как мы передадим эту строку в JavaScript?
- Ключевым моментом является то, что JavaScript может создавать экземпляры(объекты) линейной памяти WebAssembly через конструктор {{jsxref("WebAssembly.Memory()")}} и получать доступ к существующему экземпляру памяти (в настоящее время вы можете иметь только один экземпляр памяти на экземпляр модуля), используя соответствующие методы экземпляра модуля. Экземпляр памяти имеет свойство buffer, которое возвращает объект ArrayBuffer, предоставляя всю линейную память модуля.

- -

Объекты памяти могут расширятся с помощью метода Memory.grow() из JavaScript. Когда происходит расширение, текущий объект ArrayBuffer не может изменить размер и он отсоединяется. Вместо него создаётся новый объект ArrayBuffer, указывающий на новую, увеличенную память. Пользуясь этими возможностями можно передать строку в JavaScript, её начальный индекс и её длину в линейной памяти.

- -

Хотя есть много разных способов кодировать длину строки в самой строке (например, как в строках в C); для простоты здесь мы просто передаём смещение и длину в качестве параметров:

- -
(import "console" "log" (func $log (param i32) (param i32)))
- -

На стороне JavaScript, мы можем использовать TextDecoder API, чтобы легко декодировать наши байты в строку JavaScript. (Мы указываем кодировку utf8, хотя поддерживаются и другие кодировки.)

- -
function consoleLogString(offset, length) {
-  var bytes = new Uint8Array(memory.buffer, offset, length);
-  var string = new TextDecoder('utf8').decode(bytes);
-  console.log(string);
-}
- -

Последний недостающий фрагмент головоломки - это место, где функция consoleLogString получает доступ к памяти (memory) WebAssembly. WebAssembly даёт нам здесь много гибкости: либо мы можем создать объект Memory в коде JavaScript и импортировать его в модуль WebAssembly, или мы можем создать его в модуле WebAssembly и затем экспортировать в JavaScript.

- -

Для простоты, давайте создадим объект памяти в JavaScript и импортируем его в WebAssembly модуль. Напишем следующее объявление импорта (import):

- -
(import "js" "mem" (memory 1))
- -

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

- -

Давайте взглянем на наш последний вариант модуля, который выводит слово “Hi”. В обычной C программе, мы бы вызывали функцию для выделения памяти для строки. Но так как мы пишем собственную сборку и у нас есть собственная импортируемая память, то мы просто пишем содержание строки в линейную память, используя секцию data. Data-секция во время создания записывает строку байт, начиная с указанного отступа. И она действует также как и .data секция в “родных” форматах для исполнения.

- -

Наш последний вариант модуля выглядит так:

- -
(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))
- -
-

Примечание: Обратите внимание, что двойная точка с запятой (;;) позволяет оставлять комментарии в файлах WebAssembly.

-
- -

Теперь из JavaScript мы можем создать и передать объект памяти размером в 1 страницу. Результатом работы этого кода будет вывод “Hi” в консоль:

- -
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();
-});
- -
-

Примечание: вы можете найти полный исходный код на GitHub в файле logger2.html (также смотрите это вживую).

-
- -

Таблицы WebAssembly

- -

Чтобы завершить обзор текстового формата WebAssembly, давайте рассмотрим самую сложную и запутанную часть WebAssembly - таблицы. Таблицы - это массивы ссылок изменяемого размера, доступ к которым можно получить по индексу из кода WebAssembly.

- -

Чтобы понять, зачем нужны таблицы, нам нужно сначала обратить внимание, что инструкция call, которую мы видели ранее (см. Вызов функций из других функций в том же модуле), принимает статический индекс функции и может вызывать только определённую функцию. Но что, если вызываемый элемент будет значением, установленным во время выполнения?

- -
    -
  • В JavaScript это делается постоянно: функции являются ссылочными значениями.
  • -
  • В C/C++ это делается с помощью указателей на функции.
  • -
  • В C++ это делается с помощью виртуальных функций.
  • -
- -

Для того чтобы сделать это в WebAssembly нужен был отдельный тип инструкции вызова. Поэтому мы создали инструкцию call_indirect, которая принимает операнд динамической функции. Проблема в том, что типы данных, которые мы должны использовать в операндах в WebAssembly, в настоящее время такие: i32 / i64 / f32 / f64.

- -

Для WebAssembly можно было бы создать тип инструкции вызова anyfunc («любой», потому что эта инструкция смогла вызвать функции любой сигнатуры), но, к сожалению, операнд этого типа не может быть сохранён в линейной памяти по соображениям безопасности. Линейная память представляет содержимое хранимых значений в виде незащищённых байтов, и это позволяет содержимому wasm произвольно читать и изменять незащищённые адреса функций, что недопустимо для веб.

- -

Решением стало следующее. Хранить ссылки на функции в таблице и передавать вместо них индексы таблицы, которые являются просто значениями i32. Поэтому операндом инструкции call_indirect может выступить простое значение индекса i32.

- -

Определение таблицы в wasm

- -

Так как же разместить функции wasm в нашей таблице? Подобно тому, как секции data могут использоваться для инициализации областей линейной памяти байтами, секции elem могут использоваться для инициализации областей таблиц с функциями:

- -
(module
-  (table 2 anyfunc)
-  (elem (i32.const 0) $f1 $f2)
-  (func $f1 (result i32)
-    i32.const 42)
-  (func $f2 (result i32)
-    i32.const 13)
-  ...
-)
- -
    -
  • -

    В (table 2 anyfunc), 2 - это начальный размер таблицы (это означает, что она будет хранить две ссылки), а объявление anyfunc означает, что типом элемента этих ссылок является «функция с любой сигнатурой». В текущей версии WebAssembly, это единственный допустимый тип атрибута, но в будущем будет добавлено больше.

    -
  • -
  • -

    Секции функций (func) - это обычные объявления функций модуля wasm. Это те функции, на которые мы будем ссылаться в нашей таблице (каждая из них просто возвращает постоянное значение). Обратите внимание, что порядок объявления секций не имеет значения - вы можете объявить свои функции где угодно и по-прежнему ссылаться на них в секции elem.

    -
  • -
  • -

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

    -
  • -
  • -

    Значение (i32.const 0) внутри секции elem является смещением - его необходимо объявить в начале секции и указать, по какому индексу в таблице ссылок начинают заполняться ссылки на функции. Здесь мы указали 0, а размер таблицы указали как 2 (см. выше), поэтому мы можем заполнить две ссылки на индексы 0 и 1. Если бы мы захотели записать наши ссылки со смещением в 1, то нам нужно было бы написать (i32.const 1), а размер таблицы должен был быть равен 3.

    -
  • -
- -
-

Примечание: Неинициализированным элементам присваивается значение вызова по умолчанию.

-
- -

В JavaScript эквивалентный код для создания такого экземпляра таблицы ссылок будет выглядеть примерно так:

- -
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);
-};
- -

Использование таблицы

- -

Мы определили таблицу, которую нам нужно как-то использовать. Для этого добавим следующую секцию кода:

- -
(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))
- -
    -
  • Секция (type $return_i32 (func (result i32))) определяет тип с заданным именем $return_i32. Этот тип используется при выполнении проверки сигнатуры функции в таблице функций. Здесь мы указываем, что ссылки должны быть функциями, возвращаемое значение которых должно быть с типом i32.
  • -
  • Далее мы определяем экспортируемую функцию с именем callByIndex. Для единственного параметра функции задан тип i32, которому присвоено имя $i.
  • -
  • Внутри функции мы помещаем одно значение в стек - любое значение, переданное в качестве параметра $i экспортируемой функции.
  • -
  • Наконец, мы используем инструкцию call_indirect для вызова функции из таблицы - она ​​неявно получает значение $i из стека. Конечным результатом будет вызов функции из таблицы с индексом, указанным в $i.
  • -
- -

Вы также можете объявить параметр call_indirect явно во время вызова инструкции, а не до него (неявным получением из стека), например так:

- -
(call_indirect (type $return_i32) (get_local $i))
- -

На языке высокого уровня, таком как JavaScript эти же действия вы можете представить в виде манипуляций с массивом (или, скорее, с объектом), содержащим функции. Псевдокод будет выглядеть примерно так: tbl[i]().

- -

Итак, вернёмся к проверке типов. Так как в коде WebAssembly проверяются типы, а атрибут anyfunc означает “сигнатура любой функции", мы должны предоставить предполагаемую сигнатуру в месте вызова, поэтому мы включаем тип с именем $return_i32, чтобы сообщить программе, что ожидается функция, возвращающая значение с типом i32. Если вызываемая функция не имеет соответствующей сигнатуры (скажем, вместо неё возвращается f32), выбросится исключение {{jsxref("WebAssembly.RuntimeError")}}.

- -

Так как инструкция call_indirect связывается с таблицей, с которой мы вызываем функцию? Ответ заключается в том, что на данный момент для каждого экземпляра модуля разрешена только одна таблица. Поэтому инструкция call_indirect выполняет неявный вызов именно из этой таблицы. В будущем, когда будет разрешено использование нескольких таблиц, нам нужно будет указать идентификатор таблицы, например так:

- -
call_indirect $my_spicy_table (type $i32_to_void)
- -

Весь модуль в целом выглядит следующим образом и может быть найден в нашем примере файла wasm-table.wat:

- -
(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))
-)
- -

Загрузка модуля и использование экспортируемой функции в коде JavaScript будет выглядеть так:

- -
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
-});
- -
-

Примечание: Этот пример можно найти на GitHub в файле wasm-table.html (смотрите это также вживую)

-
- -
-

Примечание: Как и в случае с памятью, таблицы также можно создавать из кода JavaScript (см. WebAssembly.Table()).

-
- -

Изменяющиеся таблицы и динамическое связывание

- -

Поскольку JavaScript имеет полный доступ к ссылкам на функции, объект таблицы может быть изменён из кода JavaScript с помощью методов grow(), get() и set(). Когда WebAssembly получит ссылочные типы, код WebAssembly сможет изменять таблицы самостоятельно с помощью инструкций get_elem / set_elem.

- -

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

- -

Чтобы увидеть это в действии, мы создадим один объект импорта, содержащий объект памяти и объект таблицы. Далее мы передадим этот объект импорта при создании нескольких модулей с помощью метода instantiate().

- -

Наши примеры файлов .wat выглядят так:

- -

shared0.wat:

- -
(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)
-)
- -

shared1.wat:

- -
(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))
-)
- -

Они работают следующим образом:

- -
    -
  1. Функция shared0func определена в shared0.wat и сохраняется в нашей импортированной таблице.
  2. -
  3. Эта функция создаёт константу, содержащую значение 0, затем инструкция i32.load получает значение из импортированной памяти по предоставленному константой индексу. Предоставленный индекс равен 0. Как и другие подобные инструкции, i32.load неявно получает предоставленное значение из стека. Итак, shared0func загружает и возвращает значение, хранящееся в индексе памяти 0.
  4. -
  5. В shared1.wat мы экспортируем функцию с именем doIt - эта функция размещает в стеке две константы, содержащие значения 0 и 42. Затем она вызывает инструкцию i32.store для сохранения предоставленного значения по предоставленному индексу в импортированной памяти. Опять же, инструкция неявно получает эти значения из стека. Поэтому в результате doIt сохраняет значение 42 в индексе памяти 0.
  6. -
  7. В последней части функции создаётся константа со значением 0, затем вызывается функция с этим индексом (0) из таблицы. Это будет функция shared0func модуля shared0.wat, которая ранее была размещена там с помощью секции elem.
  8. -
  9. При вызове shared0func загружает число 42, которые мы сохранили в памяти, с помощью ранее указанной инструкции i32.store в модуле shared1.wat.
  10. -
- -
-

Примечание: Вышеприведённые выражения неявно извлекают значения из стека, но вместо этого вы можете объявить их явно в вызовах инструкций, например:

- -
(i32.store (i32.const 0) (i32.const 42))
-(call_indirect (type $void_to_i32) (i32.const 0))
-
- -

После преобразования текста в модули мы используем файлы shared0.wasm и shared1.wasm в JavaScript с помощью следующего кода:

- -
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
-});
- -

Каждый из компилируемых модулей может импортировать общие объекты памяти и таблицы. Таким образом, они могут совместно использовать одну и ту же линейную память и таблицу ссылок.

- -
-

Примечание: Этот пример можно найти на GitHub в файле shared-address-space.html (смотрите это также вживую).

-
- -

Резюме

- -

На этом мы завершаем обзор основных компонентов текстового формата WebAssembly и того, как они отображены в WebAssembly JS API.

- -

Смотрите также

- - diff --git a/files/ru/webassembly/understanding_the_text_format/index.md b/files/ru/webassembly/understanding_the_text_format/index.md new file mode 100644 index 00000000000000..ed1eadc0275058 --- /dev/null +++ b/files/ru/webassembly/understanding_the_text_format/index.md @@ -0,0 +1,557 @@ +--- +title: Описание текстового формата WebAssembly +slug: WebAssembly/Understanding_the_text_format +translation_of: WebAssembly/Understanding_the_text_format +--- +{{WebAssemblySidebar}} + +Чтобы люди могли читать и редактировать код WebAssembly, существует текстовое представление двоичного формата wasm. Это промежуточная форма, предназначенная для отображения в текстовых редакторах, средствах разработки браузеров и т. д. В этой статье объясняется, как работает этот текстовый формат с точки зрения синтаксиса, как он связан с байт-кодом, который он представляет и оболочками объектов wasm в JavaScript. + +> **Примечание:** Ознакомление с данной статьёй может оказаться излишним, если вы веб-разработчик, который просто хочет загрузить модуль wasm на страницу и использовать его в своём коде (см. [Использование WebAssembly JavaScript API](/ru/docs/WebAssembly/Using_the_JavaScript_API)). Эта статья будет наиболее полезной, если вы хотите написать несколько модулей wasm для оптимизации производительности вашей библиотеки JavaScript или создать свой собственный компилятор WebAssembly. + +## S-выражения + +Как в двоичном, так и в текстовом форматах основным блоком кода в WebAssembly является модуль. В текстовом формате модуль представлен как одно большое S-выражение. S-выражения - это очень старый и очень простой текстовый формат для представления деревьев. И поэтому мы можем думать о модуле как о дереве узлов, которые описывают структуру модуля и его код. В отличие от абстрактного синтаксического дерева в языке программирования, дерево WebAssembly довольно плоское и состоит в основном из списков инструкций. + +Во-первых, давайте посмотрим, как выглядит S-выражение. Каждый узел дерева входит в пару круглых скобок - `( ... )`. Первая метка в скобках сообщает вам, какой это тип узла, за ним следует разделённый пробелами список атрибутов или дочерних узлов. Давайте рассмотрим, что означает следующее S-выражение WebAssembly: + +``` +(module (memory 1) (func)) +``` + +Это выражение представляет дерево с корневым узлом «module» и двумя дочерними узлами - узлом «memory» с атрибутом «1» и узлом «func». Мы вскоре увидим, что на самом деле означают эти узлы. + +### Самый простой модуль + +Давайте начнём с самого простого модуля wasm. + +``` +(module) +``` + +Этот модуль полностью пуст, но является допустимым. + +Если мы сейчас преобразуем наш модуль в двоичный формат (см. [Перевод текстового формата WebAssembly в wasm](/ru/docs/WebAssembly/Text_format_to_wasm)), мы увидим только 8-байтовый заголовок модуля, описанный в [двоичном формате](http://webassembly.org/docs/binary-encoding/#high-level-structure): + +``` +0000000: 0061 736d ; WASM_BINARY_MAGIC +0000004: 0100 0000 ; WASM_BINARY_VERSION +``` + +### Добавление функциональности в ваш модуль + +Хорошо, это не очень интересно, давайте добавим немного исполняемого кода в этот модуль. + +Весь код в модуле сгруппирован в функции, которые имеют следующую структуру псевдокода: + +``` +( func ) +``` + +- **signature** объявляет, что функция принимает (параметры) и возвращает (возвращаемые значения). +- **locals** похожи на переменные в JavaScript, но с определёнными явными типами. +- **body** - это просто линейный список низкоуровневых инструкций. + +Несмотря на то, что это S-выражение, оно очень напоминает функцию в других языках. + +## Сигнатуры и параметры + +Сигнатура - это последовательность объявлений типов параметров, за которыми следует список объявлений возвращаемых типов. Здесь стоит отметить, что: + +- Отсутствие возвращаемого типа `(result)` означает, что функция ничего не возвращает. +- В текущей версии WebAssembly может быть не более 1 возвращаемого типа, но [позже это значение будет изменено](https://webassembly.org/docs/future-features#multiple-return) на любое число. + +Каждый параметр имеет явно объявленный тип; у wasm в настоящее время есть четыре доступных типа: + +- `i32`: 32-разрядное целое число +- `i64`: 64-разрядное целое число +- `f32`: 32-разрядное число с плавающей точкой +- `f64`: 64-разрядное число с плавающей точкой + +Один параметр можно записать как `(param i32)`, а тип возвращаемого значения как `(result i32)`. Двоичная функция, которая принимает два 32-разрядных целых числа и возвращает 64-разрядное число с плавающей запятой, будет записана следующим образом: + +``` +(func (param i32) (param i32) (result f64) ... ) +``` + +После сигнатуры перечисляются локальные переменные с указанием типа, например `(local i32)`. Параметры в сигнатуре приравниваются к локальным переменным, которые инициализируются значением соответствующего аргумента, переданного вызывающей стороной. + +## Получение и установка локальных переменных и параметров функции + +И параметры и локальные переменные могут быть прочитаны и записаны в теле функции с помощью инструкций `get_local` и `set_local`. + +Инструкции `get_local` и `set_local` ссылаются по индексу на параметр, который должен быть получен или установлен: сначала считаются параметры, а затем локальные переменные в порядке их объявления. Объясним это на примере следующей функции: + +``` +(func (param i32) (param f32) (local f64) + get_local 0 + get_local 1 + get_local 2) +``` + +Инструкция `get_local 0` получит параметр i32, `get_local 1` получит параметр f32, а get_local 2 получит локальную переменную local f64. + +Использование числовых индексов для ссылки на элементы может сбивать с толку и раздражать, поэтому текстовый формат позволяет присваивать имена параметрам, локальным переменным и большинству других элементов. Для этого нужно просто добавить имя с префиксом символа доллара (`$`) непосредственно перед объявлением типа. + +Таким образом, можно переписать нашу сигнатуру так: + +``` +(func (param $p1 i32) (param $p2 f32) (local $loc f64) …) +``` + +После чего можно было бы написать инструкцию получения `get_local $p1` вместо `get_local 0` и т.д. (Обратите внимание, что, когда этот текст преобразуется в двоичный файл, двоичный файл будет содержать только индексы.) + +## Стековые машины + +Прежде чем мы сможем написать тело функции, мы должны поговорить ещё о **стековых машинах**. Хотя браузер компилирует wasm-код во что-то более эффективное, выполнение его определяется в терминах стековой машины, где основная идея заключается в том, что каждый тип инструкции получает или помещает определённое количество значений `i32` / `i64` / `f32` / `f64` в стек или из стека. + +Например, инструкция `get_local` предназначена для помещения значения локальной переменной, которое она считала, в стек. А инструкция `i32.add` получает два значения `i32` (неявно получает два предыдущих значения, помещённых в стек), вычисляет их сумму и помещает назад в стек результат вычисления `i32`. + +Когда вызывается функция, для неё выделяется пустой стек, который постепенно заполняется и очищается при выполнении инструкций в теле функции. Так, например, после выполнения следующей функции: + +``` +(func (param $p i32) + get_local $p + get_local $p + i32.add) +``` + +Стек будет содержать ровно одно значение `i32` - результат выполнения выражения ($p + $p), которое обработалось инструкцией `i32.add`. Возвращаемое значение функции - это последнее значение, оставленное в стеке. + +Правила валидации WebAssembly гарантируют, выполнение следующего: если вы объявляете тип возвращаемого значения функции как `(result f32)`, то стек должен содержать ровно одно значение типа `f32` в конце. Если тип результата отсутствует, стек должен быть пустым. + +## Тело функции + +Как упоминалось ранее, тело функции - это просто список инструкций, которые выполняются при вызове функции. Объединяя это с тем, что мы уже изучили, мы можем наконец определить модуль, содержащий простую функцию: + +``` +(module + (func (param $lhs i32) (param $rhs i32) (result i32) + get_local $lhs + get_local $rhs + i32.add)) +``` + +Эта функция получает два параметра, складывает их вместе и возвращает результат. + +Есть ещё много инструкций, которые можно поместить в тело функции. Сейчас мы начнём с простых, а далее вы увидите гораздо больше примеров по мере продвижения. Полный список доступных инструкций смотрите в справочнике по [семантике webassembly.org](http://webassembly.org/docs/semantics/). + +### Вызов функции + +Определение нашей функции само по себе почти ничего не делает - теперь нам нужно её вызвать. Как мы это сделаем? Как и в модуле ES2015, функции wasm должны быть явно экспортированы инструкцией `export` внутри модуля. + +Как и локальные переменные, функции идентифицируются индексом по умолчанию, но для удобства им можно присвоить имя. Давайте это сделаем: сначала добавим имя, которому предшествует знак доллара, сразу после ключевого слова `func`: + +``` +(func $add … ) +``` + +Теперь нам нужно добавить объявление экспорта: + +``` +(export "add" (func $add)) +``` + +Здесь `add` - это имя, по которому функция будет идентифицироваться в коде JavaScript, а `$add` определяет, какая функция внутри модуля WebAssembly будет экспортироваться. + +Итак, наш последний вариант модуля (на данный момент) выглядит так: + +``` +(module + (func $add (param $lhs i32) (param $rhs i32) (result i32) + get_local $lhs + get_local $rhs + i32.add) + (export "add" (func $add)) +) +``` + +Если вы хотите собственноручно скомпилировать пример, сохраните ранее написанный модуль в файле с именем `add.wat`, а затем преобразуйте его в двоичный файл с именем `add.wasm`, используя wabt (подробности смотрите в разделе [Перевод текстового формата WebAssembly в wasm](/ru/docs/WebAssembly/Text_format_to_wasm)). + +Затем мы загрузим наш двоичный файл, скомпилируем, создадим его экземпляр и выполним нашу функцию `add` в коде JavaScript (теперь нам доступна функция `add()` в свойстве [`exports`](/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Instance/exports) экземпляра модуля): + +```js +WebAssembly.instantiateStreaming(fetch('add.wasm')) +.then(obj => { + console.log(obj.instance.exports.add(1, 2)); // "3" +}); +``` + +> **Примечание:** вы можете найти этот пример на GitHub в файле [add.html](https://github.com/mdn/webassembly-examples/blob/master/understanding-text-format/add.html) (смотрите также это [вживую](https://mdn.github.io/webassembly-examples/understanding-text-format/add.html)). Также смотрите {{jsxref("WebAssembly.instantiateStreaming()")}} для получения более подробной информации о функции создания экземпляра модуля. + +## Изучение основ + +Теперь, когда мы рассмотрели простейшие примеры, давайте перейдём к рассмотрению некоторых более сложных возможностей. + +### Вызов функций из других функций в том же модуле + +Для вызова функции по индексу или имени используется инструкция `call`. Например, следующий модуль содержит две функции - первая просто возвращает значение `42`, вторая возвращает сумму результата вызова первой функции и единицы: + +``` +(module + (func $getAnswer (result i32) + i32.const 42) + (func (export "getAnswerPlus1") (result i32) + call $getAnswer + i32.const 1 + i32.add)) +``` + +> **Примечание:** Инструкция `i32.const` создаёт 32-разрядное целое число и помещает его в стек. Вы можете поменять `i32` на любой другой доступный тип данных и изменить значение на любое другое (здесь мы установили значение `42`). + +В этом примере обратите внимание на секцию объявления экспорта `(export “getAnswerPlus1”)`, которая находится сразу после объявления второй функции `func`. Это сокращённый способ объявления, совмещённый с именем функции, которую мы хотим экспортировать. + +Функционально это эквивалентно включению отдельного объявления экспорта функции без функции, в любом месте модуля, например: + +``` +(export "getAnswerPlus1" (func $functionName)) +``` + +Код JavaScript для вызова экспортируемой функции из нашего модуля выглядит так: + +```js +WebAssembly.instantiateStreaming(fetch('call.wasm')) +.then(obj => { + console.log(obj.instance.exports.getAnswerPlus1()); // "43" +}); +``` + +> **Примечание:** вы можете найти этот пример на GitHub как [call.html](https://github.com/mdn/webassembly-examples/blob/master/understanding-text-format/call.html) (смотрите также [вживую](https://mdn.github.io/webassembly-examples/understanding-text-format/call.html)). Ещё посмотрите [wasm-utils.js](https://github.com/mdn/webassembly-examples/blob/master/wasm-utils.js) для метода `fetchAndInstantiate()`. + +### Импорт функций из JavaScript + +Мы уже видели JavaScript, вызывающий экспортируемые функции модуля WebAssembly, но как насчёт WebAssembly модуля, вызывающего функции JavaScript? WebAssembly не имеет каких либо знаний о внешнем коде JavaScript, но у него есть способ импорта, который может принимать функции из JavaScript или wasm. Давайте посмотрим на пример: + +``` +(module + (import "console" "log" (func $log (param i32))) + (func (export "logIt") + i32.const 13 + call $log)) +``` + +В инструкции импорта в модуль WebAssembly определено двухуровневое пространство имён, в котором мы указали импортировать функцию `log` из модуля `console`. Вы также можете видеть, что экспортируемая функция `logIt` вызывает импортированную функцию, используя инструкцию `call`, о которой мы говорили ранее. + +Импортируемые функции аналогичны обычным функциям: они имеют сигнатуру, которую WebAssembly проверяет статически, им присваивается индекс (в место которого можно присвоить имя) и их можно вызвать обычным способом. + +Функции JavaScript не имеют понятия сигнатуры, поэтому любую функцию JavaScript можно передать независимо от объявленной сигнатуры импорта. Если модуль объявляет импорт, вызывающая сторона (например метод {{jsxref("WebAssembly.instantiate()")}}) должна передать объект импорта, который должен иметь соответствующее свойство. + +Для иллюстрации вышесказанного нам нужен объект (назовём его `importObject`), в котором конечное свойство `importObject.console.log` должно содержать функцию JavaScript. + +Код будет выглядеть следующим образом: + +```js +var importObject = { + console: { + log: function(arg) { + console.log(arg); + } + } +}; + +WebAssembly.instantiateStreaming(fetch('logger.wasm'), importObject) +.then(obj => { + obj.instance.exports.logIt(); +}); +``` + +> **Примечание:** Этот пример можно найти на GitHub в файле [logger.html](https://github.com/mdn/webassembly-examples/blob/master/understanding-text-format/logger.html) (смотрите также [вживую](https://mdn.github.io/webassembly-examples/understanding-text-format/logger.html)). + +### Определение глобальных переменных WebAssembly + +WebAssembly имеет возможность создавать экземпляры глобальных переменных. Они доступны как в коде JavaScript, так и через импорт / экспорт для одного и более экземпляров {{jsxref("WebAssembly.Module")}}. Это очень полезная возможность в плане динамического связывания нескольких модулей. + +В текстовом формате WebAssembly это выглядит примерно так (смотрите файл [global.html](https://mdn.github.io/webassembly-examples/js-api-examples/global.html) в нашем репозитории на GitHub; смотрите также [вживую](https://mdn.github.io/webassembly-examples/js-api-examples/global.html)): + +``` +(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)))) +) +``` + +Это похоже на то, что мы делали раньше, за исключением того, что мы указываем глобальную переменную с помощью ключевого слова `global`. Также мы указываем ключевое слово `mut` вместе с типом данных значения (если хотим, чтобы глобальная переменная была изменяемой). + +Чтобы создать эквивалентный код с помощью JavaScript, вы должны использовать конструктор {{jsxref("WebAssembly.Global()")}}: + +```js +const global = new WebAssembly.Global({value:'i32', mutable:true}, 0); +``` + +### Память WebAssembly + +Приведённый выше пример - довольно ужасная функция ведения журнала: она печатает только одно целое число! Что если мы хотим записать текстовую строку? Для работы со строками и другими более сложными типами данных WebAssembly предоставляет **линейную память**. Согласно технологии WebAssembly, линейная память - это просто большой массив байтов, который со временем может увеличиваться. WebAssembly код содержит ряд инструкций, наподобие `i32.load` и `i32.store` для чтения и записи значений из [линейной памяти](http://webassembly.org/docs/semantics/#linear-memory). + +Со стороны JavaScript, линейная память как будто находится внутри одного большого (расширяющегося) объекта {{domxref("ArrayBuffer")}}. + +Таким образом, строка - это просто последовательность байтов где-то внутри этой линейной памяти. Давайте предположим, что мы записали нужную строку байтов в память; как мы передадим эту строку в JavaScript? +Ключевым моментом является то, что JavaScript может создавать экземпляры(объекты) линейной памяти WebAssembly через конструктор {{jsxref("WebAssembly.Memory()")}} и получать доступ к существующему экземпляру памяти (в настоящее время вы можете иметь только один экземпляр памяти на экземпляр модуля), используя соответствующие методы экземпляра модуля. Экземпляр памяти имеет свойство [`buffer`](/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Memory/buffer), которое возвращает объект `ArrayBuffer`, предоставляя всю линейную память модуля. + +Объекты памяти могут расширятся с помощью метода [`Memory.grow()`](/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Memory/grow) из JavaScript. Когда происходит расширение, текущий объект `ArrayBuffer` не может изменить размер и он отсоединяется. Вместо него создаётся новый объект `ArrayBuffer`, указывающий на новую, увеличенную память. Пользуясь этими возможностями можно передать строку в JavaScript, её начальный индекс и её длину в линейной памяти. + +Хотя есть много разных способов кодировать длину строки в самой строке (например, как в строках в C); для простоты здесь мы просто передаём смещение и длину в качестве параметров: + +``` +(import "console" "log" (func $log (param i32) (param i32))) +``` + +На стороне JavaScript, мы можем использовать [TextDecoder API](/ru/docs/Web/API/TextDecoder), чтобы легко декодировать наши байты в строку JavaScript. (Мы указываем кодировку utf8, хотя поддерживаются и другие кодировки.) + +```js +function consoleLogString(offset, length) { + var bytes = new Uint8Array(memory.buffer, offset, length); + var string = new TextDecoder('utf8').decode(bytes); + console.log(string); +} +``` + +Последний недостающий фрагмент головоломки - это место, где функция `consoleLogString` получает доступ к памяти (`memory`) WebAssembly. WebAssembly даёт нам здесь много гибкости: либо мы можем создать объект [`Memory`](/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Memory) в коде JavaScript и импортировать его в модуль WebAssembly, или мы можем создать его в модуле WebAssembly и затем экспортировать в JavaScript. + +Для простоты, давайте создадим объект памяти в JavaScript и импортируем его в WebAssembly модуль. Напишем следующее объявление импорта `(import`): + +``` +(import "js" "mem" (memory 1)) +``` + +Число `1` указывает, что импортируемая память должна иметь по крайней мере 1 страницу памяти (WebAssembly определяет страницу как фиксированный блок памяти в 64КБ.) + +Давайте взглянем на наш последний вариант модуля, который выводит слово “Hi”. В обычной C программе, мы бы вызывали функцию для выделения памяти для строки. Но так как мы пишем собственную сборку и у нас есть собственная импортируемая память, то мы просто пишем содержание строки в линейную память, используя секцию `data`. Data-секция во время создания записывает строку байт, начиная с указанного отступа. И она действует также как и `.data` секция в “родных” форматах для исполнения. + +Наш последний вариант модуля выглядит так: + +``` +(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)) +``` + +> **Примечание:** Обратите внимание, что двойная точка с запятой (`;;`) позволяет оставлять комментарии в файлах WebAssembly. + +Теперь из JavaScript мы можем создать и передать объект памяти размером в 1 страницу. Результатом работы этого кода будет вывод “Hi” в консоль: + +```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(); +}); +``` + +> **Примечание:** вы можете найти полный исходный код на GitHub в файле [logger2.html](https://github.com/mdn/webassembly-examples/blob/master/understanding-text-format/logger2.html) (также смотрите это [вживую](https://mdn.github.io/webassembly-examples/understanding-text-format/logger2.html)). + +### Таблицы WebAssembly + +Чтобы завершить обзор текстового формата WebAssembly, давайте рассмотрим самую сложную и запутанную часть WebAssembly - **таблицы**. Таблицы - это массивы ссылок изменяемого размера, доступ к которым можно получить по индексу из кода WebAssembly. + +Чтобы понять, зачем нужны таблицы, нам нужно сначала обратить внимание, что инструкция `call`, которую мы видели ранее (см. [Вызов функций из других функций в том же модуле](#вызов_функций_из_других_функций_в_том_же_модуле)), принимает статический индекс функции и может вызывать только определённую функцию. Но что, если вызываемый элемент будет значением, установленным во время выполнения? + +- В JavaScript это делается постоянно: функции являются ссылочными значениями. +- В C/C++ это делается с помощью указателей на функции. +- В C++ это делается с помощью виртуальных функций. + +Для того чтобы сделать это в WebAssembly нужен был отдельный тип инструкции вызова. Поэтому мы создали инструкцию `call_indirect`, которая принимает операнд динамической функции. Проблема в том, что типы данных, которые мы должны использовать в операндах в WebAssembly, в настоящее время такие: `i32` / `i64` / `f32` / `f64`. + +Для WebAssembly можно было бы создать тип инструкции вызова `anyfunc` («любой», потому что эта инструкция смогла вызвать функции любой сигнатуры), но, к сожалению, операнд этого типа не может быть сохранён в линейной памяти по соображениям безопасности. Линейная память представляет содержимое хранимых значений в виде незащищённых байтов, и это позволяет содержимому wasm произвольно читать и изменять незащищённые адреса функций, что недопустимо для веб. + +Решением стало следующее. Хранить ссылки на функции в таблице и передавать вместо них индексы таблицы, которые являются просто значениями `i32`. Поэтому операндом инструкции `call_indirect` может выступить простое значение индекса `i32`. + +#### Определение таблицы в wasm + +Так как же разместить функции wasm в нашей таблице? Подобно тому, как секции `data` могут использоваться для инициализации областей линейной памяти байтами, секции `elem` могут использоваться для инициализации областей таблиц с функциями: + +``` +(module + (table 2 anyfunc) + (elem (i32.const 0) $f1 $f2) + (func $f1 (result i32) + i32.const 42) + (func $f2 (result i32) + i32.const 13) + ... +) +``` + +- В `(table 2 anyfunc)`, 2 - это начальный размер таблицы (это означает, что она будет хранить две ссылки), а объявление `anyfunc` означает, что типом элемента этих ссылок является «функция с любой сигнатурой». В текущей версии WebAssembly, это единственный допустимый тип атрибута, но в будущем будет добавлено больше. +- Секции функций `(func) `- это обычные объявления функций модуля wasm. Это те функции, на которые мы будем ссылаться в нашей таблице (каждая из них просто возвращает постоянное значение). Обратите внимание, что порядок объявления секций не имеет значения - вы можете объявить свои функции где угодно и по-прежнему ссылаться на них в секции `elem`. +- Секция `elem` - это список функций, на которые ссылается таблица, в том порядке, в котором они указаны. Здесь можно перечислить любое количество функций, включая их дубликаты. +- Значение `(i32.const 0)` внутри секции `elem` является смещением - его необходимо объявить в начале секции и указать, по какому индексу в таблице ссылок начинают заполняться ссылки на функции. Здесь мы указали 0, а размер таблицы указали как 2 (см. выше), поэтому мы можем заполнить две ссылки на индексы 0 и 1. Если бы мы захотели записать наши ссылки со смещением в 1, то нам нужно было бы написать `(i32.const 1)`, а размер таблицы должен был быть равен 3. + +> **Примечание:** Неинициализированным элементам присваивается значение вызова по умолчанию. + +В JavaScript эквивалентный код для создания такого экземпляра таблицы ссылок будет выглядеть примерно так: + +```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); +}; +``` + +#### Использование таблицы + +Мы определили таблицу, которую нам нужно как-то использовать. Для этого добавим следующую секцию кода: + +``` +(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)) +``` + +- Секция `(type $return_i32 (func (result i32)))` определяет тип с заданным именем `$return_i32`. Этот тип используется при выполнении проверки сигнатуры функции в таблице функций. Здесь мы указываем, что ссылки должны быть функциями, возвращаемое значение которых должно быть с типом `i32`. +- Далее мы определяем экспортируемую функцию с именем `callByIndex`. Для единственного параметра функции задан тип `i32`, которому присвоено имя `$i`. +- Внутри функции мы помещаем одно значение в стек - любое значение, переданное в качестве параметра `$i` экспортируемой функции. +- Наконец, мы используем инструкцию `call_indirect` для вызова функции из таблицы - она ​​неявно получает значение `$i` из стека. Конечным результатом будет вызов функции из таблицы с индексом, указанным в `$i`. + +Вы также можете объявить параметр `call_indirect` явно во время вызова инструкции, а не до него (неявным получением из стека), например так: + +``` +(call_indirect (type $return_i32) (get_local $i)) +``` + +На языке высокого уровня, таком как JavaScript эти же действия вы можете представить в виде манипуляций с массивом (или, скорее, с объектом), содержащим функции. Псевдокод будет выглядеть примерно так: `tbl[i]()`. + +Итак, вернёмся к проверке типов. Так как в коде WebAssembly проверяются типы, а атрибут `anyfunc` означает “сигнатура любой функции", мы должны предоставить предполагаемую сигнатуру в месте вызова, поэтому мы включаем тип с именем `$return_i32`, чтобы сообщить программе, что ожидается функция, возвращающая значение с типом `i32`. Если вызываемая функция не имеет соответствующей сигнатуры (скажем, вместо неё возвращается `f32`), выбросится исключение {{jsxref("WebAssembly.RuntimeError")}}. + +Так как инструкция `call_indirect` связывается с таблицей, с которой мы вызываем функцию? Ответ заключается в том, что на данный момент для каждого экземпляра модуля разрешена только одна таблица. Поэтому инструкция `call_indirect` выполняет неявный вызов именно из этой таблицы. В будущем, когда будет разрешено использование нескольких таблиц, нам нужно будет указать идентификатор таблицы, например так: + +``` +call_indirect $my_spicy_table (type $i32_to_void) +``` + +Весь модуль в целом выглядит следующим образом и может быть найден в нашем примере файла [wasm-table.wat](https://github.com/mdn/webassembly-examples/blob/master/understanding-text-format/wasm-table.wat): + +``` +(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)) +) +``` + +Загрузка модуля и использование экспортируемой функции в коде JavaScript будет выглядеть так: + +```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 +}); +``` + +> **Примечание:** Этот пример можно найти на GitHub в файле [wasm-table.html](https://github.com/mdn/webassembly-examples/blob/master/understanding-text-format/wasm-table.html) (смотрите это также [вживую](https://mdn.github.io/webassembly-examples/understanding-text-format/wasm-table.html)) + +> **Примечание:** Как и в случае с памятью, таблицы также можно создавать из кода JavaScript (см. [`WebAssembly.Table()`](/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Table)). + +### Изменяющиеся таблицы и динамическое связывание + +Поскольку JavaScript имеет полный доступ к ссылкам на функции, объект таблицы может быть изменён из кода JavaScript с помощью методов [`grow()`](/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Table/grow), [`get()`](/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Table/get) и [`set()`](/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Table/set). Когда WebAssembly получит [ссылочные типы](http://webassembly.org/docs/gc/), код WebAssembly сможет изменять таблицы самостоятельно с помощью инструкций `get_elem` / `set_elem`. + +Поскольку таблицы являются изменяемыми, их можно использовать для реализации сложных схем [динамического связывания](http://webassembly.org/docs/dynamic-linking) во время загрузки и во время выполнения. Когда программа динамически связана, несколько экземпляров могут совместно использовать линейную память и таблицу ссылок. Это похоже на поведение в обычном приложении где несколько скомпилированных `.dll` совместно используют адресное пространство одного процесса. + +Чтобы увидеть это в действии, мы создадим один объект импорта, содержащий объект памяти и объект таблицы. Далее мы передадим этот объект импорта при создании нескольких модулей с помощью метода [`instantiate()`](/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/instantiate). + +Наши примеры файлов `.wat` выглядят так: + +`shared0.wat`: + +``` +(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) +) +``` + +`shared1.wat`: + +``` +(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)) +) +``` + +Они работают следующим образом: + +1. Функция `shared0func` определена в `shared0.wat` и сохраняется в нашей импортированной таблице. +2. Эта функция создаёт константу, содержащую значение `0`, затем инструкция `i32.load` получает значение из импортированной памяти по предоставленному константой индексу. Предоставленный индекс равен `0`. Как и другие подобные инструкции, `i32.load` неявно получает предоставленное значение из стека. Итак, `shared0func` загружает и возвращает значение, хранящееся в индексе памяти `0`. +3. В `shared1.wat` мы экспортируем функцию с именем `doIt` - эта функция размещает в стеке две константы, содержащие значения `0` и `42`. Затем она вызывает инструкцию `i32.store` для сохранения предоставленного значения по предоставленному индексу в импортированной памяти. Опять же, инструкция неявно получает эти значения из стека. Поэтому в результате `doIt` сохраняет значение `42` в индексе памяти `0`. +4. В последней части функции создаётся константа со значением `0`, затем вызывается функция с этим индексом (`0`) из таблицы. Это будет функция `shared0func` модуля `shared0.wat`, которая ранее была размещена там с помощью секции `elem`. +5. При вызове shared0func загружает число `42`, которые мы сохранили в памяти, с помощью ранее указанной инструкции `i32.store` в модуле `shared1.wat`. + +> **Примечание:** Вышеприведённые выражения неявно извлекают значения из стека, но вместо этого вы можете объявить их явно в вызовах инструкций, например: +> +> ``` +> (i32.store (i32.const 0) (i32.const 42)) +> (call_indirect (type $void_to_i32) (i32.const 0)) +> ``` + +После преобразования текста в модули мы используем файлы `shared0.wasm` и `shared1.wasm` в JavaScript с помощью следующего кода: + +```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 +}); +``` + +Каждый из компилируемых модулей может импортировать общие объекты памяти и таблицы. Таким образом, они могут совместно использовать одну и ту же линейную память и таблицу ссылок. + +> **Примечание:** Этот пример можно найти на GitHub в файле [shared-address-space.html](https://github.com/mdn/webassembly-examples/blob/master/understanding-text-format/shared-address-space.html) (смотрите это также [вживую](https://mdn.github.io/webassembly-examples/understanding-text-format/shared-address-space.html)). + +## Резюме + +На этом мы завершаем обзор основных компонентов текстового формата WebAssembly и того, как они отображены в WebAssembly JS API. + +## Смотрите также + +- [Семантика WebAssembly](http://webassembly.org/docs/semantics) для информации по всем возможным инструкциям. +- [Грамматика текстового формата](https://github.com/WebAssembly/spec/blob/master/interpreter/README.md#s-expression-syntax), который реализован в интерпретаторе спецификации. diff --git a/files/ru/webassembly/using_the_javascript_api/index.html b/files/ru/webassembly/using_the_javascript_api/index.html deleted file mode 100644 index 73ca81e34876fc..00000000000000 --- a/files/ru/webassembly/using_the_javascript_api/index.html +++ /dev/null @@ -1,302 +0,0 @@ ---- -title: Использование WebAssembly JavaScript API -slug: WebAssembly/Using_the_JavaScript_API -tags: - - API - - JavaScript - - WebAssembly -translation_of: WebAssembly/Using_the_JavaScript_API ---- -
{{WebAssemblySidebar}}
- -

Если вы уже компилировали модуль из другого языка, используя такие инструменты как Emscripten, или загружали и запускали код, то следующим шагом будет углублённое изучение возможностей WebAssembly JavaScript API. Эта статья даст необходимые знания по этому вопросу.

- -
-

Примечание: Если вы незнакомы с фундаментальными понятиями, упомянутыми в этой статье, и вам нужны дополнительные объяснения, то вам нужно сначала прочитать про Основы WebAssembly.

-
- -

Несколько простых примеров

- -

Давайте запустим несколько примеров, которые объяснят как использовать WebAssembly JavaScript API, и как использовать его для загрузки wasm-модуля в web-странице.

- -
-

Примечание: вы можете найти примеры кода в нашем репозитории webassembly-examples на GitHub.

-
- -

Подготовка примера

- -
    -
  1. Для начала нам нужен wasm-модуль! Возьмите наш файл simple.wasm и сохраните копию в новой директории на своём локальном компьютере.
  2. -
  3. Далее, давайте создадим в той же директории что и wasm-модуль простой HTML-файл и назовём его index.html (можно использовать HTML шаблон если вы этого ещё не сделали).
  4. -
  5. Теперь, для того чтобы понять что происходит в коде модуля, давайте взглянем на его текстовое представление (которое мы также встречали в Перевод из текстового формата WebAssembly в wasm): -
    (module
    -  (func $i (import "imports" "imported_func") (param i32))
    -  (func (export "exported_func")
    -    i32.const 42
    -    call $i))
    -
  6. -
  7. Во второй строчке вы видите что import имеет двухуровневое пространство имён - внутренняя функция $i импортирована из imports.imported_func. Нам нужно создать это двухуровневое пространство имён в JavaScript-объекте, который будет импортирован в wasm-модуль. Создайте <script></script> элемент в своём HTML-файле, и добавьте следующий код: -
    var importObject = {
    -  imports: { imported_func: arg => console.log(arg) }
    -};
    -
  8. -
- -

Загрузка wasm-модуля в потоке

- -

Новшество в Firefox 58 - это возможность компилировать и создавать экземпляры (объекты) модулей WebAssembly непосредственно из исходников. Это достигается использованием методов {{jsxref("WebAssembly.compileStreaming()")}} и {{jsxref("WebAssembly.instantiateStreaming()")}}. Эти методы занимают меньше места чем их непотоковые аналоги, потому что они могут преобразовывать байт-код прямо в модуль или экземпляр модуля, исключая необходимость отдельного размещения ответа ({{domxref("Response")}}) в объекте {{domxref("ArrayBuffer")}} после загрузки файла.

- -

Следующий пример (см. наш демонстрационный файл instantiate-streaming.html на GitHub и его работу вживую) показывает как использовать instantiateStreaming() чтобы загрузить wasm-модуль, импортировать JavaScript функцию в него, компилировать, создать его экземпляр и получить доступ к его экспортируемой функции. Все это в одном шаге.

- -

Добавьте этот скрипт ниже первого блока кода:

- -
WebAssembly.instantiateStreaming(fetch('simple.wasm'), importObject)
-.then(obj => obj.instance.exports.exported_func());
- -

В конце этого действия мы вызываем нашу экспортированную из WebAssembly-функцию exported_func, которая в свою очередь вызывает нашу импортированную JavaScript-функцию imported_func, которая выводит в консоль значение (42), что хранится внутри экземпляра модуля WebAssembly. Если вы сейчас сохраните пример кода и загрузите его в браузер, который поддерживает WebAssembly, вы увидите это в действии!

- -
-

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

-
- -

Загрузка wasm-модуля без потока

- -

Если вы не можете или не хотите использовать методы описанные выше, то вы можете использовать вместо этого непотоковые методы {{jsxref("WebAssembly.compile")}} / {{jsxref("WebAssembly.instantiate")}}.

- -

Эти методы не получают непосредственно доступ к байт-коду, так что требуется дополнительный шаг помещения ответа загрузки файла в объект {{domxref("ArrayBuffer")}} перед компилированием и созданием экземпляра wasm-модуля.

- -

Эквивалентный код будет выглядеть так:

- -
fetch('simple.wasm').then(response =>
-  response.arrayBuffer()
-).then(bytes =>
-  WebAssembly.instantiate(bytes, importObject)
-).then(results => {
-  results.instance.exports.exported_func();
-});
- -

Просмотр wasm в инструментах разработчика

- -

В Firefox 54+, в отладочной панели инструментов разработчика имеется возможность отображения текстового представления любого wasm-кода, включённого в веб-страницу. Для того чтобы просмотреть его, вы можете перейти на отладочную панель и нажать на пункт "wasm://".

- -

- -

В ближайших версиях в Firefox, в дополнении к просмотру wasm-кода как текста, разработчики будут иметь возможность отлаживать wasm используя текстовый формат (устанавливать точки останова, изучать стек вызовов, построчно переходить, и.т.д). См. WebAssembly debugging with Firefox DevTools в видео-анонсе.

- -

Память

- -

В низкоуровневой модели памяти WebAssembly, память представлена как диапазон смежных байт, называемых линейной памятью (Linear Memory), которая внутри модуля читается и записывается инструкциями загрузки и размещения значений. В этой модели памяти, любая инструкция загрузки или размещения может получить доступ к любому байту всей линейной памяти. Это необходимо для полноценного представления таких концепций языков C/C++ как указатели.

- -

В отличии от C/C++ программы, где доступный диапазон памяти ограничен процессом, память доступная отдельному экземпляру WebAssembly ограничена до одного специфического (потенциально очень маленького) диапазона, который содержится в объекте памяти WebAssembly. Это позволяет единственному web-приложению использовать множество независимых библиотек (использующих WebAssembly) которые могут иметь отдельные и полностью изолированные друг от друга диапазоны памяти.

- -

В JavaScript-коде, объект памяти WebAssembly можно считать объектом ArrayBuffer c изменяемыми размерами. Одно веб-приложение может создавать много таких независимых объектов памяти. Вы можете создать новый объект, используя конструктор WebAssembly.Memory(), который принимает аргументы начального и максимального размера буфера (опционально).

- -

Давайте исследуем эту возможность рассмотрев небольшой пример.

- -

Создайте ещё одну простую HTML страницу (скопируйте HTML шаблон) и назовите её memory.html. Добавьте <script></script> элемент на страницу.

- -
    -
  1. -

    Добавьте следующую строку в начало нашего скрипта, для создания экземпляра объекта памяти:

    - -
    var memory = new WebAssembly.Memory({initial:10, maximum:100});
    - -

    Единицы измерения начальной (initial) и максимальной (maximum) памяти имеют фиксированный размер в 64KB. Это означает, что в нашем случае объект памяти при создании имеет 640KB, а его максимальный возможный размер будет 6.4MB.

    - -

    Объект памяти WebAssembly предоставляет свой хранимый диапазон байт через getter/setter свойства buffer, которое возвращает объект ArrayBuffer. Для примера, чтобы записать число 42 в первое слово линейной памяти, вы можете сделать это:

    - -
    new Uint32Array(memory.buffer)[0] = 42;
    - -

    вы можете возвратить значение используя этот код:

    - -
    new Uint32Array(memory.buffer)[0]
    -
  2. -
  3. -

    Попробуйте сделать это в вашем примере - сохраните то, что вы сделали, загрузите его в браузере, после чего попробуйте ввести вышеупомянутые строчки в JavaScript-консоль.

    -
  4. -
- -

Расширение памяти

- -

Объект памяти может быть расширен с помощью вызова метода {{jsxref("Memory.prototype.grow()")}}, где аргументом будет количество единиц (размером в 64KB) памяти WebAssembly:

- -
memory.grow(1);
- -

При превышении максимального значения, указанного при создании объекта памяти, будет выброшено исключение {{jsxref("WebAssembly.RangeError")}}. Движок использует предоставленные верхние границы для резервирования памяти заранее, что делает расширение памяти более эффективным.

- -
-

Примечание: Так как размер объекта {{domxref("ArrayBuffer")}} неизменен, после успешного вызова метода {{jsxref("Memory.prototype.grow()")}} свойство buffer объекта памяти будет возвращать уже новый объект {{domxref("ArrayBuffer")}} (с новым размером в свойстве byteLength) и любые предыдущие объекты ArrayBuffer станут в некотором роде “отсоединёнными”, или отключёнными от низкоуровневой памяти, к которой они ранее относились.

-
- -

Подобно функциям, диапазоны линейной памяти могут быть импортированы или определены внутри модуля. Также, модуль имеет возможность экспортировать свою память. Это означает, что JavaScript-код может получить доступ к объекту памяти WebAssembly либо c помощью создания нового объекта через конструктор WebAssembly.Memory и передачи его в импортируемый объект, либо с помощью получения объекта памяти через экспортируемый объект (через Instance.prototype.exports).

- -

Более сложный пример

- -

Давайте сделаем вышеупомянутые утверждения понятнее, рассмотрев более сложный пример работы с памятью, где WebAssembly-модуль импортирует объект памяти, который мы определили ранее, после чего JavaScript-код наполняет его с помощью массива целых чисел, а экспортируемая wasm-функция суммирует их.

- -
    -
  1. -

    Скопируйте файл memory.wasm в локальную директорию в которой вы работаете.

    - -
    -

    Примечание: вы можете увидеть текстовое представление модуля в файле memory.wat.

    -
    -
  2. -
  3. -

    Откройте ваш файл memory.html и добавьте следующий код снизу вашего основного скрипта для загрузки, компилирования и создания экземпляра wasm-модуля:

    - -
    WebAssembly.instantiateStreaming(fetch('memory.wasm'), { js: { mem: memory } })
    -.then(results => {
    -  // add code here
    -});
    -
  4. -
  5. -

    Так как модуль экспортирует свою память, которая была передана экземпляру этого модуля при его создании, мы можем наполнить ранее импортированный массив прямо в линейной памяти экземпляра модуля (mem), и вызвать экспортированную функцию accumulate() для расчёта суммы значений. Добавьте следующий код, в обозначенном месте:

    - -
    var i32 = new Uint32Array(memory.buffer);
    -
    -for (var i = 0; i < 10; i++) {
    -  i32[i] = i;
    -}
    -
    -var sum = results.instance.exports.accumulate(0, 10);
    -console.log(sum);
    -
  6. -
- -

Обратите внимание на то, что мы создаём представление данных {{domxref("Uint32Array")}} с помощью свойства buffer объекта памяти (Memory.prototype.buffer), а не самого объекта памяти.

- -

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

- -
    -
  • Он позволяет JavaScript-коду получать и создать начальное содержание памяти перед или одновременно с компиляцией модуля.
  • -
  • Он позволяет импортировать один объект памяти во множество экземпляров модулей, что является ключевым элементом для реализации динамического связывания в WebAssembly.
  • -
- -
-

Примечание: вы можете найти полную демонстрацию в файле memory.html (см. её также вживую) — эта версия использует функцию fetchAndInstantiate().

-
- -

Таблицы

- -

Таблица WebAssembly - это расширяемый типизированный массив ссылок, доступ к которому может быть получен из JavaScript и WebAssembly кода. Так как линейная память предоставляет расширяемый массив незащищённых байт, слишком небезопасно размещать там ссылки, так как для движка ссылка является доверенным значением, чьи байты не должны быть прочитаны или записаны кодом напрямую по причинам безопасности, переносимости и стабильности.

- -

У таблиц есть тип элемента, который ограничивает тип возможной ссылки, который может быть размещён в таблице. В текущей версии WebAssembly, только один тип ссылки используется в WebAssembly коде - функции - и поэтому существует только один возможный тип элемента. В следующих версиях их количество будет увеличено.

- -

Ссылки на функции необходимы для компиляции в таких языках как C/C++, которые имеют указатели на функции. В родной реализации C/C++, указатель на функцию представлен прямым адресом на код функции в виртуальном адресном пространстве процесса, и потому для ранее упомянутой безопасности, они не могут быть размещены прямо в линейной памяти. Вместо этого ссылки на функции размещаются в таблице, а её индексы, которые являются целыми числами могут быть размещены в линейной памяти и переданы куда угодно.

- -

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

- -

Таблицы могут изменятся с помощью метода Table.prototype.set(), который обновляет одно из значений в таблице, и метода Table.prototype.grow(), который увеличивает количество значений, которое может быть размещено в таблице. Это позволяет этому "непрямо-вызываемому набору функций" изменяться со временем, что необходимо для техник динамического связывания. Изменения немедленно становятся доступными с помощью метода Table.prototype.get() в JavaScript-коде и wasm-модулях.

- -

Пример таблицы

- -

Давайте взглянем на простой пример таблицы - модуль WebAssembly, который создаёт и экспортирует таблицу с двумя элементами: элемент под индексом 0 возвращает 13, а элемент под индексом 1 возвращает 42.

- -
    -
  1. -

    Сделайте локальную копию файла table.wasm в новой директории.

    - -
    -

    Примечание: вы можете посмотреть текстовое представление модуля в файле table.wat.

    -
    -
  2. -
  3. -

    Создайте новую копию нашего HTML шаблона в той же директории и назовите его table.html.

    -
  4. -
  5. -

    Как и раньше загрузите, компилируйте, и создайте экземпляр вашего wasm-модуля, добавив следующий код в {{htmlelement("script")}} элемент в тело документа:

    - -
    WebAssembly.instantiateStreaming(fetch('table.wasm'))
    -.then(function(results) {
    -  // add code here
    -});
    -
  6. -
  7. -

    Теперь давайте получим доступ к данным в таблицах - добавим следующие строки в ваш код, в обозначенном месте:

    - -
    var tbl = results.instance.exports.tbl;
    -console.log(tbl.get(0)());  // 13
    -console.log(tbl.get(1)());  // 42
    -
  8. -
- -

Этот код получает доступ к каждой ссылке на функцию, которая размещена в таблице, после чего вызывает её и выводит хранимое значение в консоль. Обратите внимание, что каждая ссылка на функцию получена с помощью вызова метода Table.prototype.get(), после чего мы добавили пару круглых скобок для вызова самой функции.

- -
-

Примечание: вы можете найти нашу полную демонстрацию в файле table.html (см. её также вживую) — эта версия использует функцию fetchAndInstantiate().

-
- -

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

- -

WebAssembly имеет возможность создавать экземпляры глобальных переменных, доступных как в JavaScript так и в экземплярах модулей WebAssembly ({{jsxref("WebAssembly.Module")}}) посредством импорта или экспорта. Это очень полезная возможность, которая позволяет динамически связывать несколько модулей. Для создания глобальной переменной WebAssembly внутри вашего JavaScript-кода, используйте конструктор {{jsxref("WebAssembly.Global()")}}, который выглядит так:

- -
const global = new WebAssembly.Global({value:'i32', mutable:true}, 0);
- -

Вы можете видеть, что он принимает 2 параметра:

- -
    -
  • Объект, который содержит 2 свойства, описывающих глобальную переменную: -
      -
    • value: это тип данных, который может быть одним из типов данных, позволенных внутри WebAssembly модуля — i32, i64, f32, или f64.
    • -
    • mutable: булево значение, определяющее что переменная может изменяться.
    • -
    -
  • -
  • Значение, которое содержит текущее значение переменной. Это может быть любое значение для типа к которому оно относится.
  • -
- -

Что мы будем с этим делать? В следующем примере мы определим глобальную, изменяемую переменную с типом i32 и значением 0.

- -

Значение глобальной переменной будет изменено на число 42 используя свойство Global.value, а после на 43 используя экспортированную функцию incGlobal() из модуля global.wasm (это добавит 1 к установленному значению и возвратит новое).

- -
const output = document.getElementById('output');
-
-function assertEq(msg, got, expected) {
-    output.innerHTML += `Testing ${msg}: `;
-    if (got !== expected)
-        output.innerHTML += `FAIL!<br>Got: ${got}<br>Expected: ${expected}<br>`;
-    else
-        output.innerHTML += `SUCCESS! Got: ${got}<br>`;
-}
-
-assertEq("WebAssembly.Global exists", typeof WebAssembly.Global, "function");
-
-const global = new WebAssembly.Global({value:'i32', mutable:true}, 0);
-
-WebAssembly.instantiateStreaming(fetch('global.wasm'), { js: { global } })
-.then(({instance}) => {
-    assertEq("getting initial value from wasm", instance.exports.getGlobal(), 0);
-    global.value = 42;
-    assertEq("getting JS-updated value from wasm", instance.exports.getGlobal(), 42);
-    instance.exports.incGlobal();
-    assertEq("getting wasm-updated value from JS", global.value, 43);
-});
- -
-

Примечание: вы можете увидеть этот пример вживую на GitHub; смотрите также исходники.

-
- -

Множественность

- -

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

- -
    -
  • Один модуль может иметь N экземпляров, точно так же как одно определение функции может произвести N замыканий.
  • -
  • Один экземпляр модуля может использовать от 0 до 1 объекта памяти, который предоставляет “адресное пространство” экземпляра модуля. Будущие версии WebAssembly позволят иметь 0–N экземпляров объектов на один экземпляр модуля (см. Несколько таблиц и объектов памяти).
  • -
  • Один экземпляр модуля может использовать от 0 до 1 объекта таблицы - это “адресное пространство для функций” экземпляра модуля используется для реализации С/С++ указателей на функции. Будущие версии WebAssembly позволят иметь 0–N экземпляров таблиц на один экземпляр модуля.
  • -
  • Один объект памяти или объект таблицы может быть использован в 0-N экземплярах модулей - эти все экземпляры будут разделять одно и то же адресное пространство, позволяя выполнять динамическое связывание.
  • -
- -

Чтобы ознакомится с множественностью в действии, смотрите нашу объясняющую статью Изменяющиеся таблицы и динамическое связывание.

- -

Резюме

- -

В этой статье-путеводителе по основам WebAssembly JavaScript API вы включали модули WebAssembly в среду JavaScript, использовали их функции, объекты памяти, таблицы и глобальные переменные. Мы также затронули концепцию множественности.

- -

Смотрите также

- - diff --git a/files/ru/webassembly/using_the_javascript_api/index.md b/files/ru/webassembly/using_the_javascript_api/index.md new file mode 100644 index 00000000000000..23a7f1e7dd05b0 --- /dev/null +++ b/files/ru/webassembly/using_the_javascript_api/index.md @@ -0,0 +1,283 @@ +--- +title: Использование WebAssembly JavaScript API +slug: WebAssembly/Using_the_JavaScript_API +tags: + - API + - JavaScript + - WebAssembly +translation_of: WebAssembly/Using_the_JavaScript_API +--- +{{WebAssemblySidebar}} + +Если вы уже [компилировали модуль из другого языка, используя такие инструменты как Emscripten](/ru/docs/WebAssembly/C_to_wasm), или загружали и запускали код, то следующим шагом будет углублённое изучение возможностей WebAssembly JavaScript API. Эта статья даст необходимые знания по этому вопросу. + +> **Примечание:** Если вы незнакомы с фундаментальными понятиями, упомянутыми в этой статье, и вам нужны дополнительные объяснения, то вам нужно сначала прочитать про [Основы WebAssembly](/ru/docs/WebAssembly/Concepts). + +## Несколько простых примеров + +Давайте запустим несколько примеров, которые объяснят как использовать WebAssembly JavaScript API, и как использовать его для загрузки wasm-модуля в web-странице. + +> **Примечание:** вы можете найти примеры кода в нашем репозитории [webassembly-examples](https://github.com/mdn/webassembly-examples) на GitHub. + +### Подготовка примера + +1. Для начала нам нужен wasm-модуль! Возьмите наш файл [simple.wasm](https://github.com/mdn/webassembly-examples/raw/master/js-api-examples/simple.wasm) и сохраните копию в новой директории на своём локальном компьютере. +2. Далее, давайте создадим в той же директории что и wasm-модуль простой HTML-файл и назовём его `index.html` (можно использовать [HTML шаблон](https://github.com/mdn/webassembly-examples/blob/master/template/template.html) если вы этого ещё не сделали). +3. Теперь, для того чтобы понять что происходит в коде модуля, давайте взглянем на его текстовое представление (которое мы также встречали в [Перевод из текстового формата WebAssembly в wasm](/ru/docs/WebAssembly/Text_format_to_wasm#A_first_look_at_the_text_format)): + + ``` + (module + (func $i (import "imports" "imported_func") (param i32)) + (func (export "exported_func") + i32.const 42 + call $i)) + ``` + +4. Во второй строчке вы видите что import имеет двухуровневое пространство имён - внутренняя функция `$i` импортирована из `imports.imported_func`. Нам нужно создать это двухуровневое пространство имён в JavaScript-объекте, который будет импортирован в wasm-модуль. Создайте `` элемент в своём HTML-файле, и добавьте следующий код: + + ```js + var importObject = { + imports: { imported_func: arg => console.log(arg) } + }; + ``` + +### Загрузка wasm-модуля в потоке + +Новшество в Firefox 58 - это возможность компилировать и создавать экземпляры (объекты) модулей WebAssembly непосредственно из исходников. Это достигается использованием методов {{jsxref("WebAssembly.compileStreaming()")}} и {{jsxref("WebAssembly.instantiateStreaming()")}}. Эти методы занимают меньше места чем их непотоковые аналоги, потому что они могут преобразовывать байт-код прямо в модуль или экземпляр модуля, исключая необходимость отдельного размещения ответа ({{domxref("Response")}}) в объекте {{domxref("ArrayBuffer")}} после загрузки файла. + +Следующий пример (см. наш демонстрационный файл [instantiate-streaming.html](https://github.com/mdn/webassembly-examples/blob/master/js-api-examples/instantiate-streaming.html) на GitHub и его работу [вживую](https://mdn.github.io/webassembly-examples/js-api-examples/instantiate-streaming.html)) показывает как использовать `instantiateStreaming()` чтобы загрузить wasm-модуль, импортировать JavaScript функцию в него, компилировать, создать его экземпляр и получить доступ к его экспортируемой функции. Все это в одном шаге. + +Добавьте этот скрипт ниже первого блока кода: + +```js +WebAssembly.instantiateStreaming(fetch('simple.wasm'), importObject) +.then(obj => obj.instance.exports.exported_func()); +``` + +В конце этого действия мы вызываем нашу экспортированную из WebAssembly-функцию `exported_func`, которая в свою очередь вызывает нашу импортированную JavaScript-функцию `imported_func`, которая выводит в консоль значение (42), что хранится внутри экземпляра модуля WebAssembly. Если вы сейчас сохраните пример кода и загрузите его в браузер, который поддерживает WebAssembly, вы увидите это в действии! + +> **Примечание:** Этот замысловатый и запутанный пример почти ничего не делает, но он служит иллюстрацией того, что можно одновременно использовать WebAssembly-код и JavaScript-код в ваших приложениях. Как мы утверждали ранее, технология WebAssembly не призвана заменить JavaScript. Вместо этого две технологии могут работать вместе, усиливая преимущества каждой стороны. + +### Загрузка wasm-модуля без потока + +Если вы не можете или не хотите использовать методы описанные выше, то вы можете использовать вместо этого непотоковые методы {{jsxref("WebAssembly.compile")}} / {{jsxref("WebAssembly.instantiate")}}. + +Эти методы не получают непосредственно доступ к байт-коду, так что требуется дополнительный шаг помещения ответа загрузки файла в объект {{domxref("ArrayBuffer")}} перед компилированием и созданием экземпляра wasm-модуля. + +Эквивалентный код будет выглядеть так: + +```js +fetch('simple.wasm').then(response => + response.arrayBuffer() +).then(bytes => + WebAssembly.instantiate(bytes, importObject) +).then(results => { + results.instance.exports.exported_func(); +}); +``` + +### Просмотр wasm в инструментах разработчика + +В Firefox 54+, в отладочной панели инструментов разработчика имеется возможность отображения текстового представления любого wasm-кода, включённого в веб-страницу. Для того чтобы просмотреть его, вы можете перейти на отладочную панель и нажать на пункт "wasm://". + +![](https://mdn.mozillademos.org/files/15823/wasm-debug.png) + +В ближайших версиях в Firefox, в дополнении к просмотру wasm-кода как текста, разработчики будут иметь возможность отлаживать wasm используя текстовый формат (устанавливать точки останова, изучать стек вызовов, построчно переходить, и.т.д). См.[ WebAssembly debugging with Firefox DevTools](https://www.youtube.com/watch?v=R1WtBkMeGds) в видео-анонсе. + +## Память + +В низкоуровневой модели памяти WebAssembly, память представлена как диапазон смежных байт, называемых линейной памятью ([Linear Memory](http://webassembly.org/docs/semantics/#linear-memory)), которая внутри модуля читается и записывается [инструкциями загрузки и размещения](http://webassembly.org/docs/semantics/#linear-memory-accesses) значений. В этой модели памяти, любая инструкция загрузки или размещения может получить доступ к любому байту всей линейной памяти. Это необходимо для полноценного представления таких концепций языков C/C++ как указатели. + +В отличии от C/C++ программы, где доступный диапазон памяти ограничен процессом, память доступная отдельному экземпляру WebAssembly ограничена до одного специфического (потенциально очень маленького) диапазона, который содержится в объекте памяти WebAssembly. Это позволяет единственному web-приложению использовать множество независимых библиотек (использующих WebAssembly) которые могут иметь отдельные и полностью изолированные друг от друга диапазоны памяти. + +В JavaScript-коде, объект памяти WebAssembly можно считать объектом ArrayBuffer c изменяемыми размерами. Одно веб-приложение может создавать много таких независимых объектов памяти. Вы можете создать новый объект, используя конструктор WebAssembly.Memory(), который принимает аргументы начального и максимального размера буфера (опционально). + +Давайте исследуем эту возможность рассмотрев небольшой пример. + +Создайте ещё одну простую HTML страницу (скопируйте [HTML шаблон](https://github.com/mdn/webassembly-examples/blob/master/template/template.html)) и назовите её `memory.html`. Добавьте `` элемент на страницу. + +1. Добавьте следующую строку в начало нашего скрипта, для создания экземпляра объекта памяти: + + ```js + var memory = new WebAssembly.Memory({initial:10, maximum:100}); + ``` + + Единицы измерения начальной (`initial`) и максимальной (`maximum`) памяти имеют фиксированный размер в 64KB. Это означает, что в нашем случае объект памяти при создании имеет 640KB, а его максимальный возможный размер будет 6.4MB. + + Объект памяти WebAssembly предоставляет свой хранимый диапазон байт через getter/setter свойства buffer, которое возвращает объект ArrayBuffer. Для примера, чтобы записать число 42 в первое слово линейной памяти, вы можете сделать это: + + ```js + new Uint32Array(memory.buffer)[0] = 42; + ``` + + вы можете возвратить значение используя этот код: + + ```js + new Uint32Array(memory.buffer)[0] + ``` + +2. Попробуйте сделать это в вашем примере - сохраните то, что вы сделали, загрузите его в браузере, после чего попробуйте ввести вышеупомянутые строчки в JavaScript-консоль. + +### Расширение памяти + +Объект памяти может быть расширен с помощью вызова метода {{jsxref("Memory.prototype.grow()")}}, где аргументом будет количество единиц (размером в 64KB) памяти WebAssembly: + +```js +memory.grow(1); +``` + +При превышении максимального значения, указанного при создании объекта памяти, будет выброшено исключение {{jsxref("WebAssembly.RangeError")}}. Движок использует предоставленные верхние границы для резервирования памяти заранее, что делает расширение памяти более эффективным. + +> **Примечание:** Так как размер объекта {{domxref("ArrayBuffer")}} неизменен, после успешного вызова метода {{jsxref("Memory.prototype.grow()")}} свойство buffer объекта памяти будет возвращать уже новый объект {{domxref("ArrayBuffer")}} (с новым размером в свойстве byteLength) и любые предыдущие объекты ArrayBuffer станут в некотором роде “отсоединёнными”, или отключёнными от низкоуровневой памяти, к которой они ранее относились. + +Подобно функциям, диапазоны линейной памяти могут быть импортированы или определены внутри модуля. Также, модуль имеет возможность экспортировать свою память. Это означает, что JavaScript-код может получить доступ к объекту памяти WebAssembly либо c помощью создания нового объекта через конструктор `WebAssembly.Memory` и передачи его в импортируемый объект, либо с помощью получения объекта памяти через экспортируемый объект (через [`Instance.prototype.exports`](/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Instance/exports)). + +### Более сложный пример + +Давайте сделаем вышеупомянутые утверждения понятнее, рассмотрев более сложный пример работы с памятью, где WebAssembly-модуль импортирует объект памяти, который мы определили ранее, после чего JavaScript-код наполняет его с помощью массива целых чисел, а экспортируемая wasm-функция суммирует их. + +1. Скопируйте файл [memory.wasm](https://github.com/mdn/webassembly-examples/raw/master/js-api-examples/memory.wasm) в локальную директорию в которой вы работаете. + + > **Примечание:** вы можете увидеть текстовое представление модуля в файле [memory.wat](https://github.com/mdn/webassembly-examples/blob/master/js-api-examples/memory.wat). + +2. Откройте ваш файл `memory.html` и добавьте следующий код снизу вашего основного скрипта для загрузки, компилирования и создания экземпляра wasm-модуля: + + ```js + WebAssembly.instantiateStreaming(fetch('memory.wasm'), { js: { mem: memory } }) + .then(results => { + // add code here + }); + ``` + +3. Так как модуль экспортирует свою память, которая была передана экземпляру этого модуля при его создании, мы можем наполнить ранее импортированный массив прямо в линейной памяти экземпляра модуля (`mem`), и вызвать экспортированную функцию `accumulate()` для расчёта суммы значений. Добавьте следующий код, в обозначенном месте: + + ```js + var i32 = new Uint32Array(memory.buffer); + + for (var i = 0; i < 10; i++) { + i32[i] = i; + } + + var sum = results.instance.exports.accumulate(0, 10); + console.log(sum); + ``` + +Обратите внимание на то, что мы создаём представление данных {{domxref("Uint32Array")}} с помощью свойства buffer объекта памяти ([`Memory.prototype.buffer`](/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Memory/buffer)), а не самого объекта памяти. + +Импорт памяти почти такой же как импорт функций, только вместо JavaScript функций передаются объекты памяти. Импорт памяти полезен по двум причинам: + +- Он позволяет JavaScript-коду получать и создать начальное содержание памяти перед или одновременно с компиляцией модуля. +- Он позволяет импортировать один объект памяти во множество экземпляров модулей, что является ключевым элементом для реализации динамического связывания в WebAssembly. + +> **Примечание:** вы можете найти полную демонстрацию в файле [memory.html](https://github.com/mdn/webassembly-examples/blob/master/js-api-examples/memory.html) ([см. её также вживую](https://mdn.github.io/webassembly-examples/js-api-examples/memory.html)) — эта версия использует функцию [`fetchAndInstantiate()`](https://github.com/mdn/webassembly-examples/blob/master/wasm-utils.js). + +## Таблицы + +Таблица WebAssembly - это расширяемый типизированный массив [ссылок](), доступ к которому может быть получен из JavaScript и WebAssembly кода. Так как линейная память предоставляет расширяемый массив незащищённых байт, слишком небезопасно размещать там ссылки, так как для движка ссылка является доверенным значением, чьи байты не должны быть прочитаны или записаны кодом напрямую по причинам безопасности, переносимости и стабильности. + +У таблиц есть тип элемента, который ограничивает тип возможной ссылки, который может быть размещён в таблице. В текущей версии WebAssembly, только один тип ссылки используется в WebAssembly коде - функции - и поэтому существует только один возможный тип элемента. В следующих версиях их количество будет увеличено. + +Ссылки на функции необходимы для компиляции в таких языках как C/C++, которые имеют указатели на функции. В родной реализации C/C++, указатель на функцию представлен прямым адресом на код функции в виртуальном адресном пространстве процесса, и потому для ранее упомянутой безопасности, они не могут быть размещены прямо в линейной памяти. Вместо этого ссылки на функции размещаются в таблице, а её индексы, которые являются целыми числами могут быть размещены в линейной памяти и переданы куда угодно. + +Когда приходит время для вызова указателя на функцию, вызывающая сторона WebAssembly предоставляет индекс, который затем может быть проверен на безопасность по таблице перед индексацией и вызовом ссылки на индексированную функцию. Таким образом, таблицы в настоящее время являются лучшим низкоуровневым примитивом, используемым для безопасной и удобной компиляции низкоуровневых возможностей языка программирования. + +Таблицы могут изменятся с помощью метода [`Table.prototype.set()`](/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Table/set), который обновляет одно из значений в таблице, и метода [`Table.prototype.grow()`](/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Table/grow), который увеличивает количество значений, которое может быть размещено в таблице. Это позволяет этому "непрямо-вызываемому набору функций" изменяться со временем, что необходимо для [техник динамического связывания](http://webassembly.org/docs/dynamic-linking/). Изменения немедленно становятся доступными с помощью метода [`Table.prototype.get()`](/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Table/get) в JavaScript-коде и wasm-модулях. + +### Пример таблицы + +Давайте взглянем на простой пример таблицы - модуль WebAssembly, который создаёт и экспортирует таблицу с двумя элементами: элемент под индексом 0 возвращает 13, а элемент под индексом 1 возвращает 42. + +1. Сделайте локальную копию файла [table.wasm](https://github.com/mdn/webassembly-examples/raw/master/js-api-examples/table.wasm) в новой директории. + + > **Примечание:** вы можете посмотреть текстовое представление модуля в файле [table.wat](https://github.com/mdn/webassembly-examples/blob/master/js-api-examples/table.wat). + +2. Создайте новую копию нашего [HTML шаблона](https://github.com/mdn/webassembly-examples/blob/master/template/template.html) в той же директории и назовите его table.html. +3. Как и раньше загрузите, компилируйте, и создайте экземпляр вашего wasm-модуля, добавив следующий код в {{htmlelement("script")}} элемент в тело документа: + + ```js + WebAssembly.instantiateStreaming(fetch('table.wasm')) + .then(function(results) { + // add code here + }); + ``` + +4. Теперь давайте получим доступ к данным в таблицах - добавим следующие строки в ваш код, в обозначенном месте: + + ```js + var tbl = results.instance.exports.tbl; + console.log(tbl.get(0)()); // 13 + console.log(tbl.get(1)()); // 42 + ``` + +Этот код получает доступ к каждой ссылке на функцию, которая размещена в таблице, после чего вызывает её и выводит хранимое значение в консоль. Обратите внимание, что каждая ссылка на функцию получена с помощью вызова метода [`Table.prototype.get()`](/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Table/get), после чего мы добавили пару круглых скобок для вызова самой функции. + +> **Примечание:** вы можете найти нашу полную демонстрацию в файле [table.html](https://github.com/mdn/webassembly-examples/blob/master/js-api-examples/table.html) (см. её также [вживую](https://mdn.github.io/webassembly-examples/js-api-examples/table.html)) — эта версия использует функцию [`fetchAndInstantiate()`](https://github.com/mdn/webassembly-examples/blob/master/wasm-utils.js). + +## Глобальные переменные + +WebAssembly имеет возможность создавать экземпляры глобальных переменных, доступных как в JavaScript так и в экземплярах модулей WebAssembly ({{jsxref("WebAssembly.Module")}}) посредством импорта или экспорта. Это очень полезная возможность, которая позволяет динамически связывать несколько модулей. Для создания глобальной переменной WebAssembly внутри вашего JavaScript-кода, используйте конструктор {{jsxref("WebAssembly.Global()")}}, который выглядит так: + +```js +const global = new WebAssembly.Global({value:'i32', mutable:true}, 0); +``` + +Вы можете видеть, что он принимает 2 параметра: + +- Объект, который содержит 2 свойства, описывающих глобальную переменную: + + - `value`: это тип данных, который может быть одним из типов данных, позволенных внутри WebAssembly модуля — `i32`, `i64`, `f32`, или `f64`. + - `mutable`: булево значение, определяющее что переменная может изменяться. + +- Значение, которое содержит текущее значение переменной. Это может быть любое значение для типа к которому оно относится. + +Что мы будем с этим делать? В следующем примере мы определим глобальную, изменяемую переменную с типом i32 и значением 0. + +Значение глобальной переменной будет изменено на число `42` используя свойство `Global.value`, а после на `43` используя экспортированную функцию `incGlobal()` из модуля `global.wasm` (это добавит 1 к установленному значению и возвратит новое). + +```js +const output = document.getElementById('output'); + +function assertEq(msg, got, expected) { + output.innerHTML += `Testing ${msg}: `; + if (got !== expected) + output.innerHTML += `FAIL!
Got: ${got}
Expected: ${expected}
`; + else + output.innerHTML += `SUCCESS! Got: ${got}
`; +} + +assertEq("WebAssembly.Global exists", typeof WebAssembly.Global, "function"); + +const global = new WebAssembly.Global({value:'i32', mutable:true}, 0); + +WebAssembly.instantiateStreaming(fetch('global.wasm'), { js: { global } }) +.then(({instance}) => { + assertEq("getting initial value from wasm", instance.exports.getGlobal(), 0); + global.value = 42; + assertEq("getting JS-updated value from wasm", instance.exports.getGlobal(), 42); + instance.exports.incGlobal(); + assertEq("getting wasm-updated value from JS", global.value, 43); +}); +``` + +> **Примечание:** вы можете увидеть этот пример вживую на [GitHub](https://mdn.github.io/webassembly-examples/js-api-examples/global.html); смотрите также [исходники](https://github.com/mdn/webassembly-examples/blob/master/js-api-examples/global.html). + +## Множественность + +К этому моменту мы продемонстрировали использование всех ключевых составных элементов WebAssembly, и сейчас самое время рассказать о концепции множественности. Она позволяет WebAssembly иметь множество преимуществ с точки зрения архитектурной эффективности: + +- Один модуль может иметь N экземпляров, точно так же как одно определение функции может произвести N замыканий. +- Один экземпляр модуля может использовать от 0 до 1 объекта памяти, который предоставляет “адресное пространство” экземпляра модуля. Будущие версии WebAssembly позволят иметь 0–N экземпляров объектов на один экземпляр модуля (см. [Несколько таблиц и объектов памяти](http://webassembly.org/docs/future-features/#multiple-tables-and-memories)). +- Один экземпляр модуля может использовать от 0 до 1 объекта таблицы - это “адресное пространство для функций” экземпляра модуля используется для реализации С/С++ указателей на функции. Будущие версии WebAssembly позволят иметь 0–N экземпляров таблиц на один экземпляр модуля. +- Один объект памяти или объект таблицы может быть использован в 0-N экземплярах модулей - эти все экземпляры будут разделять одно и то же адресное пространство, позволяя выполнять [динамическое связывание](http://webassembly.org/docs/dynamic-linking). + +Чтобы ознакомится с множественностью в действии, смотрите нашу объясняющую статью [Изменяющиеся таблицы и динамическое связывание](/ru/docs/WebAssembly/Understanding_the_text_format#Mutating_tables_and_dynamic_linking). + +## Резюме + +В этой статье-путеводителе по основам WebAssembly JavaScript API вы включали модули WebAssembly в среду JavaScript, использовали их функции, объекты памяти, таблицы и глобальные переменные. Мы также затронули концепцию множественности. + +## Смотрите также + +- [webassembly.org](http://webassembly.org/) +- [Основы WebAssembly](/ru/docs/WebAssembly/Concepts) +- [WebAssembly на Mozilla Research](https://research.mozilla.org/webassembly/)