287 lines
5.8 KiB
JavaScript
287 lines
5.8 KiB
JavaScript
// TODO(sven): add flow in here
|
|
|
|
import { isSignature, isNumberLiteral } from "@webassemblyjs/ast";
|
|
import { assert } from "mamacro";
|
|
|
|
export function moduleContextFromModuleAST(m) {
|
|
const moduleContext = new ModuleContext();
|
|
|
|
assert(m.type === "Module");
|
|
|
|
m.fields.forEach(field => {
|
|
switch (field.type) {
|
|
case "Start": {
|
|
moduleContext.setStart(field.index);
|
|
break;
|
|
}
|
|
case "TypeInstruction": {
|
|
moduleContext.addType(field);
|
|
break;
|
|
}
|
|
case "Func": {
|
|
moduleContext.addFunction(field);
|
|
break;
|
|
}
|
|
case "Global": {
|
|
moduleContext.defineGlobal(field);
|
|
break;
|
|
}
|
|
case "ModuleImport": {
|
|
switch (field.descr.type) {
|
|
case "GlobalType": {
|
|
moduleContext.importGlobal(
|
|
field.descr.valtype,
|
|
field.descr.mutability
|
|
);
|
|
break;
|
|
}
|
|
case "Memory": {
|
|
moduleContext.addMemory(
|
|
field.descr.limits.min,
|
|
field.descr.limits.max
|
|
);
|
|
break;
|
|
}
|
|
case "FuncImportDescr": {
|
|
moduleContext.importFunction(field.descr);
|
|
break;
|
|
}
|
|
|
|
case "Table": {
|
|
// FIXME(sven): not implemented yet
|
|
break;
|
|
}
|
|
|
|
default:
|
|
throw new Error(
|
|
"Unsupported ModuleImport of type " +
|
|
JSON.stringify(field.descr.type)
|
|
);
|
|
}
|
|
break;
|
|
}
|
|
case "Memory": {
|
|
moduleContext.addMemory(field.limits.min, field.limits.max);
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
|
|
return moduleContext;
|
|
}
|
|
|
|
/**
|
|
* Module context for type checking
|
|
*/
|
|
export class ModuleContext {
|
|
constructor() {
|
|
this.funcs = [];
|
|
this.funcsOffsetByIdentifier = [];
|
|
|
|
this.types = [];
|
|
|
|
this.globals = [];
|
|
this.globalsOffsetByIdentifier = [];
|
|
|
|
this.mems = [];
|
|
|
|
// Current stack frame
|
|
this.locals = [];
|
|
this.labels = [];
|
|
this.return = [];
|
|
|
|
this.debugName = "unknown";
|
|
|
|
this.start = null;
|
|
}
|
|
|
|
/**
|
|
* Set start segment
|
|
*/
|
|
setStart(index) {
|
|
this.start = index.value;
|
|
}
|
|
|
|
/**
|
|
* Get start function
|
|
*/
|
|
getStart() {
|
|
return this.start;
|
|
}
|
|
|
|
/**
|
|
* Reset the active stack frame
|
|
*/
|
|
newContext(debugName, expectedResult) {
|
|
this.locals = [];
|
|
this.labels = [expectedResult];
|
|
this.return = expectedResult;
|
|
this.debugName = debugName;
|
|
}
|
|
|
|
/**
|
|
* Functions
|
|
*/
|
|
addFunction(func /*: Func*/) {
|
|
// eslint-disable-next-line prefer-const
|
|
let { params: args = [], results: result = [] } = func.signature || {};
|
|
|
|
args = args.map(arg => arg.valtype);
|
|
|
|
this.funcs.push({ args, result });
|
|
|
|
if (typeof func.name !== "undefined") {
|
|
this.funcsOffsetByIdentifier[func.name.value] = this.funcs.length - 1;
|
|
}
|
|
}
|
|
|
|
importFunction(funcimport) {
|
|
if (isSignature(funcimport.signature)) {
|
|
// eslint-disable-next-line prefer-const
|
|
let { params: args, results: result } = funcimport.signature;
|
|
args = args.map(arg => arg.valtype);
|
|
|
|
this.funcs.push({ args, result });
|
|
} else {
|
|
assert(isNumberLiteral(funcimport.signature));
|
|
|
|
const typeId = funcimport.signature.value;
|
|
assert(this.hasType(typeId));
|
|
|
|
const signature = this.getType(typeId);
|
|
this.funcs.push({
|
|
args: signature.params.map(arg => arg.valtype),
|
|
result: signature.results
|
|
});
|
|
}
|
|
|
|
if (typeof funcimport.id !== "undefined") {
|
|
// imports are first, we can assume their index in the array
|
|
this.funcsOffsetByIdentifier[funcimport.id.value] = this.funcs.length - 1;
|
|
}
|
|
}
|
|
|
|
hasFunction(index) {
|
|
return typeof this.getFunction(index) !== "undefined";
|
|
}
|
|
|
|
getFunction(index) {
|
|
if (typeof index !== "number") {
|
|
throw new Error("getFunction only supported for number index");
|
|
}
|
|
|
|
return this.funcs[index];
|
|
}
|
|
|
|
getFunctionOffsetByIdentifier(name) {
|
|
assert(typeof name === "string");
|
|
|
|
return this.funcsOffsetByIdentifier[name];
|
|
}
|
|
|
|
/**
|
|
* Labels
|
|
*/
|
|
addLabel(result) {
|
|
this.labels.unshift(result);
|
|
}
|
|
|
|
hasLabel(index) {
|
|
return this.labels.length > index && index >= 0;
|
|
}
|
|
|
|
getLabel(index) {
|
|
return this.labels[index];
|
|
}
|
|
|
|
popLabel() {
|
|
this.labels.shift();
|
|
}
|
|
|
|
/**
|
|
* Locals
|
|
*/
|
|
hasLocal(index) {
|
|
return typeof this.getLocal(index) !== "undefined";
|
|
}
|
|
|
|
getLocal(index) {
|
|
return this.locals[index];
|
|
}
|
|
|
|
addLocal(type) {
|
|
this.locals.push(type);
|
|
}
|
|
|
|
/**
|
|
* Types
|
|
*/
|
|
addType(type) {
|
|
assert(type.functype.type === "Signature");
|
|
this.types.push(type.functype);
|
|
}
|
|
|
|
hasType(index) {
|
|
return this.types[index] !== undefined;
|
|
}
|
|
|
|
getType(index) {
|
|
return this.types[index];
|
|
}
|
|
|
|
/**
|
|
* Globals
|
|
*/
|
|
hasGlobal(index) {
|
|
return this.globals.length > index && index >= 0;
|
|
}
|
|
|
|
getGlobal(index) {
|
|
return this.globals[index].type;
|
|
}
|
|
|
|
getGlobalOffsetByIdentifier(name) {
|
|
assert(typeof name === "string");
|
|
|
|
return this.globalsOffsetByIdentifier[name];
|
|
}
|
|
|
|
defineGlobal(global /*: Global*/) {
|
|
const type = global.globalType.valtype;
|
|
const mutability = global.globalType.mutability;
|
|
|
|
this.globals.push({ type, mutability });
|
|
|
|
if (typeof global.name !== "undefined") {
|
|
this.globalsOffsetByIdentifier[global.name.value] =
|
|
this.globals.length - 1;
|
|
}
|
|
}
|
|
|
|
importGlobal(type, mutability) {
|
|
this.globals.push({ type, mutability });
|
|
}
|
|
|
|
isMutableGlobal(index) {
|
|
return this.globals[index].mutability === "var";
|
|
}
|
|
|
|
isImmutableGlobal(index) {
|
|
return this.globals[index].mutability === "const";
|
|
}
|
|
|
|
/**
|
|
* Memories
|
|
*/
|
|
hasMemory(index) {
|
|
return this.mems.length > index && index >= 0;
|
|
}
|
|
|
|
addMemory(min, max) {
|
|
this.mems.push({ min, max });
|
|
}
|
|
|
|
getMemory(index) {
|
|
return this.mems[index];
|
|
}
|
|
}
|