Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 21 additions & 3 deletions declarations.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ declare module "@webassemblyjs/ast" {
ModuleImport?: (p: NodePath<ModuleImport>) => void;
ModuleExport?: (p: NodePath<ModuleExport>) => void;
Start?: (p: NodePath<Start>) => void;
Global?: (p: NodePath<Global>) => void;
}
);
export class NodePath<T> {
Expand All @@ -60,16 +61,33 @@ declare module "@webassemblyjs/ast" {
}
export class ModuleExport extends Node {
name: string;
descr: {
type: string;
exportType: string;
id?: Identifier;
};
}
export class ModuleExportDescr extends Node {}
export class IndexLiteral extends Node {}
export class NumberLiteral extends Node {}
export class NumberLiteral extends Node {
value: number;
raw: string;
}
export class FloatLiteral extends Node {}
export class Global extends Node {}
export class GlobalType extends Node {
valtype: string;
}
export class Global extends Node {
init: Instruction[];
globalType: GlobalType;
}
export class FuncParam extends Node {
valtype: string;
}
export class Instruction extends Node {}
export class Instruction extends Node {
id: string;
args: NumberLiteral[];
}
export class CallInstruction extends Instruction {}
export class ObjectInstruction extends Instruction {}
export class Func extends Node {
Expand Down
29 changes: 29 additions & 0 deletions lib/dependencies/WebAssemblyExportImportedDependency.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";

const DependencyReference = require("./DependencyReference");
const ModuleDependency = require("./ModuleDependency");

class WebAssemblyExportImportedDependency extends ModuleDependency {
constructor(exportName, request, name) {
super(request);
/** @type {string} */
this.exportName = exportName;
/** @type {string} */
this.name = name;
}

getReference() {
if (!this.module) return null;
return new DependencyReference(this.module, [this.name], false);
}

get type() {
return "wasm export import";
}
}

module.exports = WebAssemblyExportImportedDependency;
70 changes: 43 additions & 27 deletions lib/wasm/WebAssemblyGenerator.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ const { editWithAST, addWithAST } = require("@webassemblyjs/wasm-edit");
const { decode } = require("@webassemblyjs/wasm-parser");
const t = require("@webassemblyjs/ast");

const WebAssemblyExportImportedDependency = require("../dependencies/WebAssemblyExportImportedDependency");

/** @typedef {import("../Module")} Module */
/** @typedef {import("./WebAssemblyUtils").UsedWasmDependency} UsedWasmDependency */

Expand Down Expand Up @@ -164,6 +166,27 @@ function getNextFuncIndex(ast, countImportedFunc) {
return t.indexLiteral(vectorOfSize + countImportedFunc);
}

/**
* Create a init instruction for a global
* @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
Expand All @@ -188,21 +211,7 @@ const rewriteImportedGlobals = state => bin => {

globalType.mutability = "var";

let init;

if (globalType.valtype[0] === "i") {
// create NumberLiteral global initializer
init = t.objectInstruction("const", globalType.valtype, [
t.numberLiteralFromRaw(0)
]);
} else if (globalType.valtype[0] === "f") {
// create FloatLiteral global initializer
init = t.objectInstruction("const", globalType.valtype, [
t.floatLiteral(0, false, false, "0")
]);
} else {
throw new Error("unknown type: " + globalType.valtype);
}
const init = createDefaultInitForGlobal(globalType);

newGlobals.push(t.global(globalType, [init]));

Expand All @@ -221,12 +230,7 @@ const rewriteImportedGlobals = state => bin => {

const initialGlobalidx = init.args[0];

const valtype = node.globalType.valtype;

node.init = [
// Poisong globals, they are meant to be rewritten
t.objectInstruction("const", valtype, [t.numberLiteralFromRaw(666)])
];
node.init = [createDefaultInitForGlobal(node.globalType)];

additionalInitCode.push(
/**
Expand All @@ -253,18 +257,24 @@ const rewriteImportedGlobals = state => bin => {
* Rewrite the export names
* @param {Object} state state
* @param {Object} state.ast Module's ast
* @param {Object} state.module Module
* @param {Module} state.module Module
* @param {Set<string>} state.externalExports Module
* @returns {ArrayBufferTransform} transform
*/
const rewriteExportNames = ({ ast, module }) => bin => {
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.node.name = usedName;
} else {
if (!usedName) {
path.remove();
return;
}
path.node.name = usedName;
}
});
};
Expand Down Expand Up @@ -403,14 +413,20 @@ class WebAssemblyGenerator extends Generator {
const nextTypeIndex = getNextTypeIndex(ast);

const usedDependencyMap = getUsedDependencyMap(module);
const externalExports = new Set(
module.dependencies
.filter(d => d instanceof WebAssemblyExportImportedDependency)
.map(d => d.exportName)
);

/** @type {t.Instruction[]} */
const additionalInitCode = [];

const transform = compose(
rewriteExportNames({
ast,
module
module,
externalExports
}),

removeStartFunc({ ast }),
Expand Down
141 changes: 92 additions & 49 deletions lib/wasm/WebAssemblyJavascriptGenerator.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,59 +8,96 @@ const Generator = require("../Generator");
const Template = require("../Template");
const { RawSource } = require("webpack-sources");
const WebAssemblyImportDependency = require("../dependencies/WebAssemblyImportDependency");

function generateInitParams(module) {
const list = [];

for (const dep of module.dependencies) {
if (dep instanceof WebAssemblyImportDependency) {
if (dep.description.type === "GlobalType") {
const exportName = dep.name;
const usedName = dep.module && dep.module.isUsed(exportName);

if (dep.module === null) {
// Dependency was not found, an error will be thrown later
continue;
}

if (usedName !== false) {
list.push(
`__webpack_require__(${JSON.stringify(
dep.module.id
)})[${JSON.stringify(usedName)}]`
);
}
}
}
}

return list;
}
const WebAssemblyExportImportedDependency = require("../dependencies/WebAssemblyExportImportedDependency");

class WebAssemblyJavascriptGenerator extends Generator {
generate(module, dependencyTemplates, runtimeTemplate) {
const initIdentifer = Array.isArray(module.usedExports)
? Template.numberToIdentifer(module.usedExports.length)
: "__webpack_init__";

const generateImports = () => {
const modules = new Map();
for (const dep of module.dependencies) {
if (dep.module) modules.set(dep.module, dep.userRequest);
}
return Template.asString(
Array.from(modules, ([m, r]) => {
return `${runtimeTemplate.moduleRaw({
module: m,
request: r
})};`;
})
);
};
let needExportsCopy = false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not really a copy? It's more a rewire or a connection

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This variable is about a shallow copy of the exports object.

If false it can use module.exports = instance.exports.
If true it has to use for(var key in instance.exports) exports[key] = instance.exports[key]

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const importedModules = new Map();
const initParams = [];
let index = 0;
for (const dep of module.dependencies) {
if (dep.module) {
let importData = importedModules.get(dep.module);
if (importData === undefined) {
importedModules.set(
dep.module,
(importData = {
importVar: `m${index}`,
index,
request: dep.userRequest,
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);

// FIXME(sven): assert that the exports exists in the modules
// otherwise it will default to i32 0
const initParams = generateInitParams(module).join(",");
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 defineStatement = Template.asString([
`${module.exportsArgument}[${JSON.stringify(
usedName
)}] = ${runtimeTemplate.exportFromImport({
module: dep.module,
request: dep.request,
importVar: importData.importVar,
originModule: module,
exportName: dep.name,
asiSafe: true,
isCall: false,
callContext: null
})};`
]);
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(
Expand All @@ -69,18 +106,24 @@ class WebAssemblyJavascriptGenerator extends Generator {
"// 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)
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)",
generateImports(),

importsCode,
"",
"// exec wasm module",
`wasmExports[${JSON.stringify(initIdentifer)}](${initParams})`
`wasmExports[${JSON.stringify(initIdentifer)}](${initParams.join(
", "
)})`
].join("\n")
);
return source;
Expand Down
6 changes: 6 additions & 0 deletions lib/wasm/WebAssemblyModulesPlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const WebAssemblyParser = require("./WebAssemblyParser");
const WebAssemblyGenerator = require("./WebAssemblyGenerator");
const WebAssemblyJavascriptGenerator = require("./WebAssemblyJavascriptGenerator");
const WebAssemblyImportDependency = require("../dependencies/WebAssemblyImportDependency");
const WebAssemblyExportImportedDependency = require("../dependencies/WebAssemblyExportImportedDependency");

class WebAssemblyModulesPlugin {
apply(compiler) {
Expand All @@ -20,6 +21,11 @@ class WebAssemblyModulesPlugin {
normalModuleFactory
);

compilation.dependencyFactories.set(
WebAssemblyExportImportedDependency,
normalModuleFactory
);

normalModuleFactory.hooks.createParser
.for("webassembly/experimental")
.tap("WebAssemblyModulesPlugin", () => {
Expand Down
Loading