关于前端:emscripten学习

8次阅读

共计 7035 个字符,预计需要花费 18 分钟才能阅读完成。

what

emscripten 是一个 c /cpp 的编译器,能够将 c /cpp 代码用 LLVM,编译为 WebAssembly。其中 emcc 是他的 cli,相似于罕用的 makefile 的作用。

编译产物

可选的比拟多 html js wasm 都能够自由组合

js 产物源码解读

应用 emcc 对一个 c 文件进行编译会产出一个 js 文件和一个 wasm 文件。js 文件中的源码齐全兼容了 node 环境和 web 环境,本局部仅探讨 web 环境。

if (ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER) {if (ENVIRONMENT_IS_WORKER) { // Check worker, not web, since window could be polyfilled
    scriptDirectory = self.location.href;
  } else if (typeof document !== 'undefined' && document.currentScript) { // web
    scriptDirectory = document.currentScript.src;
  }
  // blob urls look like blob:http://site.com/etc/etc and we cannot infer anything from them.
  // otherwise, slice off the final part of the url to find the script directory.
  // if scriptDirectory does not contain a slash, lastIndexOf will return -1,
  // and scriptDirectory will correctly be replaced with an empty string.
  if (scriptDirectory.indexOf('blob:') !== 0) {scriptDirectory = scriptDirectory.substr(0, scriptDirectory.lastIndexOf('/')+1);
  } else {scriptDirectory = '';}

  // Differentiate the Web Worker from the Node Worker case, as reading must
  // be done differently.
  {

// include: web_or_worker_shell_read.js


  read_ = function shell_read(url) {var xhr = new XMLHttpRequest();
      xhr.open('GET', url, false);
      xhr.send(null);
      return xhr.responseText;
  };

  if (ENVIRONMENT_IS_WORKER) {readBinary = function readBinary(url) {var xhr = new XMLHttpRequest();
        xhr.open('GET', url, false);
        xhr.responseType = 'arraybuffer';
        xhr.send(null);
        return new Uint8Array(/** @type{!ArrayBuffer} */(xhr.response));
    };
  }

  readAsync = function readAsync(url, onload, onerror) {var xhr = new XMLHttpRequest();
    xhr.open('GET', url, true);
    xhr.responseType = 'arraybuffer';
    xhr.onload = function 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);
  };

// end include: web_or_worker_shell_read.js
  }

  setWindowTitle = function(title) {document.title = title};
} else
{throw new Error('environment detection error');
}

以上为 em 构建的在 web 中的文件系统

如何革新 em 输入的模块
em 会取全局的 Module 对象 并且反对 Module 对象的革新,能够通过这个形式改写 em 中的

em 中的一些罕用函数
js 中定义了罕用的工具函数,比方如何通过办法名拜访 c 模块或者 c 函数,c 与 js 中数据类型的转换
这部分其实就是 https://emscripten.org/docs/a… 的内容

em js 的生命周期

function preRun() {if (Module['preRun']) {if (typeof Module['preRun'] == 'function') Module['preRun'] = [Module['preRun']];
    while (Module['preRun'].length) {addOnPreRun(Module['preRun'].shift());
    }
  }

  callRuntimeCallbacks(__ATPRERUN__);
}

function initRuntime() {checkStackCookie();
  assert(!runtimeInitialized);
  runtimeInitialized = true;
  if (!Module["noFSInit"] && !FS.init.initialized) FS.init();
TTY.init();
  callRuntimeCallbacks(__ATINIT__);
}

function preMain() {checkStackCookie();
  FS.ignorePermissions = false;
  callRuntimeCallbacks(__ATMAIN__);
}

function exitRuntime() {checkStackCookie();
  runtimeExited = true;
}

这里能够减少在 asm 运行中的钩子函数

外围函数 createWasm

function createWasm() {
  // prepare imports
  var info = {
    'env': asmLibraryArg,
    'wasi_snapshot_preview1': asmLibraryArg,
  };
  // Load the wasm module and create an instance of using native support in the JS engine.
  // handle a generated wasm instance, receiving its exports and
  // performing other necessary setup
  /** @param {WebAssembly.Module=} module*/
  function receiveInstance(instance, module) {
    var exports = instance.exports;

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

    removeRunDependency('wasm-instantiate');
  }
  // we can't run yet (except in a pthread, where we have a custom sync instantiator)
  addRunDependency('wasm-instantiate');

  // Async compilation can be confusing when an error on the page overwrites Module
  // (for example, if the order of elements is wrong, and the one defining Module is
  // later), so we save Module and check it later.
  var trueModule = Module;
  function receiveInstantiatedSource(output) {
    // 'output' is a WebAssemblyInstantiatedSource 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.
    receiveInstance(output['instance']);
  }

  function instantiateArrayBuffer(receiver) {return getBinaryPromise().then(function(binary) {return WebAssembly.instantiate(binary, info);
    }).then(receiver, function(reason) {err('failed to asynchronously prepare wasm:' + reason);

      abort(reason);
    });
  }

  // Prefer streaming instantiation if available.
  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) &&
        typeof fetch === 'function') {return fetch(wasmBinaryFile, { credentials: 'same-origin'}).then(function (response) {var result = WebAssembly.instantiateStreaming(response, info);
        return result.then(receiveInstantiatedSource, 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(receiveInstantiatedSource);
          });
      });
    } else {return instantiateArrayBuffer(receiveInstantiatedSource);
    }
  }

  // User shell pages can write their own Module.instantiateWasm = function(imports, successCallback) callback
  // to manually instantiate the Wasm module themselves. This allows pages to run the instantiation parallel
  // to any other async startup actions they are performing.
  if (Module['instantiateWasm']) {
    try {var exports = Module['instantiateWasm'](info, receiveInstance);
      return exports;
    } catch(e) {err('Module.instantiateWasm callback failed with error:' + e);
      return false;
    }
  }

  instantiateAsync();
  return {}; // no exports yet; we'll fill them in later}

加载 wasm 文件,这里次要是进行了初始化的申明 蕴含了内存空间的申明和依赖的注入,这里都是由 em 主动治理的

关注点 wasm 的加载 看到源码中 wasm 文件都是近程加载的 此时咱们须要替换咱们的 wasm 文件到 cdn 上,咱们须要减少 Module[‘locateFile’] 保障咱们的 wasm 能够指向咱们须要的地址

var wasmBinaryFile = 'xxx.wasm';
if (!isDataURI(wasmBinaryFile)) {//dataurl 判断 这里只有不是 base64 的都会为 true
  wasmBinaryFile = locateFile(wasmBinaryFile);
}

// `/` should be present at the end if `scriptDirectory` is not empty
var scriptDirectory = '';
function locateFile(path) {if (Module['locateFile']) {return Module['locateFile'](path, scriptDirectory);
  }
  return scriptDirectory + path;
}

if (ENVIRONMENT_IS_WORKER) { // Check worker, not web, since window could be polyfilled
    scriptDirectory = self.location.href;
  } else if (typeof document !== 'undefined' && document.currentScript) { // web
    scriptDirectory = document.currentScript.src;
  }
//scriptDirectory 就是脚本的 src  不过这个不重要 Module['locateFile']能够从入参中获取

执行完数 createWasm 咱们就取得了一个 promise

源码中还有要害的函数钩子 Module.instantiateWasm 咱们能够在这里获取 wasm 的实例,到此 整个 wasm 的初始化后的输出模块咱们就能够取得了

main 函数的运行
这里值得是 c 中的 main 函数,如果是模块输入其实不须要 main 函数
他对照了模块中的 run 函数 在加载后 默认是会间接运行的 咱们能够通过 noInitialRun 让主函数不执行,而后利用模块的 run 函数去运行,反对参数的传入,非常适合 wasm 模块的初始化场景

工程化设计

  1. 编写定制化的 Module 对象,必须改写的函数次要是生命周期函数和 ’locateFile’
  2. 梳理 c /cpp 模块的边界,哪些是 js 实现哪些是 c 实现,确认输入输出,这里能够用组件化的思路解决
  3. 编译 c 代码,输入为 js 和 wasm 即可
  4. 对 export 进去的模块进行二次包装,次要函数的整顿,入参的类型解决
  5. 实现组件的生命周期的管制
  6. 联调外围性能
正文完
 0