Skip to content

Commit

Permalink
Merge pull request #7275 from webpack/feature/wasm-mangling
Browse files Browse the repository at this point in the history
WASM: Tree Shaking, Export and import name mangling
  • Loading branch information
sokra committed May 28, 2018
2 parents 317fb35 + 3ac1d02 commit 29cbf98
Show file tree
Hide file tree
Showing 23 changed files with 494 additions and 248 deletions.
30 changes: 20 additions & 10 deletions declarations.d.ts
Expand Up @@ -52,32 +52,38 @@ declare module "@webassemblyjs/ast" {
module: string;
descr: {
type: string;
valtype: string;
id: string;
valtype?: string;
id?: Identifier;
signature?: Signature;
};
name: string;
}
export class ModuleExport extends Node {
name: string;
}
export class ModuleExportDescr extends Node {}
export class IndexLiteral extends Node {}
export class NumberLiteral extends Node {}
export class FloatLiteral extends Node {}
export class Global extends Node {}
export class FuncParam extends Node {}
export class FuncParam extends Node {
valtype: string;
}
export class Instruction extends Node {}
export class CallInstruction extends Instruction {}
export class ObjectInstruction extends Instruction {}
export class Func extends Node {
signature: Signature;
}
export class Signature {
params: any;
result: any;
params: FuncParam[];
results: string[];
}
export class TypeInstructionFunc extends Node {}
export class TypeInstruction extends Node {}
export class IndexInFuncSection extends Node {}
export function indexLiteral(index: number): IndexLiteral;
export function numberLiteral(num: number): NumberLiteral;
export function numberLiteralFromRaw(num: number): NumberLiteral;
export function floatLiteral(value: number, nan?: boolean, inf?: boolean, raw?: string): FloatLiteral;
export function global(globalType: string, nodes: Node[]): Global;
export function identifier(indentifier: string): Identifier;
export function funcParam(valType: string, id: Identifier): FuncParam;
Expand All @@ -88,13 +94,17 @@ declare module "@webassemblyjs/ast" {
type: string,
init: Node[]
): ObjectInstruction;
export function func(initFuncId, funcParams, funcResults, funcBody): Func;
export function typeInstructionFunc(params, result): TypeInstructionFunc;
export function signature(params: FuncParam[], results: string[]): Signature;
export function func(initFuncId, Signature, funcBody): Func;
export function typeInstruction(id: Identifier, functype: Signature): TypeInstruction;
export function indexInFuncSection(index: IndexLiteral): IndexInFuncSection;
export function moduleExport(
identifier: string,
descr: ModuleExportDescr
): ModuleExport;
export function moduleExportDescr(
type: string,
index: IndexLiteral
index: ModuleExportDescr
): ModuleExport;

export function getSectionMetadata(ast: any, section: string);
Expand Down
2 changes: 1 addition & 1 deletion lib/MainTemplate.js
Expand Up @@ -46,7 +46,7 @@ const Template = require("./Template");
// __webpack_require__.r = define compatibility on export
// __webpack_require__.n = compatibility get default export
// __webpack_require__.h = the webpack hash
// __webpack_require__.w = an object containing all installed WebAssembly.Modules keys by module id
// __webpack_require__.w = an object containing all installed WebAssembly.Instance export objects keyed by module id
// __webpack_require__.oe = the uncaught error handler for the webpack runtime
// __webpack_require__.nc = the script nonce

Expand Down
8 changes: 6 additions & 2 deletions lib/dependencies/WebAssemblyImportDependency.js
Expand Up @@ -11,8 +11,11 @@ const UnsupportedWebAssemblyFeatureError = require("../wasm/UnsupportedWebAssemb
class WebAssemblyImportDependency extends ModuleDependency {
constructor(request, name, description, onlyDirectImport) {
super(request);
/** @type {string} */
this.name = name;
/** @type {TODO} */
this.description = description;
/** @type {false | string} */
this.onlyDirectImport = onlyDirectImport;
}

Expand All @@ -27,10 +30,11 @@ class WebAssemblyImportDependency extends ModuleDependency {
this.module &&
!this.module.type.startsWith("webassembly")
) {
const type = this.description.type;
return [
new UnsupportedWebAssemblyFeatureError(
`${type} imports are only available for direct wasm to wasm dependencies`
`Import with ${
this.onlyDirectImport
} can only be used for direct wasm to wasm dependencies`
)
];
}
Expand Down
172 changes: 88 additions & 84 deletions lib/wasm/WasmMainTemplatePlugin.js
Expand Up @@ -5,7 +5,9 @@
"use strict";

const Template = require("../Template");
const WebAssemblyImportDependency = require("../dependencies/WebAssemblyImportDependency");
const WebAssemblyUtils = require("./WebAssemblyUtils");

/** @typedef {import("../Module")} Module */

// Get all wasm modules
function getAllWasmModules(chunk) {
Expand All @@ -22,96 +24,88 @@ function getAllWasmModules(chunk) {
return array;
}

/**
* generates the import object function for a module
* @param {Module} module the module
* @returns {string} source code
*/
function generateImportObject(module) {
const depsByRequest = new Map();
for (const dep of module.dependencies) {
if (dep instanceof WebAssemblyImportDependency) {
// Ignore global they will be handled later
if (dep.description.type === "GlobalType") {
continue;
}
const waitForInstances = new Map();
const properties = [];
const usedWasmDependencies = WebAssemblyUtils.getUsedDependencies(module);
for (const usedDep of usedWasmDependencies) {
const dep = usedDep.dependency;
const importedModule = dep.module;
const exportName = dep.name;
const usedName = importedModule && importedModule.isUsed(exportName);
const description = dep.description;
const direct = dep.onlyDirectImport;

const request = dep.request;
let array = depsByRequest.get(request);
if (!array) {
depsByRequest.set(request, (array = []));
}
const exportName = dep.name;
const usedName = dep.module && dep.module.isUsed(exportName);
const propertyName = usedDep.name;

if (dep.module === null) {
// Dependency was not found, an error will be thrown later
continue;
}

if (usedName !== false) {
array.push({
exportName,
usedName,
module: dep.module,
description: dep.description,
direct: dep.onlyDirectImport
});
}
}
}
const importsCode = [];
const waitForPromises = new Map();
for (const pair of depsByRequest) {
const properties = [];
for (const data of pair[1]) {
if (data.direct) {
const instanceVar = `m${waitForPromises.size}`;
waitForPromises.set(
instanceVar,
`installedWasmModules[${JSON.stringify(data.module.id)}]`
);
properties.push(
`${JSON.stringify(data.exportName)}: ${instanceVar}.exports` +
`[${JSON.stringify(data.exportName)}]`
);
} else {
const params = data.description.signature.params.map(
(param, k) => "p" + k + param.valtype
);
if (direct) {
const instanceVar = `m${waitForInstances.size}`;
waitForInstances.set(instanceVar, importedModule.id);
properties.push(
`${JSON.stringify(propertyName)}: ${instanceVar}` +
`[${JSON.stringify(usedName)}]`
);
} else {
const params = description.signature.params.map(
(param, k) => "p" + k + param.valtype
);

const result = `__webpack_require__(${JSON.stringify(
data.module.id
)})[${JSON.stringify(data.usedName)}](${params})`;
const mod = `installedModules[${JSON.stringify(importedModule.id)}]`;
const func = `${mod}.exports[${JSON.stringify(usedName)}]`;

properties.push(
Template.asString([
`${JSON.stringify(data.exportName)}: function(${params}) {`,
Template.indent([`return ${result};`]),
"}"
])
);
}
properties.push(
Template.asString([
`${JSON.stringify(propertyName)}: ` +
(importedModule.type.startsWith("webassembly")
? `${mod} ? ${func} : `
: "") +
`function(${params}) {`,
Template.indent([`return ${func}(${params});`]),
"}"
])
);
}

importsCode.push(
Template.asString([
`${JSON.stringify(pair[0])}: {`,
Template.indent([properties.join(",")]),
"}"
])
);
}

if (waitForPromises.size > 0) {
const promises = Array.from(waitForPromises.values()).join(", ");
if (waitForInstances.size === 1) {
const moduleId = Array.from(waitForInstances.values())[0];
const promise = `installedWasmModules[${JSON.stringify(moduleId)}]`;
const variable = Array.from(waitForInstances.keys())[0];
return Template.asString([
`${JSON.stringify(module.id)}: function() {`,
Template.indent([
`return promiseResolve().then(function() { return ${promise}; }).then(function(${variable}) {`,
Template.indent([
"return {",
Template.indent([properties.join(",\n")]),
"};"
]),
"});"
]),
"},"
]);
} else if (waitForInstances.size > 0) {
const promises = Array.from(
waitForInstances.values(),
id => `installedWasmModules[${JSON.stringify(id)}]`
).join(", ");
const variables = Array.from(
waitForPromises.keys(),
(name, i) => `var ${name} = array[${i}];`
).join("\n");
waitForInstances.keys(),
(name, i) => `${name} = array[${i}];`
).join(", ");
return Template.asString([
`${JSON.stringify(module.id)}: function() {`,
Template.indent([
`return Promise.resolve().then(function() { return Promise.all([${promises}]); }).then(function(array) {`,
`return promiseResolve().then(function() { return Promise.all([${promises}]); }).then(function(array) {`,
Template.indent([
variables,
`var ${variables};`,
"return {",
Template.indent([importsCode.join(",")]),
Template.indent([properties.join(",\n")]),
"};"
]),
"});"
Expand All @@ -123,7 +117,7 @@ function generateImportObject(module) {
`${JSON.stringify(module.id)}: function() {`,
Template.indent([
"return {",
Template.indent([importsCode.join(",")]),
Template.indent([properties.join(",\n")]),
"};"
]),
"},"
Expand All @@ -149,6 +143,12 @@ class WasmMainTemplatePlugin {
"// object to store loaded and loading wasm modules",
"var installedWasmModules = {};",
"",
// This function is used to delay reading the installed wasm module promises
// by a microtask. Sorting them doesn't help because there are egdecases where
// sorting is not possible (modules splitted into different chunks).
// So we not even trying and solve this by a microtask delay.
"function promiseResolve() { return Promise.resolve(); }",
"",
"var wasmImportObjects = {",
Template.indent(importObjects),
"};"
Expand Down Expand Up @@ -218,13 +218,15 @@ class WasmMainTemplatePlugin {
Template.indent([
"promise = Promise.all([WebAssembly.compileStreaming(req), importObject]).then(function(items) {",
Template.indent([
"return WebAssembly.instantiate(items[0], items[1]);"
"return WebAssembly.instantiate(items[0], " +
`{ ${WebAssemblyUtils.MANGLED_MODULE}: items[1] });`
]),
"});"
]),
"} else if(typeof WebAssembly.instantiateStreaming === 'function') {",
Template.indent([
"promise = WebAssembly.instantiateStreaming(req, importObject);"
"promise = WebAssembly.instantiateStreaming(req, " +
`{ ${WebAssemblyUtils.MANGLED_MODULE}: importObject });`
])
])
: Template.asString([
Expand All @@ -238,7 +240,8 @@ class WasmMainTemplatePlugin {
]),
"]).then(function(items) {",
Template.indent([
"return WebAssembly.instantiate(items[0], items[1]);"
"return WebAssembly.instantiate(items[0], " +
`{ ${WebAssemblyUtils.MANGLED_MODULE}: items[1] });`
]),
"});"
])
Expand All @@ -248,7 +251,8 @@ class WasmMainTemplatePlugin {
"var bytesPromise = req.then(function(x) { return x.arrayBuffer(); });",
"promise = bytesPromise.then(function(bytes) {",
Template.indent([
"return WebAssembly.instantiate(bytes, importObject);"
"return WebAssembly.instantiate(bytes, " +
`{ ${WebAssemblyUtils.MANGLED_MODULE}: importObject });`
]),
"});"
]),
Expand All @@ -257,7 +261,7 @@ class WasmMainTemplatePlugin {
Template.indent([
`return ${
mainTemplate.requireFn
}.w[wasmModuleId] = res.instance || res;`
}.w[wasmModuleId] = (res.instance || res).exports;`
]),
"}));"
]),
Expand All @@ -275,7 +279,7 @@ class WasmMainTemplatePlugin {
return Template.asString([
source,
"",
"// object with all WebAssembly.instance",
"// object with all WebAssembly.instance exports",
`${mainTemplate.requireFn}.w = {};`
]);
}
Expand Down

0 comments on commit 29cbf98

Please sign in to comment.