webpack/lib/WebAssemblyGenerator.js

193 lines
4.5 KiB
JavaScript
Raw Normal View History

/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
2018-03-09 00:54:06 +08:00
const { RawSource } = require("webpack-sources");
const { edit, add } = require("@webassemblyjs/wasm-edit");
const { decode } = require("@webassemblyjs/wasm-parser");
// const { print } = require("@webassemblyjs/wast-printer");
const t = require("@webassemblyjs/ast");
// FIXME(sven): remove this once we're ready to merge
function debug(...msg) {
if (false) console.log(...msg);
}
function compose(...fns) {
return fns.reverse().reduce((prevFn, nextFn) => {
return value => nextFn(prevFn(value));
}, value => value);
}
2018-03-09 19:04:27 +08:00
/**
* Utility functions
*/
const isGlobalImport = moduleImport => moduleImport.descr.type === "GlobalType";
const isInstructionOfName = name => instr => instr.id === name;
2018-03-09 00:54:06 +08:00
/**
* Export the start function and removes the start instruction
*/
function rewriteStartFunc(bin) {
debug("rewriteStartFunc");
let startAtFuncIndex;
bin = edit(bin, {
Start(path) {
startAtFuncIndex = path.node.index;
path.remove();
}
});
// No start func, abort here
if (startAtFuncIndex === undefined) {
return bin;
}
debug("found start at func index", startAtFuncIndex.value);
bin = add(bin, [t.moduleExport("start", "Func", startAtFuncIndex)]);
return bin;
}
/**
2018-03-09 19:04:27 +08:00
* Import the WebAssembly.Table used for interop with other modules managed by
* Webpack.
*
* @param {ArrayBuffer} bin
* @returns {ArrayBuffer} bin'
*
* FIXME(sven): I abrirary choose these names but that might cause conflicts with
* the user's code, example if a module is called webpack?
2018-03-09 00:54:06 +08:00
*
2018-03-09 19:04:27 +08:00
* TODO(sven): what should be the TableDescriptor? We can infer the exact initial
* value from the number of imports.
2018-03-09 00:54:06 +08:00
*/
2018-03-09 19:04:27 +08:00
function addImportInteropTable(bin) {
return add(bin, [
t.moduleImport("webpack", "interoptable", t.table("anyfunc", t.limits(10)))
]);
}
2018-03-09 00:54:06 +08:00
2018-03-09 19:04:27 +08:00
/**
* Remove the ModuleImport for globals because they will be reachable throught
* the interoptable now.
*
* @param {ArrayBuffer} bin
* @returns {ArrayBuffer} bin'
*
* FIXME(sven): breaks the non-exported globals because their offset will be
* shifted by i-(number of import removed). We can either shift the index or
* replace by stub ones (non-imported)?
*/
function removeImportedGlobals(bin) {
return edit(bin, {
ModuleImport(path) {
if (isGlobalImport(path.node) === true) {
debug("remove import", path.node.module, path.node.name);
path.remove();
}
}
});
}
/**
* Adds the type definition and update every `get_global` to `call_indirect`.
*
* FIXME(sven): that also breaks the non-import global since they will be
* rewriting to calls
*
* @param {ArrayBuffer} bin
* @returns {ArrayBuffer} bin'
*/
function rewriteGlobalToInteroptable(bin) {
2018-03-09 00:54:06 +08:00
const ast = decode(bin, {
ignoreCodeSection: true,
ignoreDataSection: true
});
2018-03-09 19:04:27 +08:00
/**
* Add the functypes corresponding to the global imported
*/
const functypes = [];
2018-03-09 00:54:06 +08:00
t.traverse(ast, {
2018-03-09 19:04:27 +08:00
/**
* import global of type t1
* type = () => t1
*/
ModuleImport(path) {
if (isGlobalImport(path.node) === true) {
const { valtype } = path.node.descr;
const functype = t.typeInstructionFunc([], [valtype]);
2018-03-09 00:54:06 +08:00
2018-03-09 19:04:27 +08:00
functypes.push(functype);
2018-03-09 00:54:06 +08:00
}
}
});
2018-03-09 19:04:27 +08:00
debug("add functypes", functypes.map(x => x.functype));
2018-03-09 00:54:06 +08:00
2018-03-09 19:04:27 +08:00
bin = add(bin, functypes);
2018-03-09 00:54:06 +08:00
2018-03-09 19:04:27 +08:00
/**
* Rewrite get_global
*/
const isGetGlobalInstruction = isInstructionOfName("get_global");
2018-03-09 00:54:06 +08:00
bin = edit(bin, {
2018-03-09 19:04:27 +08:00
Instr(path) {
if (isGetGlobalInstruction(path.node) === true) {
const [globalIndex] = path.node.args;
const functypeIndex = functypes[globalIndex.value];
if (typeof functypeIndex === "undefined") {
throw new Error(
"Internal failure: can not find the functype for global at index " +
globalIndex.value
);
}
const callIndirectInstruction = t.callIndirectInstructionIndex(
t.indexLiteral(globalIndex.value)
2018-03-09 00:54:06 +08:00
);
2018-03-09 19:04:27 +08:00
path.replaceWith(callIndirectInstruction);
2018-03-09 00:54:06 +08:00
}
}
});
return bin;
}
const transform = compose(
2018-03-09 19:04:27 +08:00
removeImportedGlobals,
rewriteGlobalToInteroptable,
addImportInteropTable,
2018-03-09 00:54:06 +08:00
rewriteStartFunc
);
class WebAssemblyGenerator {
generate(module) {
2018-03-09 00:54:06 +08:00
const bin = module.originalSource().source();
debug("__________________________________________________________");
const newBin = transform(bin);
debug("__________________________________________________________");
2018-03-09 19:04:27 +08:00
// decode(newBin, { dump: true });
// console.log(print(decode(newBin)));
2018-03-09 00:54:06 +08:00
return new RawSource(newBin);
}
}
module.exports = WebAssemblyGenerator;