wasm系列之初探胶水代码

wasm系列之初探胶水代码

上一小节,我们完成了一个基本的Hello World的demo,我们知道在产物中有一段hello.js,一个包含了用来在原生 C 函数和 JavaScript/wasm 之间转换的胶水代码的 JavaScript 文件。

今天我们简单分析下这个js文件里面的一些关键代码。

createWasm

var asm = createWasm();

这里是发起调用的地方。

instantiateAsync

优先使用WebAssembly.instantiateStreaming()方法创建wasm模块的实例;

var dataURIPrefix = 'data:application/octet-stream;base64,'; // Indicates whether filename is a base64 data URI. function isDataURI(filename) { // Prefix of data URIs emitted by SINGLE_FILE and related options. return filename.startsWith(dataURIPrefix); function isFileURI(filename) { return filename.startsWith('file://'); function receiveInstantiationResult(result) { // 'result' is a ResultObject object which has both the module and instance. // receiveInstance() will swap in the exports (to Module.asm) so they can be called assert(Module === trueModule, 'the Module object should not be replaced during async compilation - perhaps the order of HTML elements is wrong?'); trueModule = null; // TODO: Due to Closure regression https://github.com/google/closure-compiler/issues/3193, the above line no longer optimizes out down to the following line. // When the regression is fixed, can restore the above USE_PTHREADS-enabled path. // 编译、实例化之后的结果在这里 result // result 有两个属性 // module: WebAssembly.Module 对象表示编译完成的 WebAssembly 模块。这个Module能够再次被实例化 或 通过postMessage()共享。 // instance: WebAssembly.Instance 对象包含 WebAssembly 所有公开方法 Exported WebAssembly functions. receiveInstance(result['instance']); function instantiateAsync() { if (!wasmBinary && typeof WebAssembly.instantiateStreaming == 'function' && !isDataURI(wasmBinaryFile) && // Don't use streaming for file:// delivered objects in a webview, fetch them synchronously. !isFileURI(wasmBinaryFile) && // Avoid instantiateStreaming() on Node.js environment for now, as while // Node.js v18.1.0 implements it, it does not have a full fetch() // implementation yet. // Reference: // https://github.com/emscripten-core/emscripten/pull/16917 !ENVIRONMENT_IS_NODE && typeof fetch == 'function') { // 这里主要是做了一些条件判断,比如非nodejs环境、支持fetch、wasmBinaryFile的类型要求 // 步骤1 fetch:先拉取资源 return fetch(wasmBinaryFile, { credentials: 'same-origin' }).then(function(response) { // Suppress closure warning here since the upstream definition for // instantiateStreaming only allows Promise<Repsponse> rather than // an actual Response. // TODO(https://github.com/google/closure-compiler/pull/3913): Remove if/when upstream closure is fixed. /** @suppress {checkTypes} */ // 步骤2 WebAssembly.instantiateStreaming() 方法直接从流式底层源编译和实例化 WebAssembly 模块。这是加载 wasm 代码一种非常有效的优化方式。 var result = WebAssembly.instantiateStreaming(response, info); return result.then( receiveInstantiationResult, function(reason) { // We expect the most common failure cause to be a bad MIME type for the binary, // in which case falling back to ArrayBuffer instantiation should work. err('wasm streaming compile failed: ' + reason); err('falling back to ArrayBuffer instantiation'); return instantiateArrayBuffer(receiveInstantiationResult); } else { return instantiateArrayBuffer(receiveInstantiationResult);

实例化后的结果

instantiateArrayBuffer

如果流式创建失败或者失败,则改用WebAssembly.instantiate()方法创建实例;

// 判断环境的一些方法
var ENVIRONMENT_IS_WEB = typeof window == 'object';
var ENVIRONMENT_IS_WORKER = typeof importScripts == 'function';
// N.b. Electron.js environment is simultaneously a NODE-environment, but
// also a web environment.
var ENVIRONMENT_IS_NODE = typeof process == 'object' && typeof process.versions == 'object' && typeof process.versions.node == 'string';
var ENVIRONMENT_IS_SHELL = !ENVIRONMENT_IS_WEB && !ENVIRONMENT_IS_NODE && !ENVIRONMENT_IS_WORKER;
readAsync = (url, onload, onerror) => {
  var xhr = new XMLHttpRequest();
  xhr.open('GET', url, true);
  xhr.responseType = 'arraybuffer';
  xhr.onload = () => {
    if (xhr.status == 200 || (xhr.status == 0 && xhr.response)) { // file URLs can return 0
      onload(xhr.response);
      return;
    onerror();
  xhr.onerror = onerror;
  xhr.send(null);
// 这里主要是fetch 或者降级的 ajax下载文件
function getBinaryPromise() {
  // If we don't have the binary yet, try to to load it asynchronously.
  // Fetch has some additional restrictions over XHR, like it can't be used on a file:// url.
  // See https://github.com/github/fetch/pull/92#issuecomment-140665932
  // Cordova or Electron apps are typically loaded from a file:// url.
  // So use fetch if it is available and the url is not a file, otherwise fall back to XHR.
  if (!wasmBinary && (ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER)) {
    if (typeof fetch == 'function'
      && !isFileURI(wasmBinaryFile)
      return fetch(wasmBinaryFile, { credentials: 'same-origin' }).then(function(response) {
        if (!response['ok']) {
          throw "failed to load wasm binary file at '" + wasmBinaryFile + "'";
        return response['arrayBuffer']();
      }).catch(function () {
          return getBinary(wasmBinaryFile);
    else {
      if (readAsync) {
        // fetch is not available or url is file => try XHR (readAsync uses XHR internally)
        return new Promise(function(resolve, reject) {
          readAsync(wasmBinaryFile, function(response) { resolve(new Uint8Array(/** @type{!ArrayBuffer} */(response))) }, reject)
  // Otherwise, getBinary should be able to get it synchronously
  return Promise.resolve().then(function() { return getBinary(wasmBinaryFile); });
function instantiateArrayBuffer(receiver) {
  return getBinaryPromise().then(function(binary) {
    // 降级的方法 WebAssembly.instantiate() 允许你编译和实例化 WebAssembly 代码
    return WebAssembly.instantiate(binary, info);
  }).then(function (instance) {
    return instance;
  }).then(receiver, function(reason) {
    err('failed to asynchronously prepare wasm: ' + reason);
    // Warn on some common problems.
    if (isFileURI(wasmBinaryFile)) {
      err('warning: Loading from a file URI (' + wasmBinaryFile + ') is not supported in most browsers. See https://emscripten.org/docs/getting_started/FAQ.html#how-do-i-run-a-local-webserver-for-testing-why-does-my-program-stall-in-downloading-or-preparing');
    abort(reason);

receiveInstance

成功实例化后的返回值交由receiveInstance()方法处理。

function receiveInstance(instance, module) { var exports = instance.exports; // Module['asm']中保存了WebAssembly实例的导出对象——而导出函数恰是WebAssembly实例供外部调用最主要的入口。 Module['asm'] = exports; wasmMemory = Module['asm']['memory']; assert(wasmMemory, "memory not found in wasm exports"); // This assertion doesn't hold when emscripten is run in --post-link // mode. // TODO(sbc): Read INITIAL_MEMORY out of the wasm file in post-link mode. //assert(wasmMemory.buffer.byteLength === 16777216); updateGlobalBufferAndViews(wasmMemory.buffer); wasmTable = Module['asm']['__indirect_function_table']; assert(wasmTable, "table not found in wasm exports"); addOnInit(Module['asm']['__wasm_call_ctors']); removeRunDependency('wasm-instantiate');

我们通过之前命名就知道,wasm的下载、编译、实例化的过程都是异步的。 因此如果我们提前使用Module中的方法,那么这个时候是会报错的,因为wasm 的 runtime尚未准备好。

 <script async type="text/javascript" src="hello.js"></script>
<script>
  Module._main();
</script>

那么我们可以怎么解呢?其实也很简单。

我们可以注册一个回调,等wasm runtime ready之后呢,就可以执行了。

<script async type="text/javascript" src="hello.js"></script>
<script>
  Module.onRuntimeInitialized = function () {
    Module._main();
</script>

这个在胶水代码中也是可以看到的

function doRun() {
  // run may have just been called through dependencies being fulfilled just in this very frame,
  // or while the async setStatus time below was happening
  if (calledRun) return;
  calledRun = true;
  Module['calledRun'] = true;
  if (ABORT) return;
  initRuntime();
  preMain();
  if (Module['onRuntimeInitialized']) Module['onRuntimeInitialized']();
  if (shouldRunNow) callMain(args);
  postRun();

wasm系列文章最后会同步在github,有需要的可以star关注下。

截屏2022-12-10 11.56.19.png