WebAssemblyGenerator.js 14 KB

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