2018-01-24 06:09:26 +08:00
|
|
|
/*
|
|
|
|
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
|
|
|
|
);
|
|
|
|
|
2018-01-24 06:09:26 +08:00
|
|
|
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);
|
2018-01-24 06:09:26 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = WebAssemblyGenerator;
|