WebAssemblyGenerator.js 12 KB

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