Skip to content

Commit 29cbf98

Browse files
authored
Merge pull request webpack#7275 from webpack/feature/wasm-mangling
WASM: Tree Shaking, Export and import name mangling
2 parents 317fb35 + 3ac1d02 commit 29cbf98

23 files changed

+494
-248
lines changed

declarations.d.ts

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -52,32 +52,38 @@ declare module "@webassemblyjs/ast" {
5252
module: string;
5353
descr: {
5454
type: string;
55-
valtype: string;
56-
id: string;
55+
valtype?: string;
56+
id?: Identifier;
57+
signature?: Signature;
5758
};
5859
name: string;
5960
}
6061
export class ModuleExport extends Node {
6162
name: string;
6263
}
64+
export class ModuleExportDescr extends Node {}
6365
export class IndexLiteral extends Node {}
6466
export class NumberLiteral extends Node {}
67+
export class FloatLiteral extends Node {}
6568
export class Global extends Node {}
66-
export class FuncParam extends Node {}
69+
export class FuncParam extends Node {
70+
valtype: string;
71+
}
6772
export class Instruction extends Node {}
6873
export class CallInstruction extends Instruction {}
6974
export class ObjectInstruction extends Instruction {}
7075
export class Func extends Node {
7176
signature: Signature;
7277
}
7378
export class Signature {
74-
params: any;
75-
result: any;
79+
params: FuncParam[];
80+
results: string[];
7681
}
77-
export class TypeInstructionFunc extends Node {}
82+
export class TypeInstruction extends Node {}
7883
export class IndexInFuncSection extends Node {}
7984
export function indexLiteral(index: number): IndexLiteral;
80-
export function numberLiteral(num: number): NumberLiteral;
85+
export function numberLiteralFromRaw(num: number): NumberLiteral;
86+
export function floatLiteral(value: number, nan?: boolean, inf?: boolean, raw?: string): FloatLiteral;
8187
export function global(globalType: string, nodes: Node[]): Global;
8288
export function identifier(indentifier: string): Identifier;
8389
export function funcParam(valType: string, id: Identifier): FuncParam;
@@ -88,13 +94,17 @@ declare module "@webassemblyjs/ast" {
8894
type: string,
8995
init: Node[]
9096
): ObjectInstruction;
91-
export function func(initFuncId, funcParams, funcResults, funcBody): Func;
92-
export function typeInstructionFunc(params, result): TypeInstructionFunc;
97+
export function signature(params: FuncParam[], results: string[]): Signature;
98+
export function func(initFuncId, Signature, funcBody): Func;
99+
export function typeInstruction(id: Identifier, functype: Signature): TypeInstruction;
93100
export function indexInFuncSection(index: IndexLiteral): IndexInFuncSection;
94101
export function moduleExport(
95102
identifier: string,
103+
descr: ModuleExportDescr
104+
): ModuleExport;
105+
export function moduleExportDescr(
96106
type: string,
97-
index: IndexLiteral
107+
index: ModuleExportDescr
98108
): ModuleExport;
99109

100110
export function getSectionMetadata(ast: any, section: string);

lib/MainTemplate.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ const Template = require("./Template");
4646
// __webpack_require__.r = define compatibility on export
4747
// __webpack_require__.n = compatibility get default export
4848
// __webpack_require__.h = the webpack hash
49-
// __webpack_require__.w = an object containing all installed WebAssembly.Modules keys by module id
49+
// __webpack_require__.w = an object containing all installed WebAssembly.Instance export objects keyed by module id
5050
// __webpack_require__.oe = the uncaught error handler for the webpack runtime
5151
// __webpack_require__.nc = the script nonce
5252

lib/dependencies/WebAssemblyImportDependency.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,11 @@ const UnsupportedWebAssemblyFeatureError = require("../wasm/UnsupportedWebAssemb
1111
class WebAssemblyImportDependency extends ModuleDependency {
1212
constructor(request, name, description, onlyDirectImport) {
1313
super(request);
14+
/** @type {string} */
1415
this.name = name;
16+
/** @type {TODO} */
1517
this.description = description;
18+
/** @type {false | string} */
1619
this.onlyDirectImport = onlyDirectImport;
1720
}
1821

@@ -27,10 +30,11 @@ class WebAssemblyImportDependency extends ModuleDependency {
2730
this.module &&
2831
!this.module.type.startsWith("webassembly")
2932
) {
30-
const type = this.description.type;
3133
return [
3234
new UnsupportedWebAssemblyFeatureError(
33-
`${type} imports are only available for direct wasm to wasm dependencies`
35+
`Import with ${
36+
this.onlyDirectImport
37+
} can only be used for direct wasm to wasm dependencies`
3438
)
3539
];
3640
}

lib/wasm/WasmMainTemplatePlugin.js

Lines changed: 88 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
"use strict";
66

77
const Template = require("../Template");
8-
const WebAssemblyImportDependency = require("../dependencies/WebAssemblyImportDependency");
8+
const WebAssemblyUtils = require("./WebAssemblyUtils");
9+
10+
/** @typedef {import("../Module")} Module */
911

1012
// Get all wasm modules
1113
function getAllWasmModules(chunk) {
@@ -22,96 +24,88 @@ function getAllWasmModules(chunk) {
2224
return array;
2325
}
2426

27+
/**
28+
* generates the import object function for a module
29+
* @param {Module} module the module
30+
* @returns {string} source code
31+
*/
2532
function generateImportObject(module) {
26-
const depsByRequest = new Map();
27-
for (const dep of module.dependencies) {
28-
if (dep instanceof WebAssemblyImportDependency) {
29-
// Ignore global they will be handled later
30-
if (dep.description.type === "GlobalType") {
31-
continue;
32-
}
33+
const waitForInstances = new Map();
34+
const properties = [];
35+
const usedWasmDependencies = WebAssemblyUtils.getUsedDependencies(module);
36+
for (const usedDep of usedWasmDependencies) {
37+
const dep = usedDep.dependency;
38+
const importedModule = dep.module;
39+
const exportName = dep.name;
40+
const usedName = importedModule && importedModule.isUsed(exportName);
41+
const description = dep.description;
42+
const direct = dep.onlyDirectImport;
3343

34-
const request = dep.request;
35-
let array = depsByRequest.get(request);
36-
if (!array) {
37-
depsByRequest.set(request, (array = []));
38-
}
39-
const exportName = dep.name;
40-
const usedName = dep.module && dep.module.isUsed(exportName);
44+
const propertyName = usedDep.name;
4145

42-
if (dep.module === null) {
43-
// Dependency was not found, an error will be thrown later
44-
continue;
45-
}
46-
47-
if (usedName !== false) {
48-
array.push({
49-
exportName,
50-
usedName,
51-
module: dep.module,
52-
description: dep.description,
53-
direct: dep.onlyDirectImport
54-
});
55-
}
56-
}
57-
}
58-
const importsCode = [];
59-
const waitForPromises = new Map();
60-
for (const pair of depsByRequest) {
61-
const properties = [];
62-
for (const data of pair[1]) {
63-
if (data.direct) {
64-
const instanceVar = `m${waitForPromises.size}`;
65-
waitForPromises.set(
66-
instanceVar,
67-
`installedWasmModules[${JSON.stringify(data.module.id)}]`
68-
);
69-
properties.push(
70-
`${JSON.stringify(data.exportName)}: ${instanceVar}.exports` +
71-
`[${JSON.stringify(data.exportName)}]`
72-
);
73-
} else {
74-
const params = data.description.signature.params.map(
75-
(param, k) => "p" + k + param.valtype
76-
);
46+
if (direct) {
47+
const instanceVar = `m${waitForInstances.size}`;
48+
waitForInstances.set(instanceVar, importedModule.id);
49+
properties.push(
50+
`${JSON.stringify(propertyName)}: ${instanceVar}` +
51+
`[${JSON.stringify(usedName)}]`
52+
);
53+
} else {
54+
const params = description.signature.params.map(
55+
(param, k) => "p" + k + param.valtype
56+
);
7757

78-
const result = `__webpack_require__(${JSON.stringify(
79-
data.module.id
80-
)})[${JSON.stringify(data.usedName)}](${params})`;
58+
const mod = `installedModules[${JSON.stringify(importedModule.id)}]`;
59+
const func = `${mod}.exports[${JSON.stringify(usedName)}]`;
8160

82-
properties.push(
83-
Template.asString([
84-
`${JSON.stringify(data.exportName)}: function(${params}) {`,
85-
Template.indent([`return ${result};`]),
86-
"}"
87-
])
88-
);
89-
}
61+
properties.push(
62+
Template.asString([
63+
`${JSON.stringify(propertyName)}: ` +
64+
(importedModule.type.startsWith("webassembly")
65+
? `${mod} ? ${func} : `
66+
: "") +
67+
`function(${params}) {`,
68+
Template.indent([`return ${func}(${params});`]),
69+
"}"
70+
])
71+
);
9072
}
91-
92-
importsCode.push(
93-
Template.asString([
94-
`${JSON.stringify(pair[0])}: {`,
95-
Template.indent([properties.join(",")]),
96-
"}"
97-
])
98-
);
9973
}
10074

101-
if (waitForPromises.size > 0) {
102-
const promises = Array.from(waitForPromises.values()).join(", ");
75+
if (waitForInstances.size === 1) {
76+
const moduleId = Array.from(waitForInstances.values())[0];
77+
const promise = `installedWasmModules[${JSON.stringify(moduleId)}]`;
78+
const variable = Array.from(waitForInstances.keys())[0];
79+
return Template.asString([
80+
`${JSON.stringify(module.id)}: function() {`,
81+
Template.indent([
82+
`return promiseResolve().then(function() { return ${promise}; }).then(function(${variable}) {`,
83+
Template.indent([
84+
"return {",
85+
Template.indent([properties.join(",\n")]),
86+
"};"
87+
]),
88+
"});"
89+
]),
90+
"},"
91+
]);
92+
} else if (waitForInstances.size > 0) {
93+
const promises = Array.from(
94+
waitForInstances.values(),
95+
id => `installedWasmModules[${JSON.stringify(id)}]`
96+
).join(", ");
10397
const variables = Array.from(
104-
waitForPromises.keys(),
105-
(name, i) => `var ${name} = array[${i}];`
106-
).join("\n");
98+
waitForInstances.keys(),
99+
(name, i) => `${name} = array[${i}];`
100+
).join(", ");
107101
return Template.asString([
108102
`${JSON.stringify(module.id)}: function() {`,
109103
Template.indent([
110-
`return Promise.resolve().then(function() { return Promise.all([${promises}]); }).then(function(array) {`,
104+
`return promiseResolve().then(function() { return Promise.all([${promises}]); }).then(function(array) {`,
111105
Template.indent([
112-
variables,
106+
`var ${variables};`,
113107
"return {",
114-
Template.indent([importsCode.join(",")]),
108+
Template.indent([properties.join(",\n")]),
115109
"};"
116110
]),
117111
"});"
@@ -123,7 +117,7 @@ function generateImportObject(module) {
123117
`${JSON.stringify(module.id)}: function() {`,
124118
Template.indent([
125119
"return {",
126-
Template.indent([importsCode.join(",")]),
120+
Template.indent([properties.join(",\n")]),
127121
"};"
128122
]),
129123
"},"
@@ -149,6 +143,12 @@ class WasmMainTemplatePlugin {
149143
"// object to store loaded and loading wasm modules",
150144
"var installedWasmModules = {};",
151145
"",
146+
// This function is used to delay reading the installed wasm module promises
147+
// by a microtask. Sorting them doesn't help because there are egdecases where
148+
// sorting is not possible (modules splitted into different chunks).
149+
// So we not even trying and solve this by a microtask delay.
150+
"function promiseResolve() { return Promise.resolve(); }",
151+
"",
152152
"var wasmImportObjects = {",
153153
Template.indent(importObjects),
154154
"};"
@@ -218,13 +218,15 @@ class WasmMainTemplatePlugin {
218218
Template.indent([
219219
"promise = Promise.all([WebAssembly.compileStreaming(req), importObject]).then(function(items) {",
220220
Template.indent([
221-
"return WebAssembly.instantiate(items[0], items[1]);"
221+
"return WebAssembly.instantiate(items[0], " +
222+
`{ ${WebAssemblyUtils.MANGLED_MODULE}: items[1] });`
222223
]),
223224
"});"
224225
]),
225226
"} else if(typeof WebAssembly.instantiateStreaming === 'function') {",
226227
Template.indent([
227-
"promise = WebAssembly.instantiateStreaming(req, importObject);"
228+
"promise = WebAssembly.instantiateStreaming(req, " +
229+
`{ ${WebAssemblyUtils.MANGLED_MODULE}: importObject });`
228230
])
229231
])
230232
: Template.asString([
@@ -238,7 +240,8 @@ class WasmMainTemplatePlugin {
238240
]),
239241
"]).then(function(items) {",
240242
Template.indent([
241-
"return WebAssembly.instantiate(items[0], items[1]);"
243+
"return WebAssembly.instantiate(items[0], " +
244+
`{ ${WebAssemblyUtils.MANGLED_MODULE}: items[1] });`
242245
]),
243246
"});"
244247
])
@@ -248,7 +251,8 @@ class WasmMainTemplatePlugin {
248251
"var bytesPromise = req.then(function(x) { return x.arrayBuffer(); });",
249252
"promise = bytesPromise.then(function(bytes) {",
250253
Template.indent([
251-
"return WebAssembly.instantiate(bytes, importObject);"
254+
"return WebAssembly.instantiate(bytes, " +
255+
`{ ${WebAssemblyUtils.MANGLED_MODULE}: importObject });`
252256
]),
253257
"});"
254258
]),
@@ -257,7 +261,7 @@ class WasmMainTemplatePlugin {
257261
Template.indent([
258262
`return ${
259263
mainTemplate.requireFn
260-
}.w[wasmModuleId] = res.instance || res;`
264+
}.w[wasmModuleId] = (res.instance || res).exports;`
261265
]),
262266
"}));"
263267
]),
@@ -275,7 +279,7 @@ class WasmMainTemplatePlugin {
275279
return Template.asString([
276280
source,
277281
"",
278-
"// object with all WebAssembly.instance",
282+
"// object with all WebAssembly.instance exports",
279283
`${mainTemplate.requireFn}.w = {};`
280284
]);
281285
}

0 commit comments

Comments
 (0)