WebAssemblyGenerator.js 14 KB

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