finish plugin and tests

This commit is contained in:
Tobias Koppers 2018-06-04 16:23:41 +02:00
parent 822c252a09
commit 5ecf74917f
10 changed files with 97 additions and 44 deletions

7
declarations.d.ts vendored
View File

@ -94,6 +94,7 @@ declare module "@webassemblyjs/ast" {
signature: Signature;
}
export class Signature {
type: "Signature";
params: FuncParam[];
results: string[];
}
@ -118,7 +119,7 @@ declare module "@webassemblyjs/ast" {
init: Node[]
): ObjectInstruction;
export function signature(params: FuncParam[], results: string[]): Signature;
export function func(initFuncId, Signature, funcBody): Func;
export function func(initFuncId, signature: Signature, funcBody): Func;
export function typeInstruction(
id: Identifier,
functype: Signature
@ -134,6 +135,10 @@ declare module "@webassemblyjs/ast" {
): ModuleExportDescr;
export function getSectionMetadata(ast: any, section: string);
export class FuncSignature {
args: string[];
result: string[];
}
}
/**

View File

@ -26,6 +26,7 @@ class ModuleDependencyError extends WebpackError {
this.module = module;
this.loc = loc;
this.error = err;
this.origin = module.issuer;
Error.captureStackTrace(this, this.constructor);
}

View File

@ -18,6 +18,7 @@ module.exports = class ModuleDependencyWarning extends WebpackError {
this.module = module;
this.loc = loc;
this.error = err;
this.origin = module.issuer;
Error.captureStackTrace(this, this.constructor);
}

View File

@ -32,7 +32,7 @@ class WebAssemblyImportDependency extends ModuleDependency {
) {
return [
new UnsupportedWebAssemblyFeatureError(
`Import with ${
`Import "${this.name}" from "${this.request}" with ${
this.onlyDirectImport
} can only be used for direct wasm to wasm dependencies`
)

View File

@ -6,10 +6,6 @@
const UnsupportedWebAssemblyFeatureError = require("../wasm/UnsupportedWebAssemblyFeatureError");
const error = new UnsupportedWebAssemblyFeatureError(
"JavaScript modules can not use a WebAssembly export with an incompatible type signature"
);
class WasmFinalizeExportsPlugin {
apply(compiler) {
compiler.hooks.compilation.tap("WasmFinalizeExportsPlugin", compilation => {
@ -17,34 +13,46 @@ class WasmFinalizeExportsPlugin {
"WasmFinalizeExportsPlugin",
modules => {
for (const module of modules) {
const jsIncompatibleExports =
module.buildMeta.jsIncompatibleExports;
if (
typeof jsIncompatibleExports === "undefined" ||
jsIncompatibleExports.length === 0
) {
continue;
}
// 1. if a WebAssembly module
if (module.type.startsWith("webassembly") === true) {
const jsIncompatibleExports =
module.buildMeta.jsIncompatibleExports;
if (jsIncompatibleExports === undefined) {
continue;
}
for (const reason of module.reasons) {
// 2. is referenced by a non-WebAssembly module
if (reason.module.type.startsWith("webassembly") === false) {
// const ref = reason.dependency.getReference();
const ref = reason.dependency.getReference();
// ref.importedNames // returns true?
const importedNames = ref.importedNames;
const names = [];
names.forEach(name => {
// 3. and uses a func with an incompatible JS signature
if (jsIncompatibleExports.indexOf(name) !== -1) {
// 4. error
compilation.errors.push(error);
}
});
if (Array.isArray(importedNames)) {
importedNames.forEach(name => {
// 3. and uses a func with an incompatible JS signature
if (
Object.prototype.hasOwnProperty.call(
jsIncompatibleExports,
name
)
) {
// 4. error
/** @type {any} */
const error = new UnsupportedWebAssemblyFeatureError(
`Export "${name}" with ${
jsIncompatibleExports[name]
} can only be used for direct wasm to wasm dependencies`
);
error.module = module;
error.origin = reason.module;
error.originLoc = reason.dependency.loc;
error.dependencies = [reason.dependency];
compilation.errors.push(error);
}
});
}
}
}
}

View File

@ -43,15 +43,14 @@ const isGlobalImport = n => n.descr.type === "GlobalType";
const JS_COMPAT_TYPES = new Set(["i32", "f32", "f64"]);
/**
* @param {t.ModuleImport} moduleImport the import
* @param {t.Signature} signature the func signature
* @returns {null | string} the type incompatible with js types
*/
const getJsIncompatibleType = moduleImport => {
if (moduleImport.descr.type !== "FuncImportDescr") return null;
const signature = moduleImport.descr.signature;
const getJsIncompatibleType = signature => {
for (const param of signature.params) {
if (!JS_COMPAT_TYPES.has(param.valtype))
if (!JS_COMPAT_TYPES.has(param.valtype)) {
return `${param.valtype} as parameter`;
}
}
for (const type of signature.results) {
if (!JS_COMPAT_TYPES.has(type)) return `${type} as result`;
@ -59,6 +58,23 @@ const getJsIncompatibleType = moduleImport => {
return null;
};
/**
* TODO why are there two different Signature types?
* @param {t.FuncSignature} signature the func signature
* @returns {null | string} the type incompatible with js types
*/
const getJsIncompatibleTypeOfFuncSignature = signature => {
for (const param of signature.args) {
if (!JS_COMPAT_TYPES.has(param)) {
return `${param} as parameter`;
}
}
for (const type of signature.result) {
if (!JS_COMPAT_TYPES.has(type)) return `${type} as result`;
}
return null;
};
const decoderOpts = {
ignoreCodeSection: true,
ignoreDataSection: true,
@ -96,17 +112,15 @@ class WebAssemblyParser extends Tapable {
if (descriptor.exportType === "Func") {
const funcidx = descriptor.id.value;
/** @type {t.FuncSignature} */
const funcSignature = moduleContext.getFunction(funcidx);
const hasIncompatibleArg = funcSignature.args.some(
t => !JS_COMPAT_TYPES.has(t)
);
const hasIncompatibleResult = funcSignature.result.some(
t => !JS_COMPAT_TYPES.has(t)
const incompatibleType = getJsIncompatibleTypeOfFuncSignature(
funcSignature
);
if (hasIncompatibleArg === true || hasIncompatibleResult === true) {
jsIncompatibleExports.push(node.name);
if (incompatibleType) {
jsIncompatibleExports[node.name] = incompatibleType;
}
}
@ -151,10 +165,15 @@ class WebAssemblyParser extends Tapable {
} else if (isTableImport(node) === true) {
onlyDirectImport = "Table";
} else if (isFuncImport(node) === true) {
const incompatibleType = getJsIncompatibleType(node);
const incompatibleType = getJsIncompatibleType(node.descr.signature);
if (incompatibleType) {
onlyDirectImport = `Non-JS-compatible Func Sigurature (${incompatibleType})`;
}
} else if (isGlobalImport(node) === true) {
const type = node.descr.valtype;
if (!JS_COMPAT_TYPES.has(type)) {
onlyDirectImport = `Non-JS-compatible Global Type (${type})`;
}
}
const dep = new WebAssemblyImportDependency(

View File

@ -0,0 +1,17 @@
module.exports = [
[
/export-i64-param\.wat/,
/Export "a" with i64 as parameter can only be used for direct wasm to wasm dependencies/,
/export-i64-param\.js/
],
[
/export-i64-result\.wat/,
/Export "a" with i64 as result can only be used for direct wasm to wasm dependencies/,
/export-i64-result\.js/
],
[
/import-i64\.wat/,
/Import "n" from "\.\/env.js" with Non-JS-compatible Global Type \(i64\) can only be used for direct wasm to wasm dependencies/,
/index\.js/
]
]

View File

@ -0,0 +1 @@
export { a } from "./export-i64-param.wat";

View File

@ -0,0 +1 @@
export { a } from "./export-i64-result.wat";

View File

@ -1,12 +1,12 @@
it("should disallow exporting a func signature with result i64", function() {
return import("./export-i64-result.wat").then(({a}) => {
expect(a).toThrow(/incompatible type signature/);
return import("./export-i64-result").then(({a}) => {
expect(() => a()).toThrow(/invalid type/);
});
});
it("should disallow exporting a func signature with param i64", function() {
return import("./export-i64-param.wat").then(({a}) => {
expect(a).toThrow(/incompatible type signature/);
return import("./export-i64-param").then(({a}) => {
expect(() => a()).toThrow(/invalid type/);
});
});