UNPKG

11.6 kBJavaScriptView Raw
1/*
2 MIT License http://www.opensource.org/licenses/mit-license.php
3 Author Tobias Koppers @sokra
4*/
5"use strict";
6
7const Generator = require("../Generator");
8const Template = require("../Template");
9const WebAssemblyUtils = require("./WebAssemblyUtils");
10const { RawSource } = require("webpack-sources");
11
12const { editWithAST, addWithAST } = require("@webassemblyjs/wasm-edit");
13const { decode } = require("@webassemblyjs/wasm-parser");
14const t = require("@webassemblyjs/ast");
15const {
16 moduleContextFromModuleAST
17} = require("@webassemblyjs/helper-module-context");
18
19const WebAssemblyExportImportedDependency = require("../dependencies/WebAssemblyExportImportedDependency");
20
21/** @typedef {import("../Module")} Module */
22/** @typedef {import("./WebAssemblyUtils").UsedWasmDependency} UsedWasmDependency */
23/** @typedef {import("../NormalModule")} NormalModule */
24/** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */
25/** @typedef {import("webpack-sources").Source} Source */
26/** @typedef {import("../Dependency").DependencyTemplate} DependencyTemplate */
27
28/**
29 * @typedef {(ArrayBuffer) => ArrayBuffer} ArrayBufferTransform
30 */
31
32/**
33 * @template T
34 * @param {Function[]} fns transforms
35 * @returns {Function} composed transform
36 */
37const compose = (...fns) => {
38 return fns.reduce(
39 (prevFn, nextFn) => {
40 return value => nextFn(prevFn(value));
41 },
42 value => value
43 );
44};
45
46// TODO replace with @callback
47
48/**
49 * Removes the start instruction
50 *
51 * @param {Object} state - unused state
52 * @returns {ArrayBufferTransform} transform
53 */
54const removeStartFunc = state => bin => {
55 return editWithAST(state.ast, bin, {
56 Start(path) {
57 path.remove();
58 }
59 });
60};
61
62/**
63 * Get imported globals
64 *
65 * @param {Object} ast - Module's AST
66 * @returns {Array<t.ModuleImport>} - nodes
67 */
68const getImportedGlobals = ast => {
69 const importedGlobals = [];
70
71 t.traverse(ast, {
72 ModuleImport({ node }) {
73 if (t.isGlobalType(node.descr) === true) {
74 importedGlobals.push(node);
75 }
76 }
77 });
78
79 return importedGlobals;
80};
81
82const getCountImportedFunc = ast => {
83 let count = 0;
84
85 t.traverse(ast, {
86 ModuleImport({ node }) {
87 if (t.isFuncImportDescr(node.descr) === true) {
88 count++;
89 }
90 }
91 });
92
93 return count;
94};
95
96/**
97 * Get next type index
98 *
99 * @param {Object} ast - Module's AST
100 * @returns {t.Index} - index
101 */
102const getNextTypeIndex = ast => {
103 const typeSectionMetadata = t.getSectionMetadata(ast, "type");
104
105 if (typeSectionMetadata === undefined) {
106 return t.indexLiteral(0);
107 }
108
109 return t.indexLiteral(typeSectionMetadata.vectorOfSize.value);
110};
111
112/**
113 * Get next func index
114 *
115 * The Func section metadata provide informations for implemented funcs
116 * in order to have the correct index we shift the index by number of external
117 * functions.
118 *
119 * @param {Object} ast - Module's AST
120 * @param {Number} countImportedFunc - number of imported funcs
121 * @returns {t.Index} - index
122 */
123const getNextFuncIndex = (ast, countImportedFunc) => {
124 const funcSectionMetadata = t.getSectionMetadata(ast, "func");
125
126 if (funcSectionMetadata === undefined) {
127 return t.indexLiteral(0 + countImportedFunc);
128 }
129
130 const vectorOfSize = funcSectionMetadata.vectorOfSize.value;
131
132 return t.indexLiteral(vectorOfSize + countImportedFunc);
133};
134
135/**
136 * Create a init instruction for a global
137 * @param {t.GlobalType} globalType the global type
138 * @returns {t.Instruction} init expression
139 */
140const createDefaultInitForGlobal = globalType => {
141 if (globalType.valtype[0] === "i") {
142 // create NumberLiteral global initializer
143 return t.objectInstruction("const", globalType.valtype, [
144 t.numberLiteralFromRaw(66)
145 ]);
146 } else if (globalType.valtype[0] === "f") {
147 // create FloatLiteral global initializer
148 return t.objectInstruction("const", globalType.valtype, [
149 t.floatLiteral(66, false, false, "66")
150 ]);
151 } else {
152 throw new Error("unknown type: " + globalType.valtype);
153 }
154};
155
156/**
157 * Rewrite the import globals:
158 * - removes the ModuleImport instruction
159 * - injects at the same offset a mutable global of the same time
160 *
161 * Since the imported globals are before the other global declarations, our
162 * indices will be preserved.
163 *
164 * Note that globals will become mutable.
165 *
166 * @param {Object} state - unused state
167 * @returns {ArrayBufferTransform} transform
168 */
169const rewriteImportedGlobals = state => bin => {
170 const additionalInitCode = state.additionalInitCode;
171 const newGlobals = [];
172
173 bin = editWithAST(state.ast, bin, {
174 ModuleImport(path) {
175 if (t.isGlobalType(path.node.descr) === true) {
176 const globalType = path.node.descr;
177
178 globalType.mutability = "var";
179
180 const init = createDefaultInitForGlobal(globalType);
181
182 newGlobals.push(t.global(globalType, [init]));
183
184 path.remove();
185 }
186 },
187
188 // in order to preserve non-imported global's order we need to re-inject
189 // those as well
190 Global(path) {
191 const { node } = path;
192 const [init] = node.init;
193
194 if (init.id === "get_global") {
195 node.globalType.mutability = "var";
196
197 const initialGlobalidx = init.args[0];
198
199 node.init = [createDefaultInitForGlobal(node.globalType)];
200
201 additionalInitCode.push(
202 /**
203 * get_global in global initilizer only work for imported globals.
204 * They have the same indices than the init params, so use the
205 * same index.
206 */
207 t.instruction("get_local", [initialGlobalidx]),
208 t.instruction("set_global", [t.indexLiteral(newGlobals.length)])
209 );
210 }
211
212 newGlobals.push(node);
213
214 path.remove();
215 }
216 });
217
218 // Add global declaration instructions
219 return addWithAST(state.ast, bin, newGlobals);
220};
221
222/**
223 * Rewrite the export names
224 * @param {Object} state state
225 * @param {Object} state.ast Module's ast
226 * @param {Module} state.module Module
227 * @param {Set<string>} state.externalExports Module
228 * @returns {ArrayBufferTransform} transform
229 */
230const rewriteExportNames = ({ ast, module, externalExports }) => bin => {
231 return editWithAST(ast, bin, {
232 ModuleExport(path) {
233 const isExternal = externalExports.has(path.node.name);
234 if (isExternal) {
235 path.remove();
236 return;
237 }
238 const usedName = module.isUsed(path.node.name);
239 if (!usedName) {
240 path.remove();
241 return;
242 }
243 path.node.name = usedName;
244 }
245 });
246};
247
248/**
249 * Mangle import names and modules
250 * @param {Object} state state
251 * @param {Object} state.ast Module's ast
252 * @param {Map<string, UsedWasmDependency>} state.usedDependencyMap mappings to mangle names
253 * @returns {ArrayBufferTransform} transform
254 */
255const rewriteImports = ({ ast, usedDependencyMap }) => bin => {
256 return editWithAST(ast, bin, {
257 ModuleImport(path) {
258 const result = usedDependencyMap.get(
259 path.node.module + ":" + path.node.name
260 );
261
262 if (result !== undefined) {
263 path.node.module = result.module;
264 path.node.name = result.name;
265 }
266 }
267 });
268};
269
270/**
271 * Add an init function.
272 *
273 * The init function fills the globals given input arguments.
274 *
275 * @param {Object} state transformation state
276 * @param {Object} state.ast - Module's ast
277 * @param {t.Identifier} state.initFuncId identifier of the init function
278 * @param {t.Index} state.startAtFuncOffset index of the start function
279 * @param {t.ModuleImport[]} state.importedGlobals list of imported globals
280 * @param {t.Instruction[]} state.additionalInitCode list of addition instructions for the init function
281 * @param {t.Index} state.nextFuncIndex index of the next function
282 * @param {t.Index} state.nextTypeIndex index of the next type
283 * @returns {ArrayBufferTransform} transform
284 */
285const addInitFunction = ({
286 ast,
287 initFuncId,
288 startAtFuncOffset,
289 importedGlobals,
290 additionalInitCode,
291 nextFuncIndex,
292 nextTypeIndex
293}) => bin => {
294 const funcParams = importedGlobals.map(importedGlobal => {
295 // used for debugging
296 const id = t.identifier(`${importedGlobal.module}.${importedGlobal.name}`);
297
298 return t.funcParam(importedGlobal.descr.valtype, id);
299 });
300
301 const funcBody = importedGlobals.reduce((acc, importedGlobal, index) => {
302 const args = [t.indexLiteral(index)];
303 const body = [
304 t.instruction("get_local", args),
305 t.instruction("set_global", args)
306 ];
307
308 return [...acc, ...body];
309 }, []);
310
311 if (typeof startAtFuncOffset === "number") {
312 funcBody.push(t.callInstruction(t.numberLiteralFromRaw(startAtFuncOffset)));
313 }
314
315 for (const instr of additionalInitCode) {
316 funcBody.push(instr);
317 }
318
319 const funcResults = [];
320
321 // Code section
322 const funcSignature = t.signature(funcParams, funcResults);
323 const func = t.func(initFuncId, funcSignature, funcBody);
324
325 // Type section
326 const functype = t.typeInstruction(undefined, funcSignature);
327
328 // Func section
329 const funcindex = t.indexInFuncSection(nextTypeIndex);
330
331 // Export section
332 const moduleExport = t.moduleExport(
333 initFuncId.value,
334 t.moduleExportDescr("Func", nextFuncIndex)
335 );
336
337 return addWithAST(ast, bin, [func, moduleExport, funcindex, functype]);
338};
339
340/**
341 * Extract mangle mappings from module
342 * @param {Module} module current module
343 * @param {boolean} mangle mangle imports
344 * @returns {Map<string, UsedWasmDependency>} mappings to mangled names
345 */
346const getUsedDependencyMap = (module, mangle) => {
347 /** @type {Map<string, UsedWasmDependency>} */
348 const map = new Map();
349 for (const usedDep of WebAssemblyUtils.getUsedDependencies(module, mangle)) {
350 const dep = usedDep.dependency;
351 const request = dep.request;
352 const exportName = dep.name;
353 map.set(request + ":" + exportName, usedDep);
354 }
355 return map;
356};
357
358class WebAssemblyGenerator extends Generator {
359 constructor(options) {
360 super();
361 this.options = options;
362 }
363
364 /**
365 * @param {NormalModule} module module for which the code should be generated
366 * @param {Map<Function, DependencyTemplate>} dependencyTemplates mapping from dependencies to templates
367 * @param {RuntimeTemplate} runtimeTemplate the runtime template
368 * @param {string} type which kind of code should be generated
369 * @returns {Source} generated code
370 */
371 generate(module, dependencyTemplates, runtimeTemplate, type) {
372 let bin = module.originalSource().source();
373
374 const initFuncId = t.identifier(
375 Array.isArray(module.usedExports)
376 ? Template.numberToIdentifer(module.usedExports.length)
377 : "__webpack_init__"
378 );
379
380 // parse it
381 const ast = decode(bin, {
382 ignoreDataSection: true,
383 ignoreCodeSection: true,
384 ignoreCustomNameSection: true
385 });
386
387 const moduleContext = moduleContextFromModuleAST(ast.body[0]);
388
389 const importedGlobals = getImportedGlobals(ast);
390 const countImportedFunc = getCountImportedFunc(ast);
391 const startAtFuncOffset = moduleContext.getStart();
392 const nextFuncIndex = getNextFuncIndex(ast, countImportedFunc);
393 const nextTypeIndex = getNextTypeIndex(ast);
394
395 const usedDependencyMap = getUsedDependencyMap(
396 module,
397 this.options.mangleImports
398 );
399 const externalExports = new Set(
400 module.dependencies
401 .filter(d => d instanceof WebAssemblyExportImportedDependency)
402 .map(d => {
403 const wasmDep = /** @type {WebAssemblyExportImportedDependency} */ (d);
404 return wasmDep.exportName;
405 })
406 );
407
408 /** @type {t.Instruction[]} */
409 const additionalInitCode = [];
410
411 const transform = compose(
412 rewriteExportNames({
413 ast,
414 module,
415 externalExports
416 }),
417
418 removeStartFunc({ ast }),
419
420 rewriteImportedGlobals({ ast, additionalInitCode }),
421
422 rewriteImports({
423 ast,
424 usedDependencyMap
425 }),
426
427 addInitFunction({
428 ast,
429 initFuncId,
430 importedGlobals,
431 additionalInitCode,
432 startAtFuncOffset,
433 nextFuncIndex,
434 nextTypeIndex
435 })
436 );
437
438 const newBin = transform(bin);
439
440 return new RawSource(newBin);
441 }
442}
443
444module.exports = WebAssemblyGenerator;