You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
192 lines
5.1 KiB
192 lines
5.1 KiB
/* |
|
MIT License http://www.opensource.org/licenses/mit-license.php |
|
Author Tobias Koppers @sokra |
|
*/ |
|
|
|
"use strict"; |
|
|
|
const t = require("@webassemblyjs/ast"); |
|
const { moduleContextFromModuleAST } = require("@webassemblyjs/ast"); |
|
const { decode } = require("@webassemblyjs/wasm-parser"); |
|
const Parser = require("../Parser"); |
|
const StaticExportsDependency = require("../dependencies/StaticExportsDependency"); |
|
const WebAssemblyExportImportedDependency = require("../dependencies/WebAssemblyExportImportedDependency"); |
|
const WebAssemblyImportDependency = require("../dependencies/WebAssemblyImportDependency"); |
|
|
|
/** @typedef {import("../Module")} Module */ |
|
/** @typedef {import("../Parser").ParserState} ParserState */ |
|
/** @typedef {import("../Parser").PreparsedAst} PreparsedAst */ |
|
|
|
const JS_COMPAT_TYPES = new Set(["i32", "f32", "f64"]); |
|
|
|
/** |
|
* @param {t.Signature} signature the func signature |
|
* @returns {null | string} the type incompatible with js types |
|
*/ |
|
const getJsIncompatibleType = signature => { |
|
for (const param of signature.params) { |
|
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`; |
|
} |
|
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, |
|
|
|
// this will avoid having to lookup with identifiers in the ModuleContext |
|
ignoreCustomNameSection: true |
|
}; |
|
|
|
class WebAssemblyParser extends Parser { |
|
constructor(options) { |
|
super(); |
|
this.hooks = Object.freeze({}); |
|
this.options = options; |
|
} |
|
|
|
/** |
|
* @param {string | Buffer | PreparsedAst} source the source to parse |
|
* @param {ParserState} state the parser state |
|
* @returns {ParserState} the parser state |
|
*/ |
|
parse(source, state) { |
|
if (!Buffer.isBuffer(source)) { |
|
throw new Error("WebAssemblyParser input must be a Buffer"); |
|
} |
|
|
|
// flag it as ESM |
|
state.module.buildInfo.strict = true; |
|
state.module.buildMeta.exportsType = "namespace"; |
|
|
|
// parse it |
|
const program = decode(source, decoderOpts); |
|
const module = program.body[0]; |
|
|
|
const moduleContext = moduleContextFromModuleAST(module); |
|
|
|
// extract imports and exports |
|
const exports = []; |
|
let jsIncompatibleExports = (state.module.buildMeta.jsIncompatibleExports = |
|
undefined); |
|
|
|
const importedGlobals = []; |
|
t.traverse(module, { |
|
ModuleExport({ node }) { |
|
const descriptor = node.descr; |
|
|
|
if (descriptor.exportType === "Func") { |
|
const funcIdx = descriptor.id.value; |
|
|
|
/** @type {t.FuncSignature} */ |
|
const funcSignature = moduleContext.getFunction(funcIdx); |
|
|
|
const incompatibleType = |
|
getJsIncompatibleTypeOfFuncSignature(funcSignature); |
|
|
|
if (incompatibleType) { |
|
if (jsIncompatibleExports === undefined) { |
|
jsIncompatibleExports = |
|
state.module.buildMeta.jsIncompatibleExports = {}; |
|
} |
|
jsIncompatibleExports[node.name] = incompatibleType; |
|
} |
|
} |
|
|
|
exports.push(node.name); |
|
|
|
if (node.descr && node.descr.exportType === "Global") { |
|
const refNode = importedGlobals[node.descr.id.value]; |
|
if (refNode) { |
|
const dep = new WebAssemblyExportImportedDependency( |
|
node.name, |
|
refNode.module, |
|
refNode.name, |
|
refNode.descr.valtype |
|
); |
|
|
|
state.module.addDependency(dep); |
|
} |
|
} |
|
}, |
|
|
|
Global({ node }) { |
|
const init = node.init[0]; |
|
|
|
let importNode = null; |
|
|
|
if (init.id === "get_global") { |
|
const globalIdx = init.args[0].value; |
|
|
|
if (globalIdx < importedGlobals.length) { |
|
importNode = importedGlobals[globalIdx]; |
|
} |
|
} |
|
|
|
importedGlobals.push(importNode); |
|
}, |
|
|
|
ModuleImport({ node }) { |
|
/** @type {false | string} */ |
|
let onlyDirectImport = false; |
|
|
|
if (t.isMemory(node.descr) === true) { |
|
onlyDirectImport = "Memory"; |
|
} else if (t.isTable(node.descr) === true) { |
|
onlyDirectImport = "Table"; |
|
} else if (t.isFuncImportDescr(node.descr) === true) { |
|
const incompatibleType = getJsIncompatibleType(node.descr.signature); |
|
if (incompatibleType) { |
|
onlyDirectImport = `Non-JS-compatible Func Signature (${incompatibleType})`; |
|
} |
|
} else if (t.isGlobalType(node.descr) === true) { |
|
const type = node.descr.valtype; |
|
if (!JS_COMPAT_TYPES.has(type)) { |
|
onlyDirectImport = `Non-JS-compatible Global Type (${type})`; |
|
} |
|
} |
|
|
|
const dep = new WebAssemblyImportDependency( |
|
node.module, |
|
node.name, |
|
node.descr, |
|
onlyDirectImport |
|
); |
|
|
|
state.module.addDependency(dep); |
|
|
|
if (t.isGlobalType(node.descr)) { |
|
importedGlobals.push(node); |
|
} |
|
} |
|
}); |
|
|
|
state.module.addDependency(new StaticExportsDependency(exports, false)); |
|
|
|
return state; |
|
} |
|
} |
|
|
|
module.exports = WebAssemblyParser;
|
|
|