Загрузка и запуск кода WebAssembly

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

Какие есть варианты?

WebAssembly ещё не интегрирована с <script type='module'> или ES2015 оператором import, поэтому не существует пути, позволяющего использовать модули загрузки браузера для использования импорта.

Старые методы WebAssembly.compile/WebAssembly.instantiate (en-US) требуют создания ArrayBuffer, содержащего двоичный файл модуля WebAssembly после загрузки необработанных байтов, а затем скомпилировать/создать его экземпляр. Это аналог new Function(string), за исключением того, что мы заменяем строку символов (исходный код JavaScript) буфером байтов массива (исходный код WebAssembly).

Более новые методы WebAssembly.compileStreaming/WebAssembly.instantiateStreaming (en-US) намного эффективнее - они выполняют свои действия непосредственно с необработанным потоком байтов, поступающих из сети, избавление от необходимости шага ArrayBuffer.

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

Используя Fetch

Fetch - это удобный современный API для извлечения сетевых ресурсов.

Самый быстрый и эффективный способ получить модуль wasm - использовать более новый метод WebAssembly.instantiateStreaming() (en-US), который может принять вызов fetch() в качестве первого аргумента и будет обрабатывать загрузку, компиляцию и создание экземпляра модуля за один шаг, получая доступ к необработанному байтовому коду при его потоковой передаче с сервера:

js
WebAssembly.instantiateStreaming(fetch("simple.wasm"), importObject).then(
  (results) => {
    // Do something with the results!
  },
);

Если бы мы использовали более старый метод WebAssembly.instantiate() (en-US), который не работает в прямом потоке, нам потребовался бы дополнительный шаг преобразования преобразованного байт-кода в ArrayBuffer, вот так:

js
fetch("module.wasm")
  .then((response) => response.arrayBuffer())
  .then((bytes) => WebAssembly.instantiate(bytes, importObject))
  .then((results) => {
    // Do something with the results!
  });

Помимо перегрузок instantiate()

Функция WebAssembly.instantiate() (en-US) имеет две формы перегрузки - та, что показана выше, принимает байт-код для компиляции в качестве аргумента и возвращает Promise, которое разрешается для объекта, содержащего оба объекта скомпилированного модуля, и экземпляр этого. Объект выглядит так:

js
{
  module: Module; // The newly compiled WebAssembly.Module object,
  instance: Instance; // A new WebAssembly.Instance of the module object
}

Примечание: Обычно мы заботимся только об экземпляре, но полезно иметь модуль на тот случай, если мы хотим его кешировать, поделиться им с другим работником или окном через postMessage() (en-US), или просто создать больше экземпляров.

Примечание: Вторая форма перегрузки принимает в качестве аргумента объект WebAssembly.Module (en-US) и возвращает Promise, непосредственно содержащее объект экземпляра, в качестве результата. См. Второй пример перегрузки (en-US).

Выполнение вашего кода WebAssembly

Когда у вас есть экземпляр WebAssembly, доступный в вашем JavaScript, вы можете начать использовать его возможности, которые были экспортированы через свойство WebAssembly.Instance.exports (en-US). Ваш код может выглядеть примерно так:

js
WebAssembly.instantiateStreaming(fetch("myModule.wasm"), importObject).then(
  (obj) => {
    // Call an exported function:
    obj.instance.exports.exported_func();

    // or access the buffer contents of an exported memory:
    var i32 = new Uint32Array(obj.instance.exports.memory.buffer);

    // or access the elements of an exported table:
    var table = obj.instance.exports.table;
    console.log(table.get(0)());
  },
);

Примечание: Для получения дополнительной информации о том, как работает экспорт из модуля WebAssembly, ознакомьтесь с разделами Использование JavaScript API WebAssembly, и Понимание текстового формата WebAssembly.

Используя XMLHttpRequest

XMLHttpRequest несколько старше, чем Fetch, но всё же может успешно использоваться для получения типизированного массива. Опять же, если предположить, что наш модуль называется simple.wasm:

  1. Создайте новый экземпляр XMLHttpRequest() и используйте его метод open() для открытия запроса, задав для метода запроса значение GET и указав путь к файлу, который мы хотим получить.
  2. Ключевой частью этого является установка типа ответа 'arraybuffer' с помощью свойства responseType.
  3. Затем отправьте запрос с помощью XMLHttpRequest.send().
  4. Затем мы используем обработчик событий onload для вызова функции после завершения загрузки ответа - в этой функции мы получаем буфер массива из response и затем передайте это в наш метод WebAssembly.instantiate() (en-US), как мы это делали с Fetch.

Финальный код выглядит так:

js
request = new XMLHttpRequest();
request.open("GET", "simple.wasm");
request.responseType = "arraybuffer";
request.send();

request.onload = function () {
  var bytes = request.response;
  WebAssembly.instantiate(bytes, importObject).then((results) => {
    results.instance.exports.exported_func();
  });
};

Примечание: вы можете увидеть пример этого в действии в xhr-wasm.html.