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 emptyvar 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模块的初始化场景
工程化设计
- 编写定制化的Module对象,必须改写的函数次要是生命周期函数和'locateFile'
- 梳理c/cpp模块的边界,哪些是js实现哪些是c实现,确认输入输出,这里能够用组件化的思路解决
- 编译c代码,输入为js和wasm即可
- 对export进去的模块进行二次包装,次要函数的整顿,入参的类型解决
- 实现组件的生命周期的管制
- 联调外围性能