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出来的模块进行二次包装,主要函数的整理,入参的类型处理
- 完成组件的生命周期的控制
- 联调核心功能
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。