123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536 |
- /*
- MIT License http://www.opensource.org/licenses/mit-license.php
- Author Tobias Koppers @sokra
- */
- "use strict";
- const { RawSource } = require("webpack-sources");
- const Generator = require("../Generator");
- const WebAssemblyUtils = require("./WebAssemblyUtils");
- const t = require("@webassemblyjs/ast");
- const { moduleContextFromModuleAST } = require("@webassemblyjs/ast");
- const { editWithAST, addWithAST } = require("@webassemblyjs/wasm-edit");
- const { decode } = require("@webassemblyjs/wasm-parser");
- const WebAssemblyExportImportedDependency = require("../dependencies/WebAssemblyExportImportedDependency");
- /** @typedef {import("webpack-sources").Source} Source */
- /** @typedef {import("../DependencyTemplates")} DependencyTemplates */
- /** @typedef {import("../Generator").GenerateContext} GenerateContext */
- /** @typedef {import("../Module")} Module */
- /** @typedef {import("../ModuleGraph")} ModuleGraph */
- /** @typedef {import("../NormalModule")} NormalModule */
- /** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */
- /** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */
- /** @typedef {import("./WebAssemblyUtils").UsedWasmDependency} UsedWasmDependency */
- /** @typedef {import("@webassemblyjs/ast").Instruction} Instruction */
- /** @typedef {import("@webassemblyjs/ast").ModuleImport} ModuleImport */
- /** @typedef {import("@webassemblyjs/ast").ModuleExport} ModuleExport */
- /** @typedef {import("@webassemblyjs/ast").Global} Global */
- /**
- * @template T
- * @typedef {import("@webassemblyjs/ast").NodePath<T>} NodePath
- */
- /**
- * @typedef {(buf: ArrayBuffer) => ArrayBuffer} ArrayBufferTransform
- */
- /**
- * @template T
- * @param {((prev: ArrayBuffer) => ArrayBuffer)[]} fns transforms
- * @returns {Function} composed transform
- */
- const compose = (...fns) => {
- return fns.reduce(
- (prevFn, nextFn) => {
- return value => nextFn(prevFn(value));
- },
- value => value
- );
- };
- /**
- * Removes the start instruction
- *
- * @param {object} state state
- * @param {object} state.ast Module's ast
- * @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 {t.ModuleImport[]} - nodes
- */
- const getImportedGlobals = ast => {
- /** @type {t.ModuleImport[]} */
- 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 information 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 transformation state
- * @param {object} state.ast Module's ast
- * @param {t.Instruction[]} state.additionalInitCode list of addition instructions for the init function
- * @returns {ArrayBufferTransform} transform
- */
- const rewriteImportedGlobals = state => bin => {
- const additionalInitCode = state.additionalInitCode;
- /** @type {Array<t.Global>} */
- const newGlobals = [];
- bin = editWithAST(state.ast, bin, {
- ModuleImport(path) {
- if (t.isGlobalType(path.node.descr)) {
- const globalType = /** @type {TODO} */ (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
- /**
- * @param {NodePath<Global>} path path
- */
- 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 {ModuleGraph} state.moduleGraph module graph
- * @param {Set<string>} state.externalExports Module
- * @param {RuntimeSpec} state.runtime runtime
- * @returns {ArrayBufferTransform} transform
- */
- const rewriteExportNames =
- ({ ast, moduleGraph, module, externalExports, runtime }) =>
- bin => {
- return editWithAST(ast, bin, {
- /**
- * @param {NodePath<ModuleExport>} path path
- */
- ModuleExport(path) {
- const isExternal = externalExports.has(path.node.name);
- if (isExternal) {
- path.remove();
- return;
- }
- const usedName = moduleGraph
- .getExportsInfo(module)
- .getUsedName(path.node.name, runtime);
- if (!usedName) {
- path.remove();
- return;
- }
- path.node.name = /** @type {string} */ (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, {
- /**
- * @param {NodePath<ModuleImport>} path path
- */
- 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(
- /** @type {string} */ (importedGlobal.descr.valtype),
- id
- );
- });
- /** @type {Instruction[]} */
- const funcBody = [];
- importedGlobals.forEach((importedGlobal, index) => {
- const args = [t.indexLiteral(index)];
- const body = [
- t.instruction("get_local", args),
- t.instruction("set_global", args)
- ];
- funcBody.push(...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"));
- /** @type {string[]} */
- 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 {ModuleGraph} moduleGraph module graph
- * @param {Module} module current module
- * @param {boolean | undefined} mangle mangle imports
- * @returns {Map<string, UsedWasmDependency>} mappings to mangled names
- */
- const getUsedDependencyMap = (moduleGraph, module, mangle) => {
- /** @type {Map<string, UsedWasmDependency>} */
- const map = new Map();
- for (const usedDep of WebAssemblyUtils.getUsedDependencies(
- moduleGraph,
- module,
- mangle
- )) {
- const dep = usedDep.dependency;
- const request = dep.request;
- const exportName = dep.name;
- map.set(request + ":" + exportName, usedDep);
- }
- return map;
- };
- const TYPES = new Set(["webassembly"]);
- /**
- * @typedef {object} WebAssemblyGeneratorOptions
- * @property {boolean} [mangleImports] mangle imports
- */
- class WebAssemblyGenerator extends Generator {
- /**
- * @param {WebAssemblyGeneratorOptions} options options
- */
- constructor(options) {
- super();
- this.options = options;
- }
- /**
- * @param {NormalModule} module fresh module
- * @returns {Set<string>} available types (do not mutate)
- */
- getTypes(module) {
- return TYPES;
- }
- /**
- * @param {NormalModule} module the module
- * @param {string=} type source type
- * @returns {number} estimate size of the module
- */
- getSize(module, type) {
- const originalSource = module.originalSource();
- if (!originalSource) {
- return 0;
- }
- return originalSource.size();
- }
- /**
- * @param {NormalModule} module module for which the code should be generated
- * @param {GenerateContext} generateContext context for generate
- * @returns {Source} generated code
- */
- generate(module, { moduleGraph, runtime }) {
- const bin = /** @type {Source} */ (module.originalSource()).source();
- const initFuncId = t.identifier("");
- // 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(
- moduleGraph,
- 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,
- moduleGraph,
- module,
- externalExports,
- runtime
- }),
- removeStartFunc({ ast }),
- rewriteImportedGlobals({ ast, additionalInitCode }),
- rewriteImports({
- ast,
- usedDependencyMap
- }),
- addInitFunction({
- ast,
- initFuncId,
- importedGlobals,
- additionalInitCode,
- startAtFuncOffset,
- nextFuncIndex,
- nextTypeIndex
- })
- );
- const newBin = transform(bin);
- const newBuf = Buffer.from(newBin);
- return new RawSource(newBuf);
- }
- }
- module.exports = WebAssemblyGenerator;
|