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