diff options
Diffstat (limited to 'node_modules/webpack/lib/wasm')
9 files changed, 1487 insertions, 0 deletions
diff --git a/node_modules/webpack/lib/wasm/UnsupportedWebAssemblyFeatureError.js b/node_modules/webpack/lib/wasm/UnsupportedWebAssemblyFeatureError.js new file mode 100644 index 0000000..fede4eb --- /dev/null +++ b/node_modules/webpack/lib/wasm/UnsupportedWebAssemblyFeatureError.js @@ -0,0 +1,17 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php +*/ +"use strict"; + +const WebpackError = require("../WebpackError"); + +module.exports = class UnsupportedWebAssemblyFeatureError extends WebpackError { + /** @param {string} message Error message */ + constructor(message) { + super(message); + this.name = "UnsupportedWebAssemblyFeatureError"; + this.hideStack = true; + + Error.captureStackTrace(this, this.constructor); + } +}; diff --git a/node_modules/webpack/lib/wasm/WasmFinalizeExportsPlugin.js b/node_modules/webpack/lib/wasm/WasmFinalizeExportsPlugin.js new file mode 100644 index 0000000..52df869 --- /dev/null +++ b/node_modules/webpack/lib/wasm/WasmFinalizeExportsPlugin.js @@ -0,0 +1,69 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra + */ +"use strict"; + +const UnsupportedWebAssemblyFeatureError = require("./UnsupportedWebAssemblyFeatureError"); + +class WasmFinalizeExportsPlugin { + apply(compiler) { + compiler.hooks.compilation.tap("WasmFinalizeExportsPlugin", compilation => { + compilation.hooks.finishModules.tap( + "WasmFinalizeExportsPlugin", + modules => { + for (const module of modules) { + // 1. if a WebAssembly module + if (module.type.startsWith("webassembly") === true) { + const jsIncompatibleExports = + module.buildMeta.jsIncompatibleExports; + + if (jsIncompatibleExports === undefined) { + continue; + } + + for (const reason of module.reasons) { + // 2. is referenced by a non-WebAssembly module + if (reason.module.type.startsWith("webassembly") === false) { + const ref = compilation.getDependencyReference( + reason.module, + reason.dependency + ); + + if (!ref) continue; + + const importedNames = ref.importedNames; + + if (Array.isArray(importedNames)) { + importedNames.forEach(name => { + // 3. and uses a func with an incompatible JS signature + if ( + Object.prototype.hasOwnProperty.call( + jsIncompatibleExports, + name + ) + ) { + // 4. error + /** @type {any} */ + const error = new UnsupportedWebAssemblyFeatureError( + `Export "${name}" with ${jsIncompatibleExports[name]} can only be used for direct wasm to wasm dependencies` + ); + error.module = module; + error.origin = reason.module; + error.originLoc = reason.dependency.loc; + error.dependencies = [reason.dependency]; + compilation.errors.push(error); + } + }); + } + } + } + } + } + } + ); + }); + } +} + +module.exports = WasmFinalizeExportsPlugin; diff --git a/node_modules/webpack/lib/wasm/WasmMainTemplatePlugin.js b/node_modules/webpack/lib/wasm/WasmMainTemplatePlugin.js new file mode 100644 index 0000000..494c8ec --- /dev/null +++ b/node_modules/webpack/lib/wasm/WasmMainTemplatePlugin.js @@ -0,0 +1,341 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ +"use strict"; + +const Template = require("../Template"); +const WebAssemblyUtils = require("./WebAssemblyUtils"); + +/** @typedef {import("../Module")} Module */ +/** @typedef {import("../MainTemplate")} MainTemplate */ + +// Get all wasm modules +const getAllWasmModules = chunk => { + const wasmModules = chunk.getAllAsyncChunks(); + const array = []; + for (const chunk of wasmModules) { + for (const m of chunk.modulesIterable) { + if (m.type.startsWith("webassembly")) { + array.push(m); + } + } + } + + return array; +}; + +/** + * generates the import object function for a module + * @param {Module} module the module + * @param {boolean} mangle mangle imports + * @returns {string} source code + */ +const generateImportObject = (module, mangle) => { + const waitForInstances = new Map(); + const properties = []; + const usedWasmDependencies = WebAssemblyUtils.getUsedDependencies( + module, + mangle + ); + 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 module = usedDep.module; + const name = usedDep.name; + + if (direct) { + const instanceVar = `m${waitForInstances.size}`; + waitForInstances.set(instanceVar, importedModule.id); + properties.push({ + module, + name, + value: `${instanceVar}[${JSON.stringify(usedName)}]` + }); + } else { + const params = description.signature.params.map( + (param, k) => "p" + k + param.valtype + ); + + const mod = `installedModules[${JSON.stringify(importedModule.id)}]`; + const func = `${mod}.exports[${JSON.stringify(usedName)}]`; + + properties.push({ + module, + name, + value: Template.asString([ + (importedModule.type.startsWith("webassembly") + ? `${mod} ? ${func} : ` + : "") + `function(${params}) {`, + Template.indent([`return ${func}(${params});`]), + "}" + ]) + }); + } + } + + let importObject; + if (mangle) { + importObject = [ + "return {", + Template.indent([ + properties.map(p => `${JSON.stringify(p.name)}: ${p.value}`).join(",\n") + ]), + "};" + ]; + } else { + const propertiesByModule = new Map(); + for (const p of properties) { + let list = propertiesByModule.get(p.module); + if (list === undefined) { + propertiesByModule.set(p.module, (list = [])); + } + list.push(p); + } + importObject = [ + "return {", + Template.indent([ + Array.from(propertiesByModule, ([module, list]) => { + return Template.asString([ + `${JSON.stringify(module)}: {`, + Template.indent([ + list.map(p => `${JSON.stringify(p.name)}: ${p.value}`).join(",\n") + ]), + "}" + ]); + }).join(",\n") + ]), + "};" + ]; + } + + 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(importObject), + "});" + ]), + "}," + ]); + } else if (waitForInstances.size > 0) { + const promises = Array.from( + waitForInstances.values(), + id => `installedWasmModules[${JSON.stringify(id)}]` + ).join(", "); + const variables = Array.from( + waitForInstances.keys(), + (name, i) => `${name} = array[${i}]` + ).join(", "); + return Template.asString([ + `${JSON.stringify(module.id)}: function() {`, + Template.indent([ + `return promiseResolve().then(function() { return Promise.all([${promises}]); }).then(function(array) {`, + Template.indent([`var ${variables};`, ...importObject]), + "});" + ]), + "}," + ]); + } else { + return Template.asString([ + `${JSON.stringify(module.id)}: function() {`, + Template.indent(importObject), + "}," + ]); + } +}; + +class WasmMainTemplatePlugin { + constructor({ generateLoadBinaryCode, supportsStreaming, mangleImports }) { + this.generateLoadBinaryCode = generateLoadBinaryCode; + this.supportsStreaming = supportsStreaming; + this.mangleImports = mangleImports; + } + + /** + * @param {MainTemplate} mainTemplate main template + * @returns {void} + */ + apply(mainTemplate) { + mainTemplate.hooks.localVars.tap( + "WasmMainTemplatePlugin", + (source, chunk) => { + const wasmModules = getAllWasmModules(chunk); + if (wasmModules.length === 0) return source; + const importObjects = wasmModules.map(module => { + return generateImportObject(module, this.mangleImports); + }); + return Template.asString([ + source, + "", + "// 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), + "};" + ]); + } + ); + mainTemplate.hooks.requireEnsure.tap( + "WasmMainTemplatePlugin", + (source, chunk, hash) => { + const webassemblyModuleFilename = + mainTemplate.outputOptions.webassemblyModuleFilename; + + const chunkModuleMaps = chunk.getChunkModuleMaps(m => + m.type.startsWith("webassembly") + ); + if (Object.keys(chunkModuleMaps.id).length === 0) return source; + const wasmModuleSrcPath = mainTemplate.getAssetPath( + JSON.stringify(webassemblyModuleFilename), + { + hash: `" + ${mainTemplate.renderCurrentHashCode(hash)} + "`, + hashWithLength: length => + `" + ${mainTemplate.renderCurrentHashCode(hash, length)} + "`, + module: { + id: '" + wasmModuleId + "', + hash: `" + ${JSON.stringify( + chunkModuleMaps.hash + )}[wasmModuleId] + "`, + hashWithLength(length) { + const shortChunkHashMap = Object.create(null); + for (const wasmModuleId of Object.keys(chunkModuleMaps.hash)) { + if (typeof chunkModuleMaps.hash[wasmModuleId] === "string") { + shortChunkHashMap[wasmModuleId] = chunkModuleMaps.hash[ + wasmModuleId + ].substr(0, length); + } + } + return `" + ${JSON.stringify( + shortChunkHashMap + )}[wasmModuleId] + "`; + } + } + } + ); + const createImportObject = content => + this.mangleImports + ? `{ ${JSON.stringify( + WebAssemblyUtils.MANGLED_MODULE + )}: ${content} }` + : content; + return Template.asString([ + source, + "", + "// Fetch + compile chunk loading for webassembly", + "", + `var wasmModules = ${JSON.stringify( + chunkModuleMaps.id + )}[chunkId] || [];`, + "", + "wasmModules.forEach(function(wasmModuleId) {", + Template.indent([ + "var installedWasmModuleData = installedWasmModules[wasmModuleId];", + "", + '// a Promise means "currently loading" or "already loaded".', + "if(installedWasmModuleData)", + Template.indent(["promises.push(installedWasmModuleData);"]), + "else {", + Template.indent([ + `var importObject = wasmImportObjects[wasmModuleId]();`, + `var req = ${this.generateLoadBinaryCode(wasmModuleSrcPath)};`, + "var promise;", + this.supportsStreaming + ? Template.asString([ + "if(importObject instanceof Promise && typeof WebAssembly.compileStreaming === 'function') {", + Template.indent([ + "promise = Promise.all([WebAssembly.compileStreaming(req), importObject]).then(function(items) {", + Template.indent([ + `return WebAssembly.instantiate(items[0], ${createImportObject( + "items[1]" + )});` + ]), + "});" + ]), + "} else if(typeof WebAssembly.instantiateStreaming === 'function') {", + Template.indent([ + `promise = WebAssembly.instantiateStreaming(req, ${createImportObject( + "importObject" + )});` + ]) + ]) + : Template.asString([ + "if(importObject instanceof Promise) {", + Template.indent([ + "var bytesPromise = req.then(function(x) { return x.arrayBuffer(); });", + "promise = Promise.all([", + Template.indent([ + "bytesPromise.then(function(bytes) { return WebAssembly.compile(bytes); }),", + "importObject" + ]), + "]).then(function(items) {", + Template.indent([ + `return WebAssembly.instantiate(items[0], ${createImportObject( + "items[1]" + )});` + ]), + "});" + ]) + ]), + "} else {", + Template.indent([ + "var bytesPromise = req.then(function(x) { return x.arrayBuffer(); });", + "promise = bytesPromise.then(function(bytes) {", + Template.indent([ + `return WebAssembly.instantiate(bytes, ${createImportObject( + "importObject" + )});` + ]), + "});" + ]), + "}", + "promises.push(installedWasmModules[wasmModuleId] = promise.then(function(res) {", + Template.indent([ + `return ${mainTemplate.requireFn}.w[wasmModuleId] = (res.instance || res).exports;` + ]), + "}));" + ]), + "}" + ]), + "});" + ]); + } + ); + mainTemplate.hooks.requireExtensions.tap( + "WasmMainTemplatePlugin", + (source, chunk) => { + if (!chunk.hasModuleInGraph(m => m.type.startsWith("webassembly"))) { + return source; + } + return Template.asString([ + source, + "", + "// object with all WebAssembly.instance exports", + `${mainTemplate.requireFn}.w = {};` + ]); + } + ); + mainTemplate.hooks.hash.tap("WasmMainTemplatePlugin", hash => { + hash.update("WasmMainTemplatePlugin"); + hash.update("2"); + }); + } +} + +module.exports = WasmMainTemplatePlugin; diff --git a/node_modules/webpack/lib/wasm/WebAssemblyGenerator.js b/node_modules/webpack/lib/wasm/WebAssemblyGenerator.js new file mode 100644 index 0000000..dbee5bd --- /dev/null +++ b/node_modules/webpack/lib/wasm/WebAssemblyGenerator.js @@ -0,0 +1,458 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ +"use strict"; + +const Generator = require("../Generator"); +const Template = require("../Template"); +const WebAssemblyUtils = require("./WebAssemblyUtils"); +const { RawSource } = require("webpack-sources"); + +const { editWithAST, addWithAST } = require("@webassemblyjs/wasm-edit"); +const { decode } = require("@webassemblyjs/wasm-parser"); +const t = require("@webassemblyjs/ast"); +const { + moduleContextFromModuleAST +} = require("@webassemblyjs/helper-module-context"); + +const WebAssemblyExportImportedDependency = require("../dependencies/WebAssemblyExportImportedDependency"); + +/** @typedef {import("../Module")} Module */ +/** @typedef {import("./WebAssemblyUtils").UsedWasmDependency} UsedWasmDependency */ +/** @typedef {import("../NormalModule")} NormalModule */ +/** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */ +/** @typedef {import("webpack-sources").Source} Source */ +/** @typedef {import("../Dependency").DependencyTemplate} DependencyTemplate */ + +/** + * @typedef {(ArrayBuffer) => ArrayBuffer} ArrayBufferTransform + */ + +/** + * @template T + * @param {Function[]} fns transforms + * @returns {Function} composed transform + */ +const compose = (...fns) => { + return fns.reduce( + (prevFn, nextFn) => { + return value => nextFn(prevFn(value)); + }, + value => value + ); +}; + +// TODO replace with @callback + +/** + * Removes the start instruction + * + * @param {Object} state unused state + * @returns {ArrayBufferTransform} transform + */ +const removeStartFunc = state => bin => { + return editWithAST(state.ast, bin, { + Start(path) { + path.remove(); + } + }); +}; + +/** + * Get imported globals + * + * @param {Object} ast Module's AST + * @returns {Array<t.ModuleImport>} - nodes + */ +const getImportedGlobals = ast => { + const importedGlobals = []; + + t.traverse(ast, { + ModuleImport({ node }) { + if (t.isGlobalType(node.descr)) { + importedGlobals.push(node); + } + } + }); + + return importedGlobals; +}; + +/** + * Get the count for imported func + * + * @param {Object} ast Module's AST + * @returns {Number} - count + */ +const getCountImportedFunc = ast => { + let count = 0; + + t.traverse(ast, { + ModuleImport({ node }) { + if (t.isFuncImportDescr(node.descr)) { + count++; + } + } + }); + + return count; +}; + +/** + * Get next type index + * + * @param {Object} ast Module's AST + * @returns {t.Index} - index + */ +const getNextTypeIndex = ast => { + const typeSectionMetadata = t.getSectionMetadata(ast, "type"); + + if (typeSectionMetadata === undefined) { + return t.indexLiteral(0); + } + + return t.indexLiteral(typeSectionMetadata.vectorOfSize.value); +}; + +/** + * Get next func index + * + * The Func section metadata provide informations for implemented funcs + * in order to have the correct index we shift the index by number of external + * functions. + * + * @param {Object} ast Module's AST + * @param {Number} countImportedFunc number of imported funcs + * @returns {t.Index} - index + */ +const getNextFuncIndex = (ast, countImportedFunc) => { + const funcSectionMetadata = t.getSectionMetadata(ast, "func"); + + if (funcSectionMetadata === undefined) { + return t.indexLiteral(0 + countImportedFunc); + } + + const vectorOfSize = funcSectionMetadata.vectorOfSize.value; + + return t.indexLiteral(vectorOfSize + countImportedFunc); +}; + +/** + * Creates an init instruction for a global type + * @param {t.GlobalType} globalType the global type + * @returns {t.Instruction} init expression + */ +const createDefaultInitForGlobal = globalType => { + if (globalType.valtype[0] === "i") { + // create NumberLiteral global initializer + return t.objectInstruction("const", globalType.valtype, [ + t.numberLiteralFromRaw(66) + ]); + } else if (globalType.valtype[0] === "f") { + // create FloatLiteral global initializer + return t.objectInstruction("const", globalType.valtype, [ + t.floatLiteral(66, false, false, "66") + ]); + } else { + throw new Error("unknown type: " + globalType.valtype); + } +}; + +/** + * Rewrite the import globals: + * - removes the ModuleImport instruction + * - injects at the same offset a mutable global of the same type + * + * Since the imported globals are before the other global declarations, our + * indices will be preserved. + * + * Note that globals will become mutable. + * + * @param {Object} state unused state + * @returns {ArrayBufferTransform} transform + */ +const rewriteImportedGlobals = state => bin => { + const additionalInitCode = state.additionalInitCode; + const newGlobals = []; + + bin = editWithAST(state.ast, bin, { + ModuleImport(path) { + if (t.isGlobalType(path.node.descr)) { + const globalType = path.node.descr; + + globalType.mutability = "var"; + + const init = [ + createDefaultInitForGlobal(globalType), + t.instruction("end") + ]; + + newGlobals.push(t.global(globalType, init)); + + path.remove(); + } + }, + + // in order to preserve non-imported global's order we need to re-inject + // those as well + Global(path) { + const { node } = path; + const [init] = node.init; + + if (init.id === "get_global") { + node.globalType.mutability = "var"; + + const initialGlobalidx = init.args[0]; + + node.init = [ + createDefaultInitForGlobal(node.globalType), + t.instruction("end") + ]; + + additionalInitCode.push( + /** + * get_global in global initializer only works for imported globals. + * They have the same indices as the init params, so use the + * same index. + */ + t.instruction("get_local", [initialGlobalidx]), + t.instruction("set_global", [t.indexLiteral(newGlobals.length)]) + ); + } + + newGlobals.push(node); + + path.remove(); + } + }); + + // Add global declaration instructions + return addWithAST(state.ast, bin, newGlobals); +}; + +/** + * Rewrite the export names + * @param {Object} state state + * @param {Object} state.ast Module's ast + * @param {Module} state.module Module + * @param {Set<string>} state.externalExports Module + * @returns {ArrayBufferTransform} transform + */ +const rewriteExportNames = ({ ast, module, externalExports }) => bin => { + return editWithAST(ast, bin, { + ModuleExport(path) { + const isExternal = externalExports.has(path.node.name); + if (isExternal) { + path.remove(); + return; + } + const usedName = module.isUsed(path.node.name); + if (!usedName) { + path.remove(); + return; + } + path.node.name = usedName; + } + }); +}; + +/** + * Mangle import names and modules + * @param {Object} state state + * @param {Object} state.ast Module's ast + * @param {Map<string, UsedWasmDependency>} state.usedDependencyMap mappings to mangle names + * @returns {ArrayBufferTransform} transform + */ +const rewriteImports = ({ ast, usedDependencyMap }) => bin => { + return editWithAST(ast, bin, { + ModuleImport(path) { + const result = usedDependencyMap.get( + path.node.module + ":" + path.node.name + ); + + if (result !== undefined) { + path.node.module = result.module; + path.node.name = result.name; + } + } + }); +}; + +/** + * Add an init function. + * + * The init function fills the globals given input arguments. + * + * @param {Object} state transformation state + * @param {Object} state.ast Module's ast + * @param {t.Identifier} state.initFuncId identifier of the init function + * @param {t.Index} state.startAtFuncOffset index of the start function + * @param {t.ModuleImport[]} state.importedGlobals list of imported globals + * @param {t.Instruction[]} state.additionalInitCode list of addition instructions for the init function + * @param {t.Index} state.nextFuncIndex index of the next function + * @param {t.Index} state.nextTypeIndex index of the next type + * @returns {ArrayBufferTransform} transform + */ +const addInitFunction = ({ + ast, + initFuncId, + startAtFuncOffset, + importedGlobals, + additionalInitCode, + nextFuncIndex, + nextTypeIndex +}) => bin => { + const funcParams = importedGlobals.map(importedGlobal => { + // used for debugging + const id = t.identifier(`${importedGlobal.module}.${importedGlobal.name}`); + + return t.funcParam(importedGlobal.descr.valtype, id); + }); + + const funcBody = importedGlobals.reduce((acc, importedGlobal, index) => { + const args = [t.indexLiteral(index)]; + const body = [ + t.instruction("get_local", args), + t.instruction("set_global", args) + ]; + + return [...acc, ...body]; + }, []); + + if (typeof startAtFuncOffset === "number") { + funcBody.push(t.callInstruction(t.numberLiteralFromRaw(startAtFuncOffset))); + } + + for (const instr of additionalInitCode) { + funcBody.push(instr); + } + + funcBody.push(t.instruction("end")); + + const funcResults = []; + + // Code section + const funcSignature = t.signature(funcParams, funcResults); + const func = t.func(initFuncId, funcSignature, funcBody); + + // Type section + const functype = t.typeInstruction(undefined, funcSignature); + + // Func section + const funcindex = t.indexInFuncSection(nextTypeIndex); + + // Export section + const moduleExport = t.moduleExport( + initFuncId.value, + t.moduleExportDescr("Func", nextFuncIndex) + ); + + return addWithAST(ast, bin, [func, moduleExport, funcindex, functype]); +}; + +/** + * Extract mangle mappings from module + * @param {Module} module current module + * @param {boolean} mangle mangle imports + * @returns {Map<string, UsedWasmDependency>} mappings to mangled names + */ +const getUsedDependencyMap = (module, mangle) => { + /** @type {Map<string, UsedWasmDependency>} */ + const map = new Map(); + for (const usedDep of WebAssemblyUtils.getUsedDependencies(module, mangle)) { + const dep = usedDep.dependency; + const request = dep.request; + const exportName = dep.name; + map.set(request + ":" + exportName, usedDep); + } + return map; +}; + +class WebAssemblyGenerator extends Generator { + constructor(options) { + super(); + this.options = options; + } + + /** + * @param {NormalModule} module module for which the code should be generated + * @param {Map<Function, DependencyTemplate>} dependencyTemplates mapping from dependencies to templates + * @param {RuntimeTemplate} runtimeTemplate the runtime template + * @param {string} type which kind of code should be generated + * @returns {Source} generated code + */ + generate(module, dependencyTemplates, runtimeTemplate, type) { + let bin = module.originalSource().source(); + + const initFuncId = t.identifier( + Array.isArray(module.usedExports) + ? Template.numberToIdentifer(module.usedExports.length) + : "__webpack_init__" + ); + + // parse it + const ast = decode(bin, { + ignoreDataSection: true, + ignoreCodeSection: true, + ignoreCustomNameSection: true + }); + + const moduleContext = moduleContextFromModuleAST(ast.body[0]); + + const importedGlobals = getImportedGlobals(ast); + const countImportedFunc = getCountImportedFunc(ast); + const startAtFuncOffset = moduleContext.getStart(); + const nextFuncIndex = getNextFuncIndex(ast, countImportedFunc); + const nextTypeIndex = getNextTypeIndex(ast); + + const usedDependencyMap = getUsedDependencyMap( + module, + this.options.mangleImports + ); + const externalExports = new Set( + module.dependencies + .filter(d => d instanceof WebAssemblyExportImportedDependency) + .map(d => { + const wasmDep = /** @type {WebAssemblyExportImportedDependency} */ (d); + return wasmDep.exportName; + }) + ); + + /** @type {t.Instruction[]} */ + const additionalInitCode = []; + + const transform = compose( + rewriteExportNames({ + ast, + module, + externalExports + }), + + removeStartFunc({ ast }), + + rewriteImportedGlobals({ ast, additionalInitCode }), + + rewriteImports({ + ast, + usedDependencyMap + }), + + addInitFunction({ + ast, + initFuncId, + importedGlobals, + additionalInitCode, + startAtFuncOffset, + nextFuncIndex, + nextTypeIndex + }) + ); + + const newBin = transform(bin); + + return new RawSource(newBin); + } +} + +module.exports = WebAssemblyGenerator; diff --git a/node_modules/webpack/lib/wasm/WebAssemblyInInitialChunkError.js b/node_modules/webpack/lib/wasm/WebAssemblyInInitialChunkError.js new file mode 100644 index 0000000..319e757 --- /dev/null +++ b/node_modules/webpack/lib/wasm/WebAssemblyInInitialChunkError.js @@ -0,0 +1,88 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php +*/ +"use strict"; + +const WebpackError = require("../WebpackError"); + +/** @typedef {import("../Module")} Module */ +/** @typedef {import("../RequestShortener")} RequestShortener */ + +/** + * @param {Module} module module to get chains from + * @param {RequestShortener} requestShortener to make readable identifiers + * @returns {string[]} all chains to the module + */ +const getInitialModuleChains = (module, requestShortener) => { + const queue = [ + { head: module, message: module.readableIdentifier(requestShortener) } + ]; + /** @type {Set<string>} */ + const results = new Set(); + /** @type {Set<string>} */ + const incompleteResults = new Set(); + /** @type {Set<Module>} */ + const visitedModules = new Set(); + + for (const chain of queue) { + const { head, message } = chain; + let final = true; + /** @type {Set<Module>} */ + const alreadyReferencedModules = new Set(); + for (const reason of head.reasons) { + const newHead = reason.module; + if (newHead) { + if (!newHead.getChunks().some(c => c.canBeInitial())) continue; + final = false; + if (alreadyReferencedModules.has(newHead)) continue; + alreadyReferencedModules.add(newHead); + const moduleName = newHead.readableIdentifier(requestShortener); + const detail = reason.explanation ? ` (${reason.explanation})` : ""; + const newMessage = `${moduleName}${detail} --> ${message}`; + if (visitedModules.has(newHead)) { + incompleteResults.add(`... --> ${newMessage}`); + continue; + } + visitedModules.add(newHead); + queue.push({ + head: newHead, + message: newMessage + }); + } else { + final = false; + const newMessage = reason.explanation + ? `(${reason.explanation}) --> ${message}` + : message; + results.add(newMessage); + } + } + if (final) { + results.add(message); + } + } + for (const result of incompleteResults) { + results.add(result); + } + return Array.from(results); +}; + +module.exports = class WebAssemblyInInitialChunkError extends WebpackError { + /** + * @param {Module} module WASM module + * @param {RequestShortener} requestShortener request shortener + */ + constructor(module, requestShortener) { + const moduleChains = getInitialModuleChains(module, requestShortener); + const message = `WebAssembly module is included in initial chunk. +This is not allowed, because WebAssembly download and compilation must happen asynchronous. +Add an async splitpoint (i. e. import()) somewhere between your entrypoint and the WebAssembly module: +${moduleChains.map(s => `* ${s}`).join("\n")}`; + + super(message); + this.name = "WebAssemblyInInitialChunkError"; + this.hideStack = true; + this.module = module; + + Error.captureStackTrace(this, this.constructor); + } +}; diff --git a/node_modules/webpack/lib/wasm/WebAssemblyJavascriptGenerator.js b/node_modules/webpack/lib/wasm/WebAssemblyJavascriptGenerator.js new file mode 100644 index 0000000..22dfb79 --- /dev/null +++ b/node_modules/webpack/lib/wasm/WebAssemblyJavascriptGenerator.js @@ -0,0 +1,152 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ +"use strict"; + +const Generator = require("../Generator"); +const Template = require("../Template"); +const { RawSource } = require("webpack-sources"); +const WebAssemblyImportDependency = require("../dependencies/WebAssemblyImportDependency"); +const WebAssemblyExportImportedDependency = require("../dependencies/WebAssemblyExportImportedDependency"); + +/** @typedef {import("../NormalModule")} NormalModule */ +/** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */ +/** @typedef {import("webpack-sources").Source} Source */ +/** @typedef {import("../Dependency").DependencyTemplate} DependencyTemplate */ + +class WebAssemblyJavascriptGenerator extends Generator { + /** + * @param {NormalModule} module module for which the code should be generated + * @param {Map<Function, DependencyTemplate>} dependencyTemplates mapping from dependencies to templates + * @param {RuntimeTemplate} runtimeTemplate the runtime template + * @param {string} type which kind of code should be generated + * @returns {Source} generated code + */ + generate(module, dependencyTemplates, runtimeTemplate, type) { + const initIdentifer = Array.isArray(module.usedExports) + ? Template.numberToIdentifer(module.usedExports.length) + : "__webpack_init__"; + + let needExportsCopy = false; + const importedModules = new Map(); + const initParams = []; + let index = 0; + for (const dep of module.dependencies) { + const depAsAny = /** @type {any} */ (dep); + if (dep.module) { + let importData = importedModules.get(dep.module); + if (importData === undefined) { + importedModules.set( + dep.module, + (importData = { + importVar: `m${index}`, + index, + request: + "userRequest" in depAsAny ? depAsAny.userRequest : undefined, + names: new Set(), + reexports: [] + }) + ); + index++; + } + if (dep instanceof WebAssemblyImportDependency) { + importData.names.add(dep.name); + if (dep.description.type === "GlobalType") { + const exportName = dep.name; + const usedName = dep.module && dep.module.isUsed(exportName); + + if (dep.module) { + if (usedName) { + initParams.push( + runtimeTemplate.exportFromImport({ + module: dep.module, + request: dep.request, + importVar: importData.importVar, + originModule: module, + exportName: dep.name, + asiSafe: true, + isCall: false, + callContext: null + }) + ); + } + } + } + } + if (dep instanceof WebAssemblyExportImportedDependency) { + importData.names.add(dep.name); + const usedName = module.isUsed(dep.exportName); + if (usedName) { + const exportProp = `${module.exportsArgument}[${JSON.stringify( + usedName + )}]`; + const defineStatement = Template.asString([ + `${exportProp} = ${runtimeTemplate.exportFromImport({ + module: dep.module, + request: dep.request, + importVar: importData.importVar, + originModule: module, + exportName: dep.name, + asiSafe: true, + isCall: false, + callContext: null + })};`, + `if(WebAssembly.Global) ${exportProp} = ` + + `new WebAssembly.Global({ value: ${JSON.stringify( + dep.valueType + )} }, ${exportProp});` + ]); + importData.reexports.push(defineStatement); + needExportsCopy = true; + } + } + } + } + const importsCode = Template.asString( + Array.from( + importedModules, + ([module, { importVar, request, reexports }]) => { + const importStatement = runtimeTemplate.importStatement({ + module, + request, + importVar, + originModule: module + }); + return importStatement + reexports.join("\n"); + } + ) + ); + + // create source + const source = new RawSource( + [ + '"use strict";', + "// Instantiate WebAssembly module", + "var wasmExports = __webpack_require__.w[module.i];", + + !Array.isArray(module.usedExports) + ? `__webpack_require__.r(${module.exportsArgument});` + : "", + + // this must be before import for circular dependencies + "// export exports from WebAssembly module", + Array.isArray(module.usedExports) && !needExportsCopy + ? `${module.moduleArgument}.exports = wasmExports;` + : "for(var name in wasmExports) " + + `if(name != ${JSON.stringify(initIdentifer)}) ` + + `${module.exportsArgument}[name] = wasmExports[name];`, + "// exec imports from WebAssembly module (for esm order)", + importsCode, + "", + "// exec wasm module", + `wasmExports[${JSON.stringify(initIdentifer)}](${initParams.join( + ", " + )})` + ].join("\n") + ); + return source; + } +} + +module.exports = WebAssemblyJavascriptGenerator; diff --git a/node_modules/webpack/lib/wasm/WebAssemblyModulesPlugin.js b/node_modules/webpack/lib/wasm/WebAssemblyModulesPlugin.js new file mode 100644 index 0000000..a4dd8a5 --- /dev/null +++ b/node_modules/webpack/lib/wasm/WebAssemblyModulesPlugin.js @@ -0,0 +1,128 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ +"use strict"; + +const Generator = require("../Generator"); +const WebAssemblyExportImportedDependency = require("../dependencies/WebAssemblyExportImportedDependency"); +const WebAssemblyImportDependency = require("../dependencies/WebAssemblyImportDependency"); +const WebAssemblyInInitialChunkError = require("./WebAssemblyInInitialChunkError"); + +/** @typedef {import("../Compiler")} Compiler */ + +let WebAssemblyGenerator; +let WebAssemblyJavascriptGenerator; +let WebAssemblyParser; + +class WebAssemblyModulesPlugin { + constructor(options) { + this.options = options; + } + + /** + * @param {Compiler} compiler compiler + * @returns {void} + */ + apply(compiler) { + compiler.hooks.compilation.tap( + "WebAssemblyModulesPlugin", + (compilation, { normalModuleFactory }) => { + compilation.dependencyFactories.set( + WebAssemblyImportDependency, + normalModuleFactory + ); + + compilation.dependencyFactories.set( + WebAssemblyExportImportedDependency, + normalModuleFactory + ); + + normalModuleFactory.hooks.createParser + .for("webassembly/experimental") + .tap("WebAssemblyModulesPlugin", () => { + if (WebAssemblyParser === undefined) { + WebAssemblyParser = require("./WebAssemblyParser"); + } + return new WebAssemblyParser(); + }); + + normalModuleFactory.hooks.createGenerator + .for("webassembly/experimental") + .tap("WebAssemblyModulesPlugin", () => { + if (WebAssemblyGenerator === undefined) { + WebAssemblyGenerator = require("./WebAssemblyGenerator"); + } + if (WebAssemblyJavascriptGenerator === undefined) { + WebAssemblyJavascriptGenerator = require("./WebAssemblyJavascriptGenerator"); + } + return Generator.byType({ + javascript: new WebAssemblyJavascriptGenerator(), + webassembly: new WebAssemblyGenerator(this.options) + }); + }); + + compilation.chunkTemplate.hooks.renderManifest.tap( + "WebAssemblyModulesPlugin", + (result, options) => { + const chunk = options.chunk; + const outputOptions = options.outputOptions; + const moduleTemplates = options.moduleTemplates; + const dependencyTemplates = options.dependencyTemplates; + + for (const module of chunk.modulesIterable) { + if (module.type && module.type.startsWith("webassembly")) { + const filenameTemplate = + outputOptions.webassemblyModuleFilename; + + result.push({ + render: () => + this.renderWebAssembly( + module, + moduleTemplates.webassembly, + dependencyTemplates + ), + filenameTemplate, + pathOptions: { + module + }, + identifier: `webassemblyModule${module.id}`, + hash: module.hash + }); + } + } + + return result; + } + ); + + compilation.hooks.afterChunks.tap("WebAssemblyModulesPlugin", () => { + const initialWasmModules = new Set(); + for (const chunk of compilation.chunks) { + if (chunk.canBeInitial()) { + for (const module of chunk.modulesIterable) { + if (module.type.startsWith("webassembly")) { + initialWasmModules.add(module); + } + } + } + } + for (const module of initialWasmModules) { + compilation.errors.push( + new WebAssemblyInInitialChunkError( + module, + compilation.requestShortener + ) + ); + } + }); + } + ); + } + + renderWebAssembly(module, moduleTemplate, dependencyTemplates) { + return moduleTemplate.render(module, dependencyTemplates, {}); + } +} + +module.exports = WebAssemblyModulesPlugin; diff --git a/node_modules/webpack/lib/wasm/WebAssemblyParser.js b/node_modules/webpack/lib/wasm/WebAssemblyParser.js new file mode 100644 index 0000000..eb49fb1 --- /dev/null +++ b/node_modules/webpack/lib/wasm/WebAssemblyParser.js @@ -0,0 +1,175 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ +"use strict"; + +const t = require("@webassemblyjs/ast"); +const { decode } = require("@webassemblyjs/wasm-parser"); +const { + moduleContextFromModuleAST +} = require("@webassemblyjs/helper-module-context"); + +const { Tapable } = require("tapable"); +const WebAssemblyImportDependency = require("../dependencies/WebAssemblyImportDependency"); +const WebAssemblyExportImportedDependency = require("../dependencies/WebAssemblyExportImportedDependency"); + +/** @typedef {import("../Module")} Module */ + +const JS_COMPAT_TYPES = new Set(["i32", "f32", "f64"]); + +/** + * @param {t.Signature} signature the func signature + * @returns {null | string} the type incompatible with js types + */ +const getJsIncompatibleType = signature => { + for (const param of signature.params) { + if (!JS_COMPAT_TYPES.has(param.valtype)) { + return `${param.valtype} as parameter`; + } + } + for (const type of signature.results) { + if (!JS_COMPAT_TYPES.has(type)) return `${type} as result`; + } + return null; +}; + +/** + * TODO why are there two different Signature types? + * @param {t.FuncSignature} signature the func signature + * @returns {null | string} the type incompatible with js types + */ +const getJsIncompatibleTypeOfFuncSignature = signature => { + for (const param of signature.args) { + if (!JS_COMPAT_TYPES.has(param)) { + return `${param} as parameter`; + } + } + for (const type of signature.result) { + if (!JS_COMPAT_TYPES.has(type)) return `${type} as result`; + } + return null; +}; + +const decoderOpts = { + ignoreCodeSection: true, + ignoreDataSection: true, + + // this will avoid having to lookup with identifiers in the ModuleContext + ignoreCustomNameSection: true +}; + +class WebAssemblyParser extends Tapable { + constructor(options) { + super(); + this.hooks = {}; + this.options = options; + } + + parse(binary, state) { + // flag it as ESM + state.module.buildMeta.exportsType = "namespace"; + + // parse it + const program = decode(binary, decoderOpts); + const module = program.body[0]; + + const moduleContext = moduleContextFromModuleAST(module); + + // extract imports and exports + const exports = (state.module.buildMeta.providedExports = []); + const jsIncompatibleExports = (state.module.buildMeta.jsIncompatibleExports = []); + + const importedGlobals = []; + t.traverse(module, { + ModuleExport({ node }) { + const descriptor = node.descr; + + if (descriptor.exportType === "Func") { + const funcidx = descriptor.id.value; + + /** @type {t.FuncSignature} */ + const funcSignature = moduleContext.getFunction(funcidx); + + const incompatibleType = getJsIncompatibleTypeOfFuncSignature( + funcSignature + ); + + if (incompatibleType) { + jsIncompatibleExports[node.name] = incompatibleType; + } + } + + exports.push(node.name); + + if (node.descr && node.descr.exportType === "Global") { + const refNode = importedGlobals[node.descr.id.value]; + if (refNode) { + const dep = new WebAssemblyExportImportedDependency( + node.name, + refNode.module, + refNode.name, + refNode.descr.valtype + ); + + state.module.addDependency(dep); + } + } + }, + + Global({ node }) { + const init = node.init[0]; + + let importNode = null; + + if (init.id === "get_global") { + const globalIdx = init.args[0].value; + + if (globalIdx < importedGlobals.length) { + importNode = importedGlobals[globalIdx]; + } + } + + importedGlobals.push(importNode); + }, + + ModuleImport({ node }) { + /** @type {false | string} */ + let onlyDirectImport = false; + + if (t.isMemory(node.descr) === true) { + onlyDirectImport = "Memory"; + } else if (t.isTable(node.descr) === true) { + onlyDirectImport = "Table"; + } else if (t.isFuncImportDescr(node.descr) === true) { + const incompatibleType = getJsIncompatibleType(node.descr.signature); + if (incompatibleType) { + onlyDirectImport = `Non-JS-compatible Func Sigurature (${incompatibleType})`; + } + } else if (t.isGlobalType(node.descr) === true) { + const type = node.descr.valtype; + if (!JS_COMPAT_TYPES.has(type)) { + onlyDirectImport = `Non-JS-compatible Global Type (${type})`; + } + } + + const dep = new WebAssemblyImportDependency( + node.module, + node.name, + node.descr, + onlyDirectImport + ); + + state.module.addDependency(dep); + + if (t.isGlobalType(node.descr)) { + importedGlobals.push(node); + } + } + }); + + return state; + } +} + +module.exports = WebAssemblyParser; diff --git a/node_modules/webpack/lib/wasm/WebAssemblyUtils.js b/node_modules/webpack/lib/wasm/WebAssemblyUtils.js new file mode 100644 index 0000000..75eb6d2 --- /dev/null +++ b/node_modules/webpack/lib/wasm/WebAssemblyUtils.js @@ -0,0 +1,59 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ +"use strict"; + +const Template = require("../Template"); +const WebAssemblyImportDependency = require("../dependencies/WebAssemblyImportDependency"); + +/** @typedef {import("../Module")} Module */ + +/** @typedef {Object} UsedWasmDependency + * @property {WebAssemblyImportDependency} dependency the dependency + * @property {string} name the export name + * @property {string} module the module name + */ + +const MANGLED_MODULE = "a"; + +/** + * @param {Module} module the module + * @param {boolean} mangle mangle module and export names + * @returns {UsedWasmDependency[]} used dependencies and (mangled) name + */ +const getUsedDependencies = (module, mangle) => { + /** @type {UsedWasmDependency[]} */ + const array = []; + let importIndex = 0; + for (const dep of module.dependencies) { + if (dep instanceof WebAssemblyImportDependency) { + if (dep.description.type === "GlobalType" || dep.module === null) { + continue; + } + + const exportName = dep.name; + // TODO add the following 3 lines when removing of ModuleExport is possible + // const importedModule = dep.module; + // const usedName = importedModule && importedModule.isUsed(exportName); + // if (usedName !== false) { + if (mangle) { + array.push({ + dependency: dep, + name: Template.numberToIdentifer(importIndex++), + module: MANGLED_MODULE + }); + } else { + array.push({ + dependency: dep, + name: exportName, + module: dep.request + }); + } + } + } + return array; +}; + +exports.getUsedDependencies = getUsedDependencies; +exports.MANGLED_MODULE = MANGLED_MODULE; |