webassemblyjs
Advanced tools
Comparing version 1.2.3 to 1.2.4
@@ -9,4 +9,2 @@ "use strict"; | ||
var _ast = require("@webassemblyjs/ast"); | ||
var _wastIdentifierToIndex = require("@webassemblyjs/ast/lib/transform/wast-identifier-to-index"); | ||
@@ -20,2 +18,4 @@ | ||
var t = require("@webassemblyjs/ast"); | ||
var _require = require("../../errors"), | ||
@@ -27,3 +27,2 @@ CompileError = _require.CompileError; | ||
(0, _validation.default)(ast); | ||
this._ast = ast; | ||
@@ -44,3 +43,4 @@ this._start = start; | ||
(0, _wastIdentifierToIndex.transform)(ast); | ||
(0, _ast.traverse)(ast, { | ||
(0, _validation.default)(ast); | ||
t.traverse(ast, { | ||
ModuleExport: function (_ModuleExport) { | ||
@@ -86,3 +86,81 @@ function ModuleExport(_x) { | ||
}); | ||
/** | ||
* Adds missing end instructions | ||
*/ | ||
t.traverse(ast, { | ||
Func: function (_Func) { | ||
function Func(_x3) { | ||
return _Func.apply(this, arguments); | ||
} | ||
Func.toString = function () { | ||
return _Func.toString(); | ||
}; | ||
return Func; | ||
}(function (_ref3) { | ||
var node = _ref3.node; | ||
node.body.push(t.instruction("end")); | ||
}), | ||
Global: function (_Global) { | ||
function Global(_x4) { | ||
return _Global.apply(this, arguments); | ||
} | ||
Global.toString = function () { | ||
return _Global.toString(); | ||
}; | ||
return Global; | ||
}(function (_ref4) { | ||
var node = _ref4.node; | ||
node.init.push(t.instruction("end")); | ||
}), | ||
IfInstruction: function (_IfInstruction) { | ||
function IfInstruction(_x5) { | ||
return _IfInstruction.apply(this, arguments); | ||
} | ||
IfInstruction.toString = function () { | ||
return _IfInstruction.toString(); | ||
}; | ||
return IfInstruction; | ||
}(function (_ref5) { | ||
var node = _ref5.node; | ||
node.test.push(t.instruction("end")); | ||
node.consequent.push(t.instruction("end")); | ||
node.alternate.push(t.instruction("end")); | ||
}), | ||
BlockInstruction: function (_BlockInstruction) { | ||
function BlockInstruction(_x6) { | ||
return _BlockInstruction.apply(this, arguments); | ||
} | ||
BlockInstruction.toString = function () { | ||
return _BlockInstruction.toString(); | ||
}; | ||
return BlockInstruction; | ||
}(function (_ref6) { | ||
var node = _ref6.node; | ||
node.instr.push(t.instruction("end")); | ||
}), | ||
LoopInstruction: function (_LoopInstruction) { | ||
function LoopInstruction(_x7) { | ||
return _LoopInstruction.apply(this, arguments); | ||
} | ||
LoopInstruction.toString = function () { | ||
return _LoopInstruction.toString(); | ||
}; | ||
return LoopInstruction; | ||
}(function (_ref7) { | ||
var node = _ref7.node; | ||
node.instr.push(t.instruction("end")); | ||
}) | ||
}); | ||
return new Module(ast, exports, imports, start); | ||
} |
@@ -33,2 +33,8 @@ "use strict"; | ||
return true; | ||
} // FIXME(sven): this shoudln't be needed, we need to inject our end | ||
// instructions after the validations | ||
if (instr.id === "end") { | ||
return true; | ||
} | ||
@@ -35,0 +41,0 @@ |
@@ -28,6 +28,13 @@ "use strict"; | ||
return; | ||
} | ||
} // FIXME(sven): this shoudln't be needed, we need to inject our end | ||
// instructions after the validations | ||
var last = instrs[instrs.length - 1]; // It's a ObjectInstruction | ||
var last = instrs[instrs.length - 1]; | ||
if (last.id === "end") { | ||
last = instrs[instrs.length - 2]; | ||
} // It's a ObjectInstruction | ||
if (typeof last.object === "string") { | ||
@@ -34,0 +41,0 @@ // u32 are in fact i32 |
@@ -86,3 +86,3 @@ "use strict"; | ||
id: t.identifier(exportinst.name) | ||
}); // function trace(depth, blockpc, i, frame) { | ||
}); // function trace(depth, pc, i, frame) { | ||
// function ident() { | ||
@@ -97,6 +97,5 @@ // let out = ""; | ||
// ident(), | ||
// `-------------- blockpc: ${blockpc} - depth: ${depth} --------------` | ||
// `-------------- pc: ${pc} - depth: ${depth} --------------` | ||
// ); | ||
// console.log(ident(), "instruction:", i.id); | ||
// console.log(ident(), "unwind reason:", frame._unwindReason); | ||
// console.log(ident(), "locals:"); | ||
@@ -103,0 +102,0 @@ // frame.locals.forEach((stackLocal: StackLocal) => { |
@@ -12,3 +12,3 @@ "use strict"; | ||
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } | ||
function _toArray(arr) { return Array.isArray(arr) ? arr : Array.from(arr); } | ||
@@ -21,2 +21,6 @@ function _sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } | ||
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } | ||
var t = require("@webassemblyjs/ast"); | ||
var _require = require("./instruction/binop"), | ||
@@ -50,9 +54,8 @@ binopi32 = _require.binopi32, | ||
var _require4 = require("./signals"), | ||
createTrap = _require4.createTrap; // TODO(sven): can remove asserts call at compile to gain perf in prod | ||
createTrap = _require4.createTrap; // Syntactic sugar for the Syntactic sugar | ||
// TODO(sven): do it AOT? | ||
function assert(cond) { | ||
if (!cond) { | ||
throw new _errors.RuntimeError("Assertion error"); | ||
} | ||
function addEndInstruction(body) { | ||
body.push(t.instruction("end")); | ||
} | ||
@@ -66,932 +69,1145 @@ | ||
function executeStackFrame(frame) { | ||
function executeStackFrame(firstFrame) { | ||
var depth = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; | ||
var stack = [firstFrame]; | ||
var framepointer = 0; // eax | ||
function createAndExecuteChildStackFrame(instrs) { | ||
var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, | ||
passCurrentContext = _ref.passCurrentContext; | ||
var returnRegister = null; | ||
var childStackFrame = stackframe.createChildStackFrame(frame, instrs); | ||
function run() { | ||
assertStackDepth(framepointer); | ||
var frame = stack[framepointer]; | ||
if (passCurrentContext === true) { | ||
childStackFrame.values = frame.values; | ||
childStackFrame.labels = frame.labels; | ||
} | ||
(function () { | ||
if (!(frame !== undefined)) { | ||
throw new _errors.RuntimeError("Assertion error: " + ("no frame at " + framepointer || "unknown")); | ||
} | ||
})(); | ||
var res = executeStackFrame(childStackFrame, depth + 1); | ||
pushResult(res); | ||
} | ||
framepointer++; | ||
function getLocalByIndex(index) { | ||
var local = frame.locals[index]; | ||
function getLocalByIndex(index) { | ||
var local = frame.locals[index]; | ||
if (typeof local === "undefined") { | ||
throw newRuntimeError("Assertion error: no local value at index " + index); | ||
if (typeof local === "undefined") { | ||
throw newRuntimeError("Assertion error: no local value at index " + index); | ||
} | ||
frame.values.push(local); | ||
} | ||
frame.values.push(local); | ||
} | ||
function setLocalByIndex(index, value) { | ||
(function () { | ||
if (!(typeof index === "number")) { | ||
throw new _errors.RuntimeError("Assertion error: " + (undefined || "unknown")); | ||
} | ||
})(); | ||
function setLocalByIndex(index, value) { | ||
assert(typeof index === "number"); | ||
frame.locals[index] = value; | ||
} | ||
function pushResult(res) { | ||
if (typeof res === "undefined") { | ||
return; | ||
frame.locals[index] = value; | ||
} | ||
frame.values.push(res); | ||
} | ||
function pushResult(res) { | ||
if (typeof res === "undefined") { | ||
return; | ||
} | ||
function popArrayOfValTypes(types) { | ||
assertNItemsOnStack(frame.values, types.length); | ||
return types.map(function (type) { | ||
return pop1OfType(type); | ||
}); | ||
} | ||
frame.values.push(res); | ||
} | ||
function pop1OfType(type) { | ||
assertNItemsOnStack(frame.values, 1); | ||
var v = frame.values.pop(); | ||
function popArrayOfValTypes(types) { | ||
(function () { | ||
if (frame.values.length < types.length) { | ||
throw new _errors.RuntimeError("Assertion error: expected " + types.length + " on the stack, found " + frame.values.length); | ||
} | ||
})(); | ||
if (typeof type === "string" && v.type !== type) { | ||
throw newRuntimeError("Internal failure: expected value of type " + type + " on top of the stack, type given: " + v.type); | ||
return types.map(function (type) { | ||
return pop1OfType(type); | ||
}); | ||
} | ||
return v; | ||
} | ||
function pop1OfType(type) { | ||
(function () { | ||
if (frame.values.length < 1) { | ||
throw new _errors.RuntimeError("Assertion error: expected " + 1 + " on the stack, found " + frame.values.length); | ||
} | ||
})(); | ||
function pop1() { | ||
assertNItemsOnStack(frame.values, 1); | ||
return frame.values.pop(); | ||
} | ||
var v = frame.values.pop(); | ||
function pop2(type1, type2) { | ||
assertNItemsOnStack(frame.values, 2); | ||
var c2 = frame.values.pop(); | ||
var c1 = frame.values.pop(); | ||
if (typeof type === "string" && v.type !== type) { | ||
throw newRuntimeError("Internal failure: expected value of type " + type + " on top of the stack, type given: " + v.type); | ||
} | ||
if (c2.type !== type2) { | ||
throw newRuntimeError("Internal failure: expected c2 value of type " + type2 + " on top of the stack, give type: " + c2.type); | ||
return v; | ||
} | ||
if (c1.type !== type1) { | ||
throw newRuntimeError("Internal failure: expected c1 value of type " + type1 + " on top of the stack, give type: " + c1.type); | ||
function pop1() { | ||
(function () { | ||
if (frame.values.length < 1) { | ||
throw new _errors.RuntimeError("Assertion error: expected " + 1 + " on the stack, found " + frame.values.length); | ||
} | ||
})(); | ||
return frame.values.pop(); | ||
} | ||
return [c1, c2]; | ||
} | ||
function pop2(type1, type2) { | ||
(function () { | ||
if (frame.values.length < 2) { | ||
throw new _errors.RuntimeError("Assertion error: expected " + 2 + " on the stack, found " + frame.values.length); | ||
} | ||
})(); | ||
function getLabel(index) { | ||
var code; | ||
var c2 = frame.values.pop(); | ||
var c1 = frame.values.pop(); | ||
if (index.type === "NumberLiteral") { | ||
var _label = index; // WASM | ||
if (c2.type !== type2) { | ||
throw newRuntimeError("Internal failure: expected c2 value of type " + type2 + " on top of the stack, give type: " + c2.type); | ||
} | ||
code = frame.labels.find(function (l) { | ||
return l.value.value === _label.value; | ||
}); | ||
} else if (index.type === "Identifier") { | ||
var _label2 = index; // WAST | ||
if (c1.type !== type1) { | ||
throw newRuntimeError("Internal failure: expected c1 value of type " + type1 + " on top of the stack, give type: " + c1.type); | ||
} | ||
code = frame.labels.find(function (l) { | ||
if (l.id == null) { | ||
return false; | ||
} | ||
return l.id.value === _label2.value; | ||
}); | ||
return [c1, c2]; | ||
} | ||
if (typeof code !== "undefined") { | ||
return code.value; | ||
function getLabel(index) { | ||
var code; | ||
if (index.type === "NumberLiteral") { | ||
var _label = index; // WASM | ||
code = frame.labels.find(function (l) { | ||
return l.value.value === _label.value; | ||
}); | ||
} else if (index.type === "Identifier") { | ||
var _label2 = index; // WAST | ||
code = frame.labels.find(function (l) { | ||
if (l.id == null) { | ||
return false; | ||
} | ||
return l.id.value === _label2.value; | ||
}); | ||
} | ||
if (typeof code !== "undefined") { | ||
return code.value; | ||
} | ||
} | ||
} | ||
function br(label) { | ||
var code = getLabel(label); | ||
function br(label) { | ||
var code = getLabel(label); | ||
if (typeof code === "undefined") { | ||
throw newRuntimeError("Label ".concat(label.value, " doesn't exist")); | ||
} // FIXME(sven): find a more generic way to handle label and its code | ||
// Currently func body and block instr*. | ||
if (typeof code === "undefined") { | ||
throw newRuntimeError("Label ".concat(label.value, " doesn't exist")); | ||
} // FIXME(sven): find a more generic way to handle label and its code | ||
// Currently func body and block instr*. | ||
var childStackFrame = stackframe.createChildStackFrame(frame, code.body || code.instr); | ||
return executeStackFrame(childStackFrame, depth + 1); | ||
} | ||
var childStackFrame = stackframe.createChildStackFrame(frame, code.body || code.instr); | ||
return executeStackFrame(childStackFrame, depth + 1); | ||
} | ||
function getMemoryOffset(instruction) { | ||
if (instruction.namedArgs && instruction.namedArgs.offset) { | ||
var offset = instruction.namedArgs.offset.value; | ||
function getMemoryOffset(instruction) { | ||
if (instruction.namedArgs && instruction.namedArgs.offset) { | ||
var offset = instruction.namedArgs.offset.value; | ||
if (offset < 0) { | ||
throw newRuntimeError("offset must be positive"); | ||
if (offset < 0) { | ||
throw newRuntimeError("offset must be positive"); | ||
} | ||
if (offset > 0xffffffff) { | ||
throw newRuntimeError("offset must be less than or equal to 0xffffffff"); | ||
} | ||
return offset; | ||
} else { | ||
return 0; | ||
} | ||
} | ||
if (offset > 0xffffffff) { | ||
throw newRuntimeError("offset must be less than or equal to 0xffffffff"); | ||
function getMemory() { | ||
if (frame.originatingModule.memaddrs.length !== 1) { | ||
throw newRuntimeError("unknown memory"); | ||
} | ||
return offset; | ||
} else { | ||
return 0; | ||
var memAddr = frame.originatingModule.memaddrs[0]; | ||
return frame.allocator.get(memAddr); | ||
} | ||
} | ||
function getMemory() { | ||
if (frame.originatingModule.memaddrs.length !== 1) { | ||
throw newRuntimeError("unknown memory"); | ||
function newRuntimeError(msg) { | ||
return new _errors.RuntimeError(msg); | ||
} | ||
var memAddr = frame.originatingModule.memaddrs[0]; | ||
return frame.allocator.get(memAddr); | ||
} | ||
function createAndExecuteChildStackFrame(instrs) { | ||
var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, | ||
passCurrentContext = _ref.passCurrentContext; | ||
function newRuntimeError(msg) { | ||
return new _errors.RuntimeError(msg); | ||
} | ||
// FIXME(sven): that's wrong | ||
var frame = stack[framepointer - 1]; | ||
assertStackDepth(depth); | ||
(function () { | ||
if (!(frame !== undefined)) { | ||
throw new _errors.RuntimeError("Assertion error: " + ("no active frame" || "unknown")); | ||
} | ||
})(); | ||
while (frame._pc < frame.code.length) { | ||
var instruction = frame.code[frame._pc]; | ||
var nextStackFrame = stackframe.createChildStackFrame(frame, instrs); | ||
switch (instruction.type) { | ||
/** | ||
* Function declaration | ||
* | ||
* FIXME(sven): seems unspecified in the spec but it's required for the `call` | ||
* instruction. | ||
*/ | ||
case "Func": | ||
{ | ||
var func = instruction; | ||
/** | ||
* Register the function into the stack frame labels | ||
*/ | ||
if (passCurrentContext === true) { | ||
nextStackFrame.values = frame.values; | ||
nextStackFrame.labels = frame.labels; | ||
} // Push the frame on top of the stack | ||
if (_typeof(func.name) === "object") { | ||
if (func.name.type === "Identifier") { | ||
frame.labels.push({ | ||
value: func, | ||
arity: func.params.length, | ||
id: func.name | ||
}); | ||
stack[framepointer] = nextStackFrame; // Jump and execute the next frame | ||
run(); | ||
if (returnRegister !== null) { | ||
var _frame$values; | ||
(_frame$values = frame.values).push.apply(_frame$values, _toConsumableArray(returnRegister)); | ||
returnRegister = null; | ||
} | ||
} | ||
while (true) { | ||
var instruction = frame.code[frame._pc]; | ||
(function () { | ||
if (!(instruction !== undefined)) { | ||
throw new _errors.RuntimeError("Assertion error: " + ("no instruction at pc ".concat(frame._pc, " in frame ").concat(framepointer) || "unknown")); | ||
} | ||
})(); | ||
if (typeof frame.trace === "function") { | ||
frame.trace(framepointer, frame._pc, instruction, frame); | ||
} | ||
frame._pc++; | ||
switch (instruction.type) { | ||
/** | ||
* Function declaration | ||
* | ||
* FIXME(sven): seems unspecified in the spec but it's required for the `call` | ||
* instruction. | ||
*/ | ||
case "Func": | ||
{ | ||
var func = instruction; | ||
/** | ||
* Register the function into the stack frame labels | ||
*/ | ||
if (_typeof(func.name) === "object") { | ||
if (func.name.type === "Identifier") { | ||
frame.labels.push({ | ||
value: func, | ||
arity: func.params.length, | ||
id: func.name | ||
}); | ||
} | ||
} | ||
break; | ||
} | ||
} | ||
break; | ||
} | ||
} | ||
switch (instruction.id) { | ||
case "const": | ||
{ | ||
// https://webassembly.github.io/spec/core/exec/instructions.html#exec-const | ||
var n = instruction.args[0]; | ||
switch (instruction.id) { | ||
case "const": | ||
{ | ||
// https://webassembly.github.io/spec/core/exec/instructions.html#exec-const | ||
var n = instruction.args[0]; | ||
if (typeof n === "undefined") { | ||
throw newRuntimeError("const requires one argument, none given."); | ||
} | ||
if (typeof n === "undefined") { | ||
throw newRuntimeError("const requires one argument, none given."); | ||
if (n.type !== "NumberLiteral" && n.type !== "LongNumberLiteral" && n.type !== "FloatLiteral") { | ||
throw newRuntimeError("const: unsupported value of type: " + n.type); | ||
} | ||
pushResult(castIntoStackLocalOfType(instruction.object, n.value)); | ||
break; | ||
} | ||
if (n.type !== "NumberLiteral" && n.type !== "LongNumberLiteral" && n.type !== "FloatLiteral") { | ||
throw newRuntimeError("const: unsupported value of type: " + n.type); | ||
/** | ||
* Control Instructions | ||
* | ||
* https://webassembly.github.io/spec/core/exec/instructions.html#control-instructions | ||
*/ | ||
case "nop": | ||
{ | ||
// Do nothing | ||
// https://webassembly.github.io/spec/core/exec/instructions.html#exec-nop | ||
break; | ||
} | ||
pushResult(castIntoStackLocalOfType(instruction.object, n.value)); | ||
break; | ||
} | ||
case "loop": | ||
{ | ||
// https://webassembly.github.io/spec/core/exec/instructions.html#exec-loop | ||
var loop = instruction; | ||
/** | ||
* Control Instructions | ||
* | ||
* https://webassembly.github.io/spec/core/exec/instructions.html#control-instructions | ||
*/ | ||
(function () { | ||
if (!(_typeof(loop.instr) === "object" && typeof loop.instr.length !== "undefined")) { | ||
throw new _errors.RuntimeError("Assertion error: " + (undefined || "unknown")); | ||
} | ||
})(); // 2. Enter the block instr∗ with label | ||
case "nop": | ||
{ | ||
// Do nothing | ||
// https://webassembly.github.io/spec/core/exec/instructions.html#exec-nop | ||
break; | ||
} | ||
case "loop": | ||
{ | ||
// https://webassembly.github.io/spec/core/exec/instructions.html#exec-loop | ||
var loop = instruction; | ||
assert(_typeof(loop.instr) === "object" && typeof loop.instr.length !== "undefined"); // 2. Enter the block instr∗ with label | ||
frame.labels.push({ | ||
value: loop, | ||
arity: 0, | ||
id: loop.label | ||
}); | ||
pushResult(label.createValue(loop.label.value)); | ||
frame.labels.push({ | ||
value: loop, | ||
arity: 0, | ||
id: loop.label | ||
}); | ||
pushResult(label.createValue(loop.label.value)); | ||
if (loop.instr.length > 0) { | ||
createAndExecuteChildStackFrame(loop.instr, { | ||
passCurrentContext: true | ||
}); | ||
} | ||
if (loop.instr.length > 0) { | ||
createAndExecuteChildStackFrame(loop.instr, { | ||
passCurrentContext: true | ||
}); | ||
break; | ||
} | ||
break; | ||
} | ||
case "drop": | ||
{ | ||
// https://webassembly.github.io/spec/core/exec/instructions.html#exec-drop | ||
// 1. Assert: due to validation, a value is on the top of the stack. | ||
(function () { | ||
if (frame.values.length < 1) { | ||
throw new _errors.RuntimeError("Assertion error: expected " + 1 + " on the stack, found " + frame.values.length); | ||
} | ||
})(); // 2. Pop the value valval from the stack. | ||
case "drop": | ||
{ | ||
// https://webassembly.github.io/spec/core/exec/instructions.html#exec-drop | ||
// 1. Assert: due to validation, a value is on the top of the stack. | ||
assertNItemsOnStack(frame.values, 1); // 2. Pop the value valval from the stack. | ||
pop1(); | ||
break; | ||
} | ||
pop1(); | ||
break; | ||
} | ||
case "call": | ||
{ | ||
// According to the spec call doesn't support an Identifier as argument | ||
// but the Script syntax supports it. | ||
// https://webassembly.github.io/spec/core/exec/instructions.html#exec-call | ||
var call = instruction; | ||
case "call": | ||
{ | ||
// According to the spec call doesn't support an Identifier as argument | ||
// but the Script syntax supports it. | ||
// https://webassembly.github.io/spec/core/exec/instructions.html#exec-call | ||
var call = instruction; | ||
if (call.index.type === "Identifier") { | ||
throw newRuntimeError("Internal compiler error: Identifier argument in call must be " + "transformed to a NumberLiteral node"); | ||
} // WASM | ||
if (call.index.type === "Identifier") { | ||
throw newRuntimeError("Internal compiler error: Identifier argument in call must be " + "transformed to a NumberLiteral node"); | ||
} // WASM | ||
if (call.index.type === "NumberLiteral") { | ||
var index = call.index.value; | ||
assert(typeof frame.originatingModule !== "undefined"); // 2. Assert: due to validation, F.module.funcaddrs[x] exists. | ||
if (call.index.type === "NumberLiteral") { | ||
var index = call.index.value; | ||
var funcaddr = frame.originatingModule.funcaddrs[index]; | ||
(function () { | ||
if (!(typeof frame.originatingModule !== "undefined")) { | ||
throw new _errors.RuntimeError("Assertion error: " + (undefined || "unknown")); | ||
} | ||
})(); // 2. Assert: due to validation, F.module.funcaddrs[x] exists. | ||
if (typeof funcaddr === "undefined") { | ||
throw newRuntimeError("No function were found in module at address ".concat(index)); | ||
} // 3. Let a be the function address F.module.funcaddrs[x] | ||
var funcaddr = frame.originatingModule.funcaddrs[index]; | ||
var subroutine = frame.allocator.get(funcaddr); | ||
if (typeof funcaddr === "undefined") { | ||
throw newRuntimeError("No function were found in module at address ".concat(index)); | ||
} // 3. Let a be the function address F.module.funcaddrs[x] | ||
if (_typeof(subroutine) !== "object") { | ||
throw newRuntimeError("Cannot call function at address ".concat(funcaddr, ": not a function")); | ||
} // 4. Invoke the function instance at address a | ||
// FIXME(sven): assert that res has type of resultType | ||
var subroutine = frame.allocator.get(funcaddr); | ||
var _subroutine$type = _slicedToArray(subroutine.type, 2), | ||
argTypes = _subroutine$type[0], | ||
resultType = _subroutine$type[1]; | ||
if (_typeof(subroutine) !== "object") { | ||
throw newRuntimeError("Cannot call function at address ".concat(funcaddr, ": not a function")); | ||
} // 4. Invoke the function instance at address a | ||
// FIXME(sven): assert that res has type of resultType | ||
var args = popArrayOfValTypes(argTypes); | ||
if (subroutine.isExternal === false) { | ||
createAndExecuteChildStackFrame(subroutine.code); | ||
} else { | ||
var res = subroutine.code(args.map(function (arg) { | ||
return arg.value; | ||
})); | ||
var _subroutine$type = _slicedToArray(subroutine.type, 2), | ||
argTypes = _subroutine$type[0], | ||
resultType = _subroutine$type[1]; | ||
if (typeof res !== "undefined") { | ||
pushResult(castIntoStackLocalOfType(resultType, res)); | ||
var args = popArrayOfValTypes(argTypes); | ||
if (subroutine.isExternal === false) { | ||
createAndExecuteChildStackFrame(subroutine.code); | ||
} else { | ||
var res = subroutine.code(args.map(function (arg) { | ||
return arg.value; | ||
})); | ||
if (typeof res !== "undefined") { | ||
pushResult(castIntoStackLocalOfType(resultType, res)); | ||
} | ||
} | ||
} | ||
break; | ||
} | ||
break; | ||
} | ||
case "block": | ||
{ | ||
var _ret = function () { | ||
// https://webassembly.github.io/spec/core/exec/instructions.html#blocks | ||
var block = instruction; | ||
/** | ||
* Used to keep track of the number of values added on top of the stack | ||
* because we need to remove the label after the execution of this block. | ||
*/ | ||
case "block": | ||
{ | ||
var _ret = function () { | ||
// https://webassembly.github.io/spec/core/exec/instructions.html#blocks | ||
var block = instruction; | ||
/** | ||
* Used to keep track of the number of values added on top of the stack | ||
* because we need to remove the label after the execution of this block. | ||
*/ | ||
var numberOfValuesAddedOnTopOfTheStack = 0; // 2. Enter the block instr∗ with label | ||
var numberOfValuesAddedOnTopOfTheStack = 0; // 2. Enter the block instr∗ with label | ||
frame.labels.push({ | ||
value: block, | ||
arity: 0, | ||
id: block.label | ||
}); | ||
frame.labels.push({ | ||
value: block, | ||
arity: 0, | ||
id: block.label | ||
}); | ||
if (block.label.type === "Identifier") { | ||
pushResult(label.createValue(block.label.value)); | ||
} else { | ||
throw newRuntimeError("Block has no id"); | ||
} | ||
if (block.label.type === "Identifier") { | ||
pushResult(label.createValue(block.label.value)); | ||
} else { | ||
throw newRuntimeError("Block has no id"); | ||
} | ||
(function () { | ||
if (!(_typeof(block.instr) === "object" && typeof block.instr.length !== "undefined")) { | ||
throw new _errors.RuntimeError("Assertion error: " + (undefined || "unknown")); | ||
} | ||
})(); | ||
assert(_typeof(block.instr) === "object" && typeof block.instr.length !== "undefined"); | ||
if (block.instr.length > 0) { | ||
var oldStackSize = frame.values.length; | ||
createAndExecuteChildStackFrame(block.instr, { | ||
passCurrentContext: true | ||
}); | ||
numberOfValuesAddedOnTopOfTheStack = frame.values.length - oldStackSize; | ||
} | ||
/** | ||
* Wen exiting the block | ||
* | ||
* > Let m be the number of values on the top of the stack | ||
* | ||
* The Stack (values) are seperated by StackFrames and we are running on | ||
* one single thread, there's no need to check if values were added. | ||
* | ||
* We tracked it in numberOfValuesAddedOnTopOfTheStack anyway. | ||
*/ | ||
if (block.instr.length > 0) { | ||
var oldStackSize = frame.values.length; | ||
createAndExecuteChildStackFrame(block.instr, { | ||
passCurrentContext: true | ||
var topOfTheStack = frame.values.slice(frame.values.length - numberOfValuesAddedOnTopOfTheStack); | ||
frame.values.splice(frame.values.length - numberOfValuesAddedOnTopOfTheStack); // 3. Assert: due to validation, the label LL is now on the top of the stack. | ||
// 4. Pop the label from the stack. | ||
pop1OfType("label"); | ||
frame.values = _toConsumableArray(frame.values).concat(_toConsumableArray(topOfTheStack)); // Remove label | ||
frame.labels = frame.labels.filter(function (x) { | ||
if (x.id == null) { | ||
return true; | ||
} | ||
return x.id.value !== block.label.value; | ||
}); | ||
numberOfValuesAddedOnTopOfTheStack = frame.values.length - oldStackSize; | ||
} | ||
/** | ||
* Wen exiting the block | ||
* | ||
* > Let m be the number of values on the top of the stack | ||
* | ||
* The Stack (values) are seperated by StackFrames and we are running on | ||
* one single thread, there's no need to check if values were added. | ||
* | ||
* We tracked it in numberOfValuesAddedOnTopOfTheStack anyway. | ||
*/ | ||
return "break"; | ||
}(); | ||
if (_ret === "break") break; | ||
} | ||
var topOfTheStack = frame.values.slice(frame.values.length - numberOfValuesAddedOnTopOfTheStack); | ||
frame.values.splice(frame.values.length - numberOfValuesAddedOnTopOfTheStack); // 3. Assert: due to validation, the label LL is now on the top of the stack. | ||
// 4. Pop the label from the stack. | ||
case "br": | ||
{ | ||
var _ret2 = function () { | ||
// https://webassembly.github.io/spec/core/exec/instructions.html#exec-br | ||
var _instruction$args = _toArray(instruction.args), | ||
label = _instruction$args[0], | ||
children = _instruction$args.slice(1); | ||
pop1OfType("label"); | ||
frame.values = _toConsumableArray(frame.values).concat(_toConsumableArray(topOfTheStack)); // Remove label | ||
frame.labels = frame.labels.filter(function (x) { | ||
if (x.id == null) { | ||
return true; | ||
if (label.type === "Identifier") { | ||
throw newRuntimeError("Internal compiler error: Identifier argument in br must be " + "transformed to a NumberLiteral node"); | ||
} | ||
return x.id.value !== block.label.value; | ||
}); | ||
return "break"; | ||
}(); | ||
var l = label.value; // 1. Assert: due to validation, the stack contains at least l+1 labels. | ||
if (_ret === "break") break; | ||
} | ||
(function () { | ||
if (frame.values.length < l + 1) { | ||
throw new _errors.RuntimeError("Assertion error: expected " + (l + 1) + " on the stack, found " + frame.values.length); | ||
} | ||
})(); // 2. Let L be the l-th label appearing on the stack, starting from the top and counting from zero. | ||
case "br_if": | ||
{ | ||
var _instruction$args = _slicedToArray(instruction.args, 1), | ||
_label3 = _instruction$args[0]; // 1. Assert: due to validation, a value of type i32 is on the top of the stack. | ||
// 2. Pop the value ci32.const c from the stack. | ||
var seenLabels = 0; | ||
var labelidx = { | ||
value: "unknown" | ||
}; // for (var i = 0, len = frame.values.length; i < len; i++) { | ||
var c = pop1OfType("i32"); | ||
for (var i = frame.values.length; i--;) { | ||
if (frame.values[i].type === "label") { | ||
if (seenLabels === l) { | ||
labelidx = frame.values[i]; | ||
break; | ||
} | ||
if (!c.value.eqz().isTrue()) { | ||
// 3. If c is non-zero, then | ||
// 3. a. Execute the instruction (br l). | ||
var _res = br(_label3); | ||
seenLabels++; | ||
} | ||
} // $FlowIgnore | ||
pushResult(_res); | ||
} else {// 4. Else: | ||
// 4. a. Do nothing. | ||
} | ||
break; | ||
} | ||
var L = frame.labels.find(function (x) { | ||
return x.id.value === labelidx.value; | ||
}); | ||
case "if": | ||
{ | ||
if (instruction.test.length > 0) { | ||
createAndExecuteChildStackFrame(instruction.test); | ||
} // 1. Assert: due to validation, a value of value type i32 is on the top of the stack. | ||
// 2. Pop the value i32.const from the stack. | ||
if (typeof L === "undefined") { | ||
throw newRuntimeError("br: unknown label ".concat(labelidx.value)); | ||
} // 3. Let n be the arity of L. | ||
var _c = pop1OfType("i32"); | ||
var n = L.arity; // 4. Assert: due to validation, there are at least nn values on the top of the stack. | ||
if (_c.value.eqz().isTrue() === false) { | ||
/** | ||
* Execute consequent | ||
*/ | ||
createAndExecuteChildStackFrame(instruction.consequent); | ||
} else if (typeof instruction.alternate !== "undefined" && instruction.alternate.length > 0) { | ||
/** | ||
* Execute alternate | ||
*/ | ||
createAndExecuteChildStackFrame(instruction.alternate); | ||
} | ||
(function () { | ||
if (frame.values.length < n) { | ||
throw new _errors.RuntimeError("Assertion error: expected " + n + " on the stack, found " + frame.values.length); | ||
} | ||
})(); // 5. Pop the values valn from the stack | ||
break; | ||
} | ||
/** | ||
* Administrative Instructions | ||
* | ||
* https://webassembly.github.io/spec/core/exec/runtime.html#administrative-instructions | ||
*/ | ||
var val = frame.values[n]; | ||
var bottomOfTheStack = frame.values.slice(0, n); | ||
var topOfTheStack = frame.values.slice(n + 1); | ||
frame.values = _toConsumableArray(bottomOfTheStack).concat(_toConsumableArray(topOfTheStack)); // 6. Repeat l+1 times: | ||
case "unreachable": // https://webassembly.github.io/spec/core/exec/instructions.html#exec-unreachable | ||
for (var _i2 = 0; _i2 < l + 1; _i2++) { | ||
// a. While the top of the stack is a value, do: | ||
// i. Pop the value from the stack | ||
var value = frame.values[frame.values.length - 1]; | ||
case "trap": | ||
{ | ||
// signalling abrupt termination | ||
// https://webassembly.github.io/spec/core/exec/runtime.html#syntax-trap | ||
throw createTrap(); | ||
} | ||
if (typeof value === "undefined") { | ||
break; | ||
} | ||
case "local": | ||
{ | ||
var _instruction$args2 = _slicedToArray(instruction.args, 1), | ||
valtype = _instruction$args2[0]; | ||
if (value.type !== "label") { | ||
pop1(); | ||
} | ||
} // b. Assert: due to validation, the top of the stack now is a label. | ||
// c. Pop the label from the stack. | ||
var init = castIntoStackLocalOfType(valtype.name, 0); | ||
frame.locals.push(init); | ||
break; | ||
} | ||
/** | ||
* Memory Instructions | ||
* | ||
* https://webassembly.github.io/spec/core/exec/instructions.html#memory-instructions | ||
*/ | ||
pop1OfType("label"); // 7. Push the values valn to the stack. | ||
case "get_local": | ||
{ | ||
// https://webassembly.github.io/spec/core/exec/instructions.html#exec-get-local | ||
var _index = instruction.args[0]; | ||
pushResult(val); // 0 is the current frame, 1 is it's parent. | ||
if (typeof _index === "undefined") { | ||
throw newRuntimeError("get_local requires one argument, none given."); | ||
} | ||
stack = stack.slice(0, -(l + 1)); | ||
framepointer -= l + 1; // execute childrens | ||
if (_index.type === "NumberLiteral" || _index.type === "FloatLiteral") { | ||
getLocalByIndex(_index.value); | ||
} else { | ||
throw newRuntimeError("get_local: unsupported index of type: " + _index.type); | ||
addEndInstruction(children); | ||
createAndExecuteChildStackFrame(children, { | ||
passCurrentContext: true | ||
}); | ||
return { | ||
v: void 0 | ||
}; | ||
}(); | ||
if (_typeof(_ret2) === "object") return _ret2.v; | ||
} | ||
break; | ||
} | ||
case "br_if": | ||
{ | ||
var _instruction$args2 = _toArray(instruction.args), | ||
_label3 = _instruction$args2[0], | ||
children = _instruction$args2.slice(1); // execute childrens | ||
case "set_local": | ||
{ | ||
// https://webassembly.github.io/spec/core/exec/instructions.html#exec-set-local | ||
var _index2 = instruction.args[0]; | ||
var _init = instruction.args[1]; | ||
if (typeof _init !== "undefined" && _init.type === "Instr") { | ||
// WAST | ||
createAndExecuteChildStackFrame([_init], { | ||
addEndInstruction(children); | ||
createAndExecuteChildStackFrame(children, { | ||
passCurrentContext: true | ||
}); | ||
}); // 1. Assert: due to validation, a value of type i32 is on the top of the stack. | ||
// 2. Pop the value ci32.const c from the stack. | ||
var _res2 = pop1(); | ||
var c = pop1OfType("i32"); | ||
setLocalByIndex(_index2.value, _res2); | ||
} else if (_index2.type === "NumberLiteral") { | ||
// WASM | ||
// 4. Pop the value val from the stack | ||
var val = pop1(); // 5. Replace F.locals[x] with the value val | ||
if (!c.value.eqz().isTrue()) { | ||
// 3. If c is non-zero, then | ||
// 3. a. Execute the instruction (br l). | ||
var _res = br(_label3); | ||
setLocalByIndex(_index2.value, val); | ||
} else { | ||
throw newRuntimeError("set_local: unsupported index of type: " + _index2.type); | ||
pushResult(_res); | ||
} else {// 4. Else: | ||
// 4. a. Do nothing. | ||
} | ||
break; | ||
} | ||
break; | ||
} | ||
case "if": | ||
{ | ||
if (instruction.test.length > 0) { | ||
createAndExecuteChildStackFrame(instruction.test); | ||
} // 1. Assert: due to validation, a value of value type i32 is on the top of the stack. | ||
// 2. Pop the value i32.const from the stack. | ||
case "tee_local": | ||
{ | ||
// https://webassembly.github.io/spec/core/exec/instructions.html#exec-tee-local | ||
var _index3 = instruction.args[0]; | ||
var _init2 = instruction.args[1]; | ||
if (typeof _init2 !== "undefined" && _init2.type === "Instr") { | ||
// WAST | ||
createAndExecuteChildStackFrame([_init2], { | ||
passCurrentContext: true | ||
}); | ||
var _c = pop1OfType("i32"); | ||
var _res3 = pop1(); | ||
if (_c.value.eqz().isTrue() === false) { | ||
/** | ||
* Execute consequent | ||
*/ | ||
createAndExecuteChildStackFrame(instruction.consequent); | ||
} else if (typeof instruction.alternate !== "undefined" && instruction.alternate.length > 0) { | ||
/** | ||
* Execute alternate | ||
*/ | ||
createAndExecuteChildStackFrame(instruction.alternate); | ||
} | ||
setLocalByIndex(_index3.value, _res3); | ||
pushResult(_res3); | ||
} else if (_index3.type === "NumberLiteral") { | ||
// WASM | ||
// 1. Assert: due to validation, a value is on the top of the stack. | ||
// 2. Pop the value val from the stack. | ||
var _val = pop1(); // 3. Push the value valval to the stack. | ||
break; | ||
} | ||
/** | ||
* Administrative Instructions | ||
* | ||
* https://webassembly.github.io/spec/core/exec/runtime.html#administrative-instructions | ||
*/ | ||
pushResult(_val); // 4. Push the value valval to the stack. | ||
case "unreachable": // https://webassembly.github.io/spec/core/exec/instructions.html#exec-unreachable | ||
pushResult(_val); // 5. Execute the instruction (set_local x). | ||
// 5. 4. Pop the value val from the stack | ||
case "trap": | ||
{ | ||
// signalling abrupt termination | ||
// https://webassembly.github.io/spec/core/exec/runtime.html#syntax-trap | ||
throw createTrap(); | ||
} | ||
var val2 = pop1(); // 5. 5. Replace F.locals[x] with the value val | ||
case "local": | ||
{ | ||
var _instruction$args3 = _slicedToArray(instruction.args, 1), | ||
valtype = _instruction$args3[0]; | ||
setLocalByIndex(_index3.value, val2); | ||
} else { | ||
throw newRuntimeError("tee_local: unsupported index of type: " + _index3.type); | ||
var init = castIntoStackLocalOfType(valtype.name, 0); | ||
frame.locals.push(init); | ||
break; | ||
} | ||
break; | ||
} | ||
/** | ||
* Memory Instructions | ||
* | ||
* https://webassembly.github.io/spec/core/exec/instructions.html#memory-instructions | ||
*/ | ||
case "set_global": | ||
{ | ||
// https://webassembly.github.io/spec/core/exec/instructions.html#exec-set-global | ||
var _instruction$args3 = _slicedToArray(instruction.args, 2), | ||
_index4 = _instruction$args3[0], | ||
right = _instruction$args3[1]; // Interpret right branch first if it's a child instruction | ||
case "get_local": | ||
{ | ||
// https://webassembly.github.io/spec/core/exec/instructions.html#exec-get-local | ||
var _index = instruction.args[0]; | ||
if (typeof _index === "undefined") { | ||
throw newRuntimeError("get_local requires one argument, none given."); | ||
} | ||
if (typeof right !== "undefined") { | ||
createAndExecuteChildStackFrame([right], { | ||
passCurrentContext: true | ||
}); | ||
} // 2. Assert: due to validation, F.module.globaladdrs[x] exists. | ||
if (_index.type === "NumberLiteral" || _index.type === "FloatLiteral") { | ||
getLocalByIndex(_index.value); | ||
} else { | ||
throw newRuntimeError("get_local: unsupported index of type: " + _index.type); | ||
} | ||
break; | ||
} | ||
var globaladdr = frame.originatingModule.globaladdrs[_index4.value]; | ||
case "set_local": | ||
{ | ||
// https://webassembly.github.io/spec/core/exec/instructions.html#exec-set-local | ||
var _index2 = instruction.args[0]; | ||
var _init = instruction.args[1]; | ||
if (typeof globaladdr === "undefined") { | ||
throw newRuntimeError("Global address ".concat(_index4.value, " not found")); | ||
} // 4. Assert: due to validation, S.globals[a] exists. | ||
if (typeof _init !== "undefined" && _init.type === "Instr") { | ||
// WAST | ||
var code = [_init]; | ||
addEndInstruction(code); | ||
createAndExecuteChildStackFrame(code, { | ||
passCurrentContext: true | ||
}); | ||
var _res2 = pop1(); | ||
var globalinst = frame.allocator.get(globaladdr); | ||
setLocalByIndex(_index2.value, _res2); | ||
} else if (_index2.type === "NumberLiteral") { | ||
// WASM | ||
// 4. Pop the value val from the stack | ||
var val = pop1(); // 5. Replace F.locals[x] with the value val | ||
if (_typeof(globalinst) !== "object") { | ||
throw newRuntimeError("Unexpected data for global at ".concat(globaladdr)); | ||
} // 7. Pop the value val from the stack. | ||
setLocalByIndex(_index2.value, val); | ||
} else { | ||
throw newRuntimeError("set_local: unsupported index of type: " + _index2.type); | ||
} | ||
break; | ||
} | ||
var _val2 = pop1(); // 8. Replace glob.value with the value val. | ||
case "tee_local": | ||
{ | ||
// https://webassembly.github.io/spec/core/exec/instructions.html#exec-tee-local | ||
var _index3 = instruction.args[0]; | ||
var _init2 = instruction.args[1]; | ||
if (typeof _init2 !== "undefined" && _init2.type === "Instr") { | ||
// WAST | ||
var _code = [_init2]; | ||
addEndInstruction(_code); | ||
createAndExecuteChildStackFrame(_code, { | ||
passCurrentContext: true | ||
}); | ||
globalinst.value = _val2.value; | ||
frame.allocator.set(globaladdr, globalinst); | ||
break; | ||
} | ||
var _res3 = pop1(); | ||
case "get_global": | ||
{ | ||
// https://webassembly.github.io/spec/core/exec/instructions.html#exec-get-global | ||
var _index5 = instruction.args[0]; // 2. Assert: due to validation, F.module.globaladdrs[x] exists. | ||
setLocalByIndex(_index3.value, _res3); | ||
pushResult(_res3); | ||
} else if (_index3.type === "NumberLiteral") { | ||
// WASM | ||
// 1. Assert: due to validation, a value is on the top of the stack. | ||
// 2. Pop the value val from the stack. | ||
var _val = pop1(); // 3. Push the value valval to the stack. | ||
var _globaladdr = frame.originatingModule.globaladdrs[_index5.value]; | ||
if (typeof _globaladdr === "undefined") { | ||
throw newRuntimeError("Unknown global at index: ".concat(_index5.value)); | ||
} // 4. Assert: due to validation, S.globals[a] exists. | ||
pushResult(_val); // 4. Push the value valval to the stack. | ||
pushResult(_val); // 5. Execute the instruction (set_local x). | ||
// 5. 4. Pop the value val from the stack | ||
var _globalinst = frame.allocator.get(_globaladdr); | ||
var val2 = pop1(); // 5. 5. Replace F.locals[x] with the value val | ||
if (_typeof(_globalinst) !== "object") { | ||
throw newRuntimeError("Unexpected data for global at ".concat(_globaladdr)); | ||
} // 7. Pop the value val from the stack. | ||
setLocalByIndex(_index3.value, val2); | ||
} else { | ||
throw newRuntimeError("tee_local: unsupported index of type: " + _index3.type); | ||
} | ||
break; | ||
} | ||
pushResult(_globalinst); | ||
break; | ||
} | ||
case "set_global": | ||
{ | ||
// https://webassembly.github.io/spec/core/exec/instructions.html#exec-set-global | ||
var _instruction$args4 = _slicedToArray(instruction.args, 2), | ||
_index4 = _instruction$args4[0], | ||
right = _instruction$args4[1]; // Interpret right branch first if it's a child instruction | ||
case "return": | ||
{ | ||
var _args = instruction.args; | ||
if (_args.length > 0) { | ||
createAndExecuteChildStackFrame(_args, { | ||
passCurrentContext: true | ||
}); | ||
} // Abort execution and return the first item on the stack | ||
if (typeof right !== "undefined") { | ||
var _code2 = [right]; | ||
addEndInstruction(_code2); | ||
createAndExecuteChildStackFrame(_code2, { | ||
passCurrentContext: true | ||
}); | ||
} // 2. Assert: due to validation, F.module.globaladdrs[x] exists. | ||
return pop1(); | ||
} | ||
var globaladdr = frame.originatingModule.globaladdrs[_index4.value]; | ||
/** | ||
* Memory operations | ||
*/ | ||
// https://webassembly.github.io/spec/core/exec/instructions.html#exec-storen | ||
if (typeof globaladdr === "undefined") { | ||
throw newRuntimeError("Global address ".concat(_index4.value, " not found")); | ||
} // 4. Assert: due to validation, S.globals[a] exists. | ||
case "store": | ||
case "store8": | ||
case "store16": | ||
case "store32": | ||
{ | ||
var id = instruction.id, | ||
object = instruction.object, | ||
_args2 = instruction.args; // Interpret children first | ||
// only WAST | ||
if (typeof _args2 !== "undefined" && _args2.length > 0) { | ||
createAndExecuteChildStackFrame(_args2, { | ||
passCurrentContext: true | ||
}); | ||
var globalinst = frame.allocator.get(globaladdr); | ||
if (_typeof(globalinst) !== "object") { | ||
throw newRuntimeError("Unexpected data for global at ".concat(globaladdr)); | ||
} // 7. Pop the value val from the stack. | ||
var _val2 = pop1(); // 8. Replace glob.value with the value val. | ||
globalinst.value = _val2.value; | ||
frame.allocator.set(globaladdr, globalinst); | ||
break; | ||
} | ||
var memory = getMemory(); | ||
case "get_global": | ||
{ | ||
// https://webassembly.github.io/spec/core/exec/instructions.html#exec-get-global | ||
var _index5 = instruction.args[0]; // 2. Assert: due to validation, F.module.globaladdrs[x] exists. | ||
var _pop = pop2("i32", object), | ||
_pop2 = _slicedToArray(_pop, 2), | ||
c1 = _pop2[0], | ||
c2 = _pop2[1]; | ||
var _globaladdr = frame.originatingModule.globaladdrs[_index5.value]; | ||
var ptr = c1.value.toNumber() + getMemoryOffset(instruction); | ||
var valueBuffer = c2.value.toByteArray(); | ||
if (typeof _globaladdr === "undefined") { | ||
throw newRuntimeError("Unknown global at index: ".concat(_index5.value)); | ||
} // 4. Assert: due to validation, S.globals[a] exists. | ||
switch (id) { | ||
case "store8": | ||
valueBuffer = valueBuffer.slice(0, 1); | ||
break; | ||
case "store16": | ||
valueBuffer = valueBuffer.slice(0, 2); | ||
break; | ||
var _globalinst = frame.allocator.get(_globaladdr); | ||
case "store32": | ||
valueBuffer = valueBuffer.slice(0, 4); | ||
break; | ||
} | ||
if (_typeof(_globalinst) !== "object") { | ||
throw newRuntimeError("Unexpected data for global at ".concat(_globaladdr)); | ||
} // 7. Pop the value val from the stack. | ||
if (ptr + valueBuffer.length > memory.buffer.byteLength) { | ||
throw newRuntimeError("memory access out of bounds"); | ||
pushResult(_globalinst); | ||
break; | ||
} | ||
var memoryBuffer = new Uint8Array(memory.buffer); // load / store use little-endian order | ||
case "return": | ||
{ | ||
var _args = instruction.args; | ||
for (var ptrOffset = 0; ptrOffset < valueBuffer.length; ptrOffset++) { | ||
memoryBuffer[ptr + ptrOffset] = valueBuffer[ptrOffset]; | ||
if (_args.length > 0) { | ||
addEndInstruction(_args); | ||
createAndExecuteChildStackFrame(_args, { | ||
passCurrentContext: true | ||
}); | ||
} // Abort execution and return the first item on the stack | ||
returnRegister = [pop1()]; | ||
return; | ||
} | ||
break; | ||
} | ||
// https://webassembly.github.io/spec/core/exec/instructions.html#and | ||
/** | ||
* Memory operations | ||
*/ | ||
// https://webassembly.github.io/spec/core/exec/instructions.html#exec-storen | ||
case "load": | ||
case "load16_s": | ||
case "load16_u": | ||
case "load8_s": | ||
case "load8_u": | ||
case "load32_s": | ||
case "load32_u": | ||
{ | ||
var _id = instruction.id, | ||
_object = instruction.object, | ||
_args3 = instruction.args; // Interpret children first | ||
// only WAST | ||
case "store": | ||
case "store8": | ||
case "store16": | ||
case "store32": | ||
{ | ||
var id = instruction.id, | ||
object = instruction.object, | ||
_args2 = instruction.args; // Interpret children first | ||
// only WAST | ||
if (typeof _args3 !== "undefined" && _args3.length > 0) { | ||
createAndExecuteChildStackFrame(_args3, { | ||
passCurrentContext: true | ||
}); | ||
} | ||
if (typeof _args2 !== "undefined" && _args2.length > 0) { | ||
addEndInstruction(_args2); | ||
createAndExecuteChildStackFrame(_args2, { | ||
passCurrentContext: true | ||
}); | ||
} | ||
var _memory = getMemory(); | ||
var memory = getMemory(); | ||
var _ptr = pop1OfType("i32").value.toNumber() + getMemoryOffset(instruction); // for i32 / i64 ops, handle extended load | ||
var _pop = pop2("i32", object), | ||
_pop2 = _slicedToArray(_pop, 2), | ||
c1 = _pop2[0], | ||
c2 = _pop2[1]; | ||
var ptr = c1.value.toNumber() + getMemoryOffset(instruction); | ||
var valueBuffer = c2.value.toByteArray(); | ||
var extend = 0; // for i64 values, increase the bitshift by 4 bytes | ||
switch (id) { | ||
case "store8": | ||
valueBuffer = valueBuffer.slice(0, 1); | ||
break; | ||
var extendOffset = _object === "i32" ? 0 : 32; | ||
var signed = false; | ||
case "store16": | ||
valueBuffer = valueBuffer.slice(0, 2); | ||
break; | ||
switch (_id) { | ||
case "load16_s": | ||
extend = 16 + extendOffset; | ||
signed = true; | ||
break; | ||
case "store32": | ||
valueBuffer = valueBuffer.slice(0, 4); | ||
break; | ||
} | ||
case "load16_u": | ||
extend = 16 + extendOffset; | ||
signed = false; | ||
break; | ||
if (ptr + valueBuffer.length > memory.buffer.byteLength) { | ||
throw newRuntimeError("memory access out of bounds"); | ||
} | ||
case "load8_s": | ||
extend = 24 + extendOffset; | ||
signed = true; | ||
break; | ||
var memoryBuffer = new Uint8Array(memory.buffer); // load / store use little-endian order | ||
case "load8_u": | ||
extend = 24 + extendOffset; | ||
signed = false; | ||
break; | ||
for (var ptrOffset = 0; ptrOffset < valueBuffer.length; ptrOffset++) { | ||
memoryBuffer[ptr + ptrOffset] = valueBuffer[ptrOffset]; | ||
} | ||
case "load32_u": | ||
extend = 0 + extendOffset; | ||
signed = false; | ||
break; | ||
break; | ||
} | ||
// https://webassembly.github.io/spec/core/exec/instructions.html#and | ||
case "load32_s": | ||
extend = 0 + extendOffset; | ||
signed = true; | ||
break; | ||
} // check for memory access out of bounds | ||
case "load": | ||
case "load16_s": | ||
case "load16_u": | ||
case "load8_s": | ||
case "load8_u": | ||
case "load32_s": | ||
case "load32_u": | ||
{ | ||
var _id = instruction.id, | ||
_object = instruction.object, | ||
_args3 = instruction.args; // Interpret children first | ||
// only WAST | ||
if (typeof _args3 !== "undefined" && _args3.length > 0) { | ||
addEndInstruction(_args3); | ||
createAndExecuteChildStackFrame(_args3, { | ||
passCurrentContext: true | ||
}); | ||
} | ||
switch (_object) { | ||
case "i32": | ||
case "f32": | ||
if (_ptr + 4 > _memory.buffer.byteLength) { | ||
throw newRuntimeError("memory access out of bounds"); | ||
} | ||
var _memory = getMemory(); | ||
break; | ||
var _ptr = pop1OfType("i32").value.toNumber() + getMemoryOffset(instruction); // for i32 / i64 ops, handle extended load | ||
case "i64": | ||
case "f64": | ||
if (_ptr + 8 > _memory.buffer.byteLength) { | ||
throw newRuntimeError("memory access out of bounds"); | ||
} | ||
break; | ||
} | ||
var extend = 0; // for i64 values, increase the bitshift by 4 bytes | ||
switch (_object) { | ||
case "i32": | ||
pushResult(i32.createValueFromArrayBuffer(_memory.buffer, _ptr, extend, signed)); | ||
break; | ||
var extendOffset = _object === "i32" ? 0 : 32; | ||
var signed = false; | ||
case "i64": | ||
pushResult(i64.createValueFromArrayBuffer(_memory.buffer, _ptr, extend, signed)); | ||
break; | ||
switch (_id) { | ||
case "load16_s": | ||
extend = 16 + extendOffset; | ||
signed = true; | ||
break; | ||
case "f32": | ||
pushResult(f32.createValueFromArrayBuffer(_memory.buffer, _ptr)); | ||
break; | ||
case "load16_u": | ||
extend = 16 + extendOffset; | ||
signed = false; | ||
break; | ||
case "f64": | ||
pushResult(f64.createValueFromArrayBuffer(_memory.buffer, _ptr)); | ||
break; | ||
} | ||
case "load8_s": | ||
extend = 24 + extendOffset; | ||
signed = true; | ||
break; | ||
break; | ||
} | ||
case "load8_u": | ||
extend = 24 + extendOffset; | ||
signed = false; | ||
break; | ||
/** | ||
* Binary operations | ||
*/ | ||
case "load32_u": | ||
extend = 0 + extendOffset; | ||
signed = false; | ||
break; | ||
case "add": | ||
case "mul": | ||
case "sub": | ||
/** | ||
* There are two seperated operation for both signed and unsigned integer, | ||
* but since the host environment will handle that, we don't have too :) | ||
*/ | ||
case "load32_s": | ||
extend = 0 + extendOffset; | ||
signed = true; | ||
break; | ||
} // check for memory access out of bounds | ||
case "div_s": | ||
case "div_u": | ||
case "rem_s": | ||
case "rem_u": | ||
case "shl": | ||
case "shr_s": | ||
case "shr_u": | ||
case "rotl": | ||
case "rotr": | ||
case "div": | ||
case "min": | ||
case "max": | ||
case "copysign": | ||
case "or": | ||
case "xor": | ||
case "and": | ||
case "eq": | ||
case "ne": | ||
case "lt_s": | ||
case "lt_u": | ||
case "le_s": | ||
case "le_u": | ||
case "gt": | ||
case "gt_s": | ||
case "gt_u": | ||
case "ge_s": | ||
case "ge_u": | ||
{ | ||
var binopFn = void 0; | ||
switch (instruction.object) { | ||
case "i32": | ||
binopFn = binopi32; | ||
break; | ||
switch (_object) { | ||
case "i32": | ||
case "f32": | ||
if (_ptr + 4 > _memory.buffer.byteLength) { | ||
throw newRuntimeError("memory access out of bounds"); | ||
} | ||
case "i64": | ||
binopFn = binopi64; | ||
break; | ||
break; | ||
case "f32": | ||
binopFn = binopf32; | ||
break; | ||
case "i64": | ||
case "f64": | ||
if (_ptr + 8 > _memory.buffer.byteLength) { | ||
throw newRuntimeError("memory access out of bounds"); | ||
} | ||
case "f64": | ||
binopFn = binopf64; | ||
break; | ||
break; | ||
} | ||
default: | ||
throw createTrap("Unsupported operation " + instruction.id + " on " + instruction.object); | ||
switch (_object) { | ||
case "i32": | ||
pushResult(i32.createValueFromArrayBuffer(_memory.buffer, _ptr, extend, signed)); | ||
break; | ||
case "i64": | ||
pushResult(i64.createValueFromArrayBuffer(_memory.buffer, _ptr, extend, signed)); | ||
break; | ||
case "f32": | ||
pushResult(f32.createValueFromArrayBuffer(_memory.buffer, _ptr)); | ||
break; | ||
case "f64": | ||
pushResult(f64.createValueFromArrayBuffer(_memory.buffer, _ptr)); | ||
break; | ||
} | ||
break; | ||
} | ||
var _instruction$args4 = _slicedToArray(instruction.args, 2), | ||
left = _instruction$args4[0], | ||
_right = _instruction$args4[1]; // Interpret left branch first if it's a child instruction | ||
/** | ||
* Binary operations | ||
*/ | ||
case "add": | ||
case "mul": | ||
case "sub": | ||
/** | ||
* There are two seperated operation for both signed and unsigned integer, | ||
* but since the host environment will handle that, we don't have too :) | ||
*/ | ||
if (typeof left !== "undefined") { | ||
createAndExecuteChildStackFrame([left], { | ||
passCurrentContext: true | ||
}); | ||
} // Interpret right branch first if it's a child instruction | ||
case "div_s": | ||
case "div_u": | ||
case "rem_s": | ||
case "rem_u": | ||
case "shl": | ||
case "shr_s": | ||
case "shr_u": | ||
case "rotl": | ||
case "rotr": | ||
case "div": | ||
case "min": | ||
case "max": | ||
case "copysign": | ||
case "or": | ||
case "xor": | ||
case "and": | ||
case "eq": | ||
case "ne": | ||
case "lt_s": | ||
case "lt_u": | ||
case "le_s": | ||
case "le_u": | ||
case "gt": | ||
case "gt_s": | ||
case "gt_u": | ||
case "ge_s": | ||
case "ge_u": | ||
{ | ||
var binopFn = void 0; | ||
switch (instruction.object) { | ||
case "i32": | ||
binopFn = binopi32; | ||
break; | ||
if (typeof _right !== "undefined") { | ||
createAndExecuteChildStackFrame([_right], { | ||
passCurrentContext: true | ||
}); | ||
} | ||
case "i64": | ||
binopFn = binopi64; | ||
break; | ||
var _pop3 = pop2(instruction.object, instruction.object), | ||
_pop4 = _slicedToArray(_pop3, 2), | ||
_c2 = _pop4[0], | ||
_c3 = _pop4[1]; | ||
case "f32": | ||
binopFn = binopf32; | ||
break; | ||
pushResult(binopFn(_c2, _c3, instruction.id)); | ||
break; | ||
} | ||
case "f64": | ||
binopFn = binopf64; | ||
break; | ||
/** | ||
* Unary operations | ||
*/ | ||
default: | ||
throw createTrap("Unsupported operation " + instruction.id + " on " + instruction.object); | ||
} | ||
case "abs": | ||
case "neg": | ||
case "clz": | ||
case "ctz": | ||
case "popcnt": | ||
case "eqz": | ||
case "reinterpret/f32": | ||
case "reinterpret/f64": | ||
{ | ||
var unopFn = void 0; // for conversion operations, the operand type appears after the forward-slash | ||
// e.g. with i32.reinterpret/f32, the oprand is f32, and the resultant is i32 | ||
var _instruction$args5 = _slicedToArray(instruction.args, 2), | ||
left = _instruction$args5[0], | ||
_right = _instruction$args5[1]; // Interpret left branch first if it's a child instruction | ||
var opType = instruction.id.indexOf("/") !== -1 ? instruction.id.split("/")[1] : instruction.object; | ||
switch (opType) { | ||
case "i32": | ||
unopFn = unopi32; | ||
break; | ||
if (typeof left !== "undefined") { | ||
var _code3 = [left]; | ||
addEndInstruction(_code3); | ||
createAndExecuteChildStackFrame(_code3, { | ||
passCurrentContext: true | ||
}); | ||
} // Interpret right branch first if it's a child instruction | ||
case "i64": | ||
unopFn = unopi64; | ||
break; | ||
case "f32": | ||
unopFn = unopf32; | ||
break; | ||
if (typeof _right !== "undefined") { | ||
var _code4 = [_right]; | ||
addEndInstruction(_code4); | ||
createAndExecuteChildStackFrame(_code4, { | ||
passCurrentContext: true | ||
}); | ||
} | ||
case "f64": | ||
unopFn = unopf64; | ||
break; | ||
var _pop3 = pop2(instruction.object, instruction.object), | ||
_pop4 = _slicedToArray(_pop3, 2), | ||
_c2 = _pop4[0], | ||
_c3 = _pop4[1]; | ||
default: | ||
throw createTrap("Unsupported operation " + instruction.id + " on " + opType); | ||
pushResult(binopFn(_c2, _c3, instruction.id)); | ||
break; | ||
} | ||
var _instruction$args5 = _slicedToArray(instruction.args, 1), | ||
operand = _instruction$args5[0]; // Interpret argument first if it's a child instruction | ||
/** | ||
* Unary operations | ||
*/ | ||
case "abs": | ||
case "neg": | ||
case "clz": | ||
case "ctz": | ||
case "popcnt": | ||
case "eqz": | ||
case "reinterpret/f32": | ||
case "reinterpret/f64": | ||
{ | ||
var unopFn = void 0; // for conversion operations, the operand type appears after the forward-slash | ||
// e.g. with i32.reinterpret/f32, the oprand is f32, and the resultant is i32 | ||
if (typeof operand !== "undefined") { | ||
createAndExecuteChildStackFrame([operand], { | ||
passCurrentContext: true | ||
}); | ||
} | ||
var opType = instruction.id.indexOf("/") !== -1 ? instruction.id.split("/")[1] : instruction.object; | ||
var _c4 = pop1OfType(opType); | ||
switch (opType) { | ||
case "i32": | ||
unopFn = unopi32; | ||
break; | ||
pushResult(unopFn(_c4, instruction.id)); | ||
break; | ||
} | ||
} | ||
case "i64": | ||
unopFn = unopi64; | ||
break; | ||
if (typeof frame.trace === "function") { | ||
frame.trace(depth, frame._pc, instruction, frame); | ||
} | ||
case "f32": | ||
unopFn = unopf32; | ||
break; | ||
frame._pc++; | ||
} // Return the item on top of the values/stack; | ||
case "f64": | ||
unopFn = unopf64; | ||
break; | ||
default: | ||
throw createTrap("Unsupported operation " + instruction.id + " on " + opType); | ||
} | ||
if (frame.values.length > 0) { | ||
var _res4 = pop1(); | ||
var _instruction$args6 = _slicedToArray(instruction.args, 1), | ||
operand = _instruction$args6[0]; // Interpret argument first if it's a child instruction | ||
if (_res4.type !== "label") { | ||
return _res4; | ||
} else { | ||
// Push label back | ||
pushResult(_res4); | ||
if (typeof operand !== "undefined") { | ||
var _code5 = [operand]; | ||
addEndInstruction(_code5); | ||
createAndExecuteChildStackFrame(_code5, { | ||
passCurrentContext: true | ||
}); | ||
} | ||
var _c4 = pop1OfType(opType); | ||
pushResult(unopFn(_c4, instruction.id)); | ||
break; | ||
} | ||
case "end": | ||
{ | ||
// Pop active frame from the stack | ||
stack.pop(); | ||
framepointer--; // Return the item on top of the values/stack; | ||
if (frame.values.length > 0) { | ||
var _res4 = pop1(); | ||
if (_res4.type !== "label") { | ||
returnRegister = [_res4]; | ||
} else { | ||
// Push label back | ||
pushResult(_res4); | ||
} | ||
} | ||
return; | ||
} | ||
} | ||
} | ||
} | ||
} | ||
function assertNItemsOnStack(stack, numberOfItem) { | ||
if (stack.length < numberOfItem) { | ||
throw new _errors.RuntimeError("Assertion error: expected " + numberOfItem + " on the stack, found " + stack.length); | ||
run(); | ||
if (returnRegister !== null) { | ||
// FIXME(sven): handle multiple results in hostfunc | ||
return returnRegister[0]; | ||
} | ||
} |
@@ -28,5 +28,6 @@ "use strict"; | ||
// TODO(sven): find a specification reference for that | ||
// FIXME(sven): +1 because of the implicit end, change the order of validations | ||
if (node.init.length > 1 || node.init.length === 0) { | ||
if (node.init.length > 2 || node.init.length === 1) { | ||
throw new CompileError("type mismatch"); | ||
@@ -33,0 +34,0 @@ } // Validate the type |
{ | ||
"name": "webassemblyjs", | ||
"version": "1.2.3", | ||
"version": "1.2.4", | ||
"keywords": [ | ||
@@ -21,9 +21,9 @@ "webassembly", | ||
"dependencies": { | ||
"@webassemblyjs/ast": "1.2.3", | ||
"@webassemblyjs/wasm-parser": "1.2.3", | ||
"@webassemblyjs/wast-parser": "1.2.3", | ||
"@webassemblyjs/ast": "1.2.4", | ||
"@webassemblyjs/wasm-parser": "1.2.4", | ||
"@webassemblyjs/wast-parser": "1.2.4", | ||
"long": "^3.2.0" | ||
}, | ||
"devDependencies": { | ||
"@webassemblyjs/floating-point-hex-parser": "1.2.3" | ||
"@webassemblyjs/floating-point-hex-parser": "1.2.4" | ||
}, | ||
@@ -30,0 +30,0 @@ "repository": { |
// @flow | ||
import { traverse } from "@webassemblyjs/ast"; | ||
import { transform } from "@webassemblyjs/ast/lib/transform/wast-identifier-to-index"; | ||
const t = require("@webassemblyjs/ast"); | ||
import validateAST from "../validation"; | ||
@@ -22,4 +23,2 @@ const { CompileError } = require("../../errors"); | ||
) { | ||
validateAST(ast); | ||
this._ast = ast; | ||
@@ -43,3 +42,5 @@ this._start = start; | ||
traverse(ast, { | ||
validateAST(ast); | ||
t.traverse(ast, { | ||
ModuleExport({ node }: NodePath<ModuleExport>) { | ||
@@ -63,3 +64,30 @@ if (node.descr.type === "Func") { | ||
/** | ||
* Adds missing end instructions | ||
*/ | ||
t.traverse(ast, { | ||
Func({ node }: NodePath<Func>) { | ||
node.body.push(t.instruction("end")); | ||
}, | ||
Global({ node }: NodePath<Global>) { | ||
node.init.push(t.instruction("end")); | ||
}, | ||
IfInstruction({ node }: NodePath<IfInstruction>) { | ||
node.test.push(t.instruction("end")); | ||
node.consequent.push(t.instruction("end")); | ||
node.alternate.push(t.instruction("end")); | ||
}, | ||
BlockInstruction({ node }: NodePath<BlockInstruction>) { | ||
node.instr.push(t.instruction("end")); | ||
}, | ||
LoopInstruction({ node }: NodePath<LoopInstruction>) { | ||
node.instr.push(t.instruction("end")); | ||
} | ||
}); | ||
return new Module(ast, exports, imports, start); | ||
} |
@@ -30,4 +30,10 @@ // @flow | ||
// FIXME(sven): this shoudln't be needed, we need to inject our end | ||
// instructions after the validations | ||
if (instr.id === "end") { | ||
return true; | ||
} | ||
return false; | ||
}, true); | ||
} |
@@ -24,4 +24,10 @@ // @flow | ||
const last = instrs[instrs.length - 1]; | ||
// FIXME(sven): this shoudln't be needed, we need to inject our end | ||
// instructions after the validations | ||
let last = instrs[instrs.length - 1]; | ||
if (last.id === "end") { | ||
last = instrs[instrs.length - 2]; | ||
} | ||
// It's a ObjectInstruction | ||
@@ -28,0 +34,0 @@ if (typeof last.object === "string") { |
@@ -99,3 +99,3 @@ // @flow | ||
// function trace(depth, blockpc, i, frame) { | ||
// function trace(depth, pc, i, frame) { | ||
// function ident() { | ||
@@ -113,7 +113,6 @@ // let out = ""; | ||
// ident(), | ||
// `-------------- blockpc: ${blockpc} - depth: ${depth} --------------` | ||
// `-------------- pc: ${pc} - depth: ${depth} --------------` | ||
// ); | ||
// console.log(ident(), "instruction:", i.id); | ||
// console.log(ident(), "unwind reason:", frame._unwindReason); | ||
@@ -120,0 +119,0 @@ // console.log(ident(), "locals:"); |
// @flow | ||
type AssertArgs = {| | ||
MSG?: string, | ||
COND: boolean | ||
|}; | ||
declare function assert(AssertArgs): void; | ||
type AssertNItemsOnStackArgs = {| | ||
N: number | ||
|}; | ||
declare function assertNItemsOnStack(AssertNItemsOnStackArgs): void; | ||
import { Memory } from "../runtime/values/memory"; | ||
import { RuntimeError } from "../../errors"; | ||
const t = require("@webassemblyjs/ast"); | ||
MACRO( | ||
assert, | ||
`if (!COND) { | ||
throw new RuntimeError("Assertion error: " + (MSG || "unknown")); | ||
}` | ||
); | ||
MACRO( | ||
assertNItemsOnStack, | ||
`if (frame.values.length < N) { | ||
throw new RuntimeError("Assertion error: expected " + N + " on the stack, found " + frame.values.length); | ||
}` | ||
); | ||
const { | ||
@@ -23,7 +50,6 @@ binopi32, | ||
// TODO(sven): can remove asserts call at compile to gain perf in prod | ||
function assert(cond) { | ||
if (!cond) { | ||
throw new RuntimeError("Assertion error"); | ||
} | ||
// Syntactic sugar for the Syntactic sugar | ||
// TODO(sven): do it AOT? | ||
function addEndInstruction(body) { | ||
body.push(t.instruction("end")); | ||
} | ||
@@ -43,960 +69,1145 @@ | ||
export function executeStackFrame( | ||
frame: StackFrame, | ||
firstFrame: StackFrame, | ||
depth: number = 0 | ||
): ?StackLocal { | ||
function createAndExecuteChildStackFrame( | ||
instrs: Array<Instruction>, | ||
{ passCurrentContext }: createChildStackFrameOptions = {} | ||
): ?StackLocal { | ||
const childStackFrame = stackframe.createChildStackFrame(frame, instrs); | ||
let stack: Array<StackFrame> = [firstFrame]; | ||
let framepointer = 0; | ||
if (passCurrentContext === true) { | ||
childStackFrame.values = frame.values; | ||
childStackFrame.labels = frame.labels; | ||
} | ||
// eax | ||
let returnRegister = null; | ||
const res = executeStackFrame(childStackFrame, depth + 1); | ||
pushResult(res); | ||
} | ||
function run() { | ||
assertStackDepth(framepointer); | ||
function getLocalByIndex(index: number) { | ||
const local = frame.locals[index]; | ||
const frame = stack[framepointer]; | ||
if (typeof local === "undefined") { | ||
throw newRuntimeError( | ||
"Assertion error: no local value at index " + index | ||
); | ||
assert({ | ||
COND: frame !== undefined, | ||
MSG: "no frame at " + framepointer | ||
}); | ||
framepointer++; | ||
function getLocalByIndex(index: number) { | ||
const local = frame.locals[index]; | ||
if (typeof local === "undefined") { | ||
throw newRuntimeError( | ||
"Assertion error: no local value at index " + index | ||
); | ||
} | ||
frame.values.push(local); | ||
} | ||
frame.values.push(local); | ||
} | ||
function setLocalByIndex(index: number, value: StackLocal) { | ||
assert({ COND: typeof index === "number" }); | ||
function setLocalByIndex(index: number, value: StackLocal) { | ||
assert(typeof index === "number"); | ||
frame.locals[index] = value; | ||
} | ||
frame.locals[index] = value; | ||
} | ||
function pushResult(res: ?StackLocal) { | ||
if (typeof res === "undefined") { | ||
return; | ||
} | ||
function pushResult(res: ?StackLocal) { | ||
if (typeof res === "undefined") { | ||
return; | ||
frame.values.push(res); | ||
} | ||
frame.values.push(res); | ||
} | ||
function popArrayOfValTypes(types: Array<Valtype>): any { | ||
assertNItemsOnStack({ N: types.length }); | ||
function popArrayOfValTypes(types: Array<Valtype>): any { | ||
assertNItemsOnStack(frame.values, types.length); | ||
return types.map(type => { | ||
return pop1OfType(type); | ||
}); | ||
} | ||
return types.map(type => { | ||
return pop1OfType(type); | ||
}); | ||
} | ||
function pop1OfType(type: Valtype): any { | ||
assertNItemsOnStack({ N: 1 }); | ||
function pop1OfType(type: Valtype): any { | ||
assertNItemsOnStack(frame.values, 1); | ||
const v = frame.values.pop(); | ||
const v = frame.values.pop(); | ||
if (typeof type === "string" && v.type !== type) { | ||
throw newRuntimeError( | ||
"Internal failure: expected value of type " + | ||
type + | ||
" on top of the stack, type given: " + | ||
v.type | ||
); | ||
} | ||
if (typeof type === "string" && v.type !== type) { | ||
throw newRuntimeError( | ||
"Internal failure: expected value of type " + | ||
type + | ||
" on top of the stack, type given: " + | ||
v.type | ||
); | ||
return v; | ||
} | ||
return v; | ||
} | ||
function pop1(): any { | ||
assertNItemsOnStack({ N: 1 }); | ||
function pop1(): any { | ||
assertNItemsOnStack(frame.values, 1); | ||
return frame.values.pop(); | ||
} | ||
return frame.values.pop(); | ||
} | ||
function pop2(type1: Valtype, type2: Valtype): [any, any] { | ||
assertNItemsOnStack({ N: 2 }); | ||
function pop2(type1: Valtype, type2: Valtype): [any, any] { | ||
assertNItemsOnStack(frame.values, 2); | ||
const c2 = frame.values.pop(); | ||
const c1 = frame.values.pop(); | ||
const c2 = frame.values.pop(); | ||
const c1 = frame.values.pop(); | ||
if (c2.type !== type2) { | ||
throw newRuntimeError( | ||
"Internal failure: expected c2 value of type " + | ||
type2 + | ||
" on top of the stack, give type: " + | ||
c2.type | ||
); | ||
} | ||
if (c2.type !== type2) { | ||
throw newRuntimeError( | ||
"Internal failure: expected c2 value of type " + | ||
type2 + | ||
" on top of the stack, give type: " + | ||
c2.type | ||
); | ||
if (c1.type !== type1) { | ||
throw newRuntimeError( | ||
"Internal failure: expected c1 value of type " + | ||
type1 + | ||
" on top of the stack, give type: " + | ||
c1.type | ||
); | ||
} | ||
return [c1, c2]; | ||
} | ||
if (c1.type !== type1) { | ||
throw newRuntimeError( | ||
"Internal failure: expected c1 value of type " + | ||
type1 + | ||
" on top of the stack, give type: " + | ||
c1.type | ||
); | ||
function getLabel(index: Index): any { | ||
let code; | ||
if (index.type === "NumberLiteral") { | ||
const label: NumberLiteral = index; | ||
// WASM | ||
code = frame.labels.find(l => l.value.value === label.value); | ||
} else if (index.type === "Identifier") { | ||
const label: Identifier = index; | ||
// WAST | ||
code = frame.labels.find(l => { | ||
if (l.id == null) { | ||
return false; | ||
} | ||
return l.id.value === label.value; | ||
}); | ||
} | ||
if (typeof code !== "undefined") { | ||
return code.value; | ||
} | ||
} | ||
return [c1, c2]; | ||
} | ||
function br(label: Index) { | ||
const code = getLabel(label); | ||
function getLabel(index: Index): any { | ||
let code; | ||
if (typeof code === "undefined") { | ||
throw newRuntimeError(`Label ${label.value} doesn't exist`); | ||
} | ||
if (index.type === "NumberLiteral") { | ||
const label: NumberLiteral = index; | ||
// FIXME(sven): find a more generic way to handle label and its code | ||
// Currently func body and block instr*. | ||
const childStackFrame = stackframe.createChildStackFrame( | ||
frame, | ||
code.body || code.instr | ||
); | ||
// WASM | ||
code = frame.labels.find(l => l.value.value === label.value); | ||
} else if (index.type === "Identifier") { | ||
const label: Identifier = index; | ||
return executeStackFrame(childStackFrame, depth + 1); | ||
} | ||
// WAST | ||
code = frame.labels.find(l => { | ||
if (l.id == null) { | ||
return false; | ||
function getMemoryOffset(instruction) { | ||
if (instruction.namedArgs && instruction.namedArgs.offset) { | ||
const offset = instruction.namedArgs.offset.value; | ||
if (offset < 0) { | ||
throw newRuntimeError("offset must be positive"); | ||
} | ||
if (offset > 0xffffffff) { | ||
throw newRuntimeError( | ||
"offset must be less than or equal to 0xffffffff" | ||
); | ||
} | ||
return offset; | ||
} else { | ||
return 0; | ||
} | ||
} | ||
return l.id.value === label.value; | ||
}); | ||
function getMemory(): Memory { | ||
if (frame.originatingModule.memaddrs.length !== 1) { | ||
throw newRuntimeError("unknown memory"); | ||
} | ||
const memAddr = frame.originatingModule.memaddrs[0]; | ||
return frame.allocator.get(memAddr); | ||
} | ||
if (typeof code !== "undefined") { | ||
return code.value; | ||
function newRuntimeError(msg) { | ||
return new RuntimeError(msg); | ||
} | ||
} | ||
function br(label: Index) { | ||
const code = getLabel(label); | ||
function createAndExecuteChildStackFrame( | ||
instrs: Array<Instruction>, | ||
{ passCurrentContext }: createChildStackFrameOptions = {} | ||
): ?StackLocal { | ||
// FIXME(sven): that's wrong | ||
const frame = stack[framepointer - 1]; | ||
if (typeof code === "undefined") { | ||
throw newRuntimeError(`Label ${label.value} doesn't exist`); | ||
assert({ | ||
COND: frame !== undefined, | ||
MSG: "no active frame" | ||
}); | ||
const nextStackFrame = stackframe.createChildStackFrame(frame, instrs); | ||
if (passCurrentContext === true) { | ||
nextStackFrame.values = frame.values; | ||
nextStackFrame.labels = frame.labels; | ||
} | ||
// Push the frame on top of the stack | ||
stack[framepointer] = nextStackFrame; | ||
// Jump and execute the next frame | ||
run(); | ||
if (returnRegister !== null) { | ||
frame.values.push(...returnRegister); | ||
returnRegister = null; | ||
} | ||
} | ||
// FIXME(sven): find a more generic way to handle label and its code | ||
// Currently func body and block instr*. | ||
const childStackFrame = stackframe.createChildStackFrame( | ||
frame, | ||
code.body || code.instr | ||
); | ||
while (true) { | ||
const instruction = frame.code[frame._pc]; | ||
return executeStackFrame(childStackFrame, depth + 1); | ||
} | ||
assert({ | ||
COND: instruction !== undefined, | ||
MSG: `no instruction at pc ${frame._pc} in frame ${framepointer}` | ||
}); | ||
function getMemoryOffset(instruction) { | ||
if (instruction.namedArgs && instruction.namedArgs.offset) { | ||
const offset = instruction.namedArgs.offset.value; | ||
if (offset < 0) { | ||
throw newRuntimeError("offset must be positive"); | ||
if (typeof frame.trace === "function") { | ||
frame.trace(framepointer, frame._pc, instruction, frame); | ||
} | ||
if (offset > 0xffffffff) { | ||
throw newRuntimeError( | ||
"offset must be less than or equal to 0xffffffff" | ||
); | ||
frame._pc++; | ||
switch (instruction.type) { | ||
/** | ||
* Function declaration | ||
* | ||
* FIXME(sven): seems unspecified in the spec but it's required for the `call` | ||
* instruction. | ||
*/ | ||
case "Func": { | ||
const func = instruction; | ||
/** | ||
* Register the function into the stack frame labels | ||
*/ | ||
if (typeof func.name === "object") { | ||
if (func.name.type === "Identifier") { | ||
frame.labels.push({ | ||
value: func, | ||
arity: func.params.length, | ||
id: func.name | ||
}); | ||
} | ||
} | ||
break; | ||
} | ||
} | ||
return offset; | ||
} else { | ||
return 0; | ||
} | ||
} | ||
function getMemory(): Memory { | ||
if (frame.originatingModule.memaddrs.length !== 1) { | ||
throw newRuntimeError("unknown memory"); | ||
} | ||
switch (instruction.id) { | ||
case "const": { | ||
// https://webassembly.github.io/spec/core/exec/instructions.html#exec-const | ||
const memAddr = frame.originatingModule.memaddrs[0]; | ||
return frame.allocator.get(memAddr); | ||
} | ||
const n = instruction.args[0]; | ||
function newRuntimeError(msg) { | ||
return new RuntimeError(msg); | ||
} | ||
if (typeof n === "undefined") { | ||
throw newRuntimeError("const requires one argument, none given."); | ||
} | ||
assertStackDepth(depth); | ||
if ( | ||
n.type !== "NumberLiteral" && | ||
n.type !== "LongNumberLiteral" && | ||
n.type !== "FloatLiteral" | ||
) { | ||
throw newRuntimeError( | ||
"const: unsupported value of type: " + n.type | ||
); | ||
} | ||
while (frame._pc < frame.code.length) { | ||
const instruction = frame.code[frame._pc]; | ||
pushResult(castIntoStackLocalOfType(instruction.object, n.value)); | ||
switch (instruction.type) { | ||
/** | ||
* Function declaration | ||
* | ||
* FIXME(sven): seems unspecified in the spec but it's required for the `call` | ||
* instruction. | ||
*/ | ||
case "Func": { | ||
const func = instruction; | ||
break; | ||
} | ||
/** | ||
* Register the function into the stack frame labels | ||
* Control Instructions | ||
* | ||
* https://webassembly.github.io/spec/core/exec/instructions.html#control-instructions | ||
*/ | ||
if (typeof func.name === "object") { | ||
if (func.name.type === "Identifier") { | ||
frame.labels.push({ | ||
value: func, | ||
arity: func.params.length, | ||
id: func.name | ||
case "nop": { | ||
// Do nothing | ||
// https://webassembly.github.io/spec/core/exec/instructions.html#exec-nop | ||
break; | ||
} | ||
case "loop": { | ||
// https://webassembly.github.io/spec/core/exec/instructions.html#exec-loop | ||
const loop = instruction; | ||
assert({ | ||
COND: | ||
typeof loop.instr === "object" && | ||
typeof loop.instr.length !== "undefined" | ||
}); | ||
// 2. Enter the block instr∗ with label | ||
frame.labels.push({ | ||
value: loop, | ||
arity: 0, | ||
id: loop.label | ||
}); | ||
pushResult(label.createValue(loop.label.value)); | ||
if (loop.instr.length > 0) { | ||
createAndExecuteChildStackFrame(loop.instr, { | ||
passCurrentContext: true | ||
}); | ||
} | ||
break; | ||
} | ||
break; | ||
} | ||
} | ||
case "drop": { | ||
// https://webassembly.github.io/spec/core/exec/instructions.html#exec-drop | ||
switch (instruction.id) { | ||
case "const": { | ||
// https://webassembly.github.io/spec/core/exec/instructions.html#exec-const | ||
// 1. Assert: due to validation, a value is on the top of the stack. | ||
assertNItemsOnStack({ N: 1 }); | ||
const n = instruction.args[0]; | ||
// 2. Pop the value valval from the stack. | ||
pop1(); | ||
if (typeof n === "undefined") { | ||
throw newRuntimeError("const requires one argument, none given."); | ||
break; | ||
} | ||
if ( | ||
n.type !== "NumberLiteral" && | ||
n.type !== "LongNumberLiteral" && | ||
n.type !== "FloatLiteral" | ||
) { | ||
throw newRuntimeError("const: unsupported value of type: " + n.type); | ||
} | ||
case "call": { | ||
// According to the spec call doesn't support an Identifier as argument | ||
// but the Script syntax supports it. | ||
// https://webassembly.github.io/spec/core/exec/instructions.html#exec-call | ||
pushResult(castIntoStackLocalOfType(instruction.object, n.value)); | ||
const call = instruction; | ||
break; | ||
} | ||
if (call.index.type === "Identifier") { | ||
throw newRuntimeError( | ||
"Internal compiler error: Identifier argument in call must be " + | ||
"transformed to a NumberLiteral node" | ||
); | ||
} | ||
/** | ||
* Control Instructions | ||
* | ||
* https://webassembly.github.io/spec/core/exec/instructions.html#control-instructions | ||
*/ | ||
case "nop": { | ||
// Do nothing | ||
// https://webassembly.github.io/spec/core/exec/instructions.html#exec-nop | ||
break; | ||
} | ||
// WASM | ||
if (call.index.type === "NumberLiteral") { | ||
const index = call.index.value; | ||
case "loop": { | ||
// https://webassembly.github.io/spec/core/exec/instructions.html#exec-loop | ||
const loop = instruction; | ||
assert({ | ||
COND: typeof frame.originatingModule !== "undefined" | ||
}); | ||
assert( | ||
typeof loop.instr === "object" && | ||
typeof loop.instr.length !== "undefined" | ||
); | ||
// 2. Assert: due to validation, F.module.funcaddrs[x] exists. | ||
const funcaddr = frame.originatingModule.funcaddrs[index]; | ||
// 2. Enter the block instr∗ with label | ||
frame.labels.push({ | ||
value: loop, | ||
arity: 0, | ||
id: loop.label | ||
}); | ||
if (typeof funcaddr === "undefined") { | ||
throw newRuntimeError( | ||
`No function were found in module at address ${index}` | ||
); | ||
} | ||
pushResult(label.createValue(loop.label.value)); | ||
// 3. Let a be the function address F.module.funcaddrs[x] | ||
if (loop.instr.length > 0) { | ||
createAndExecuteChildStackFrame(loop.instr, { | ||
passCurrentContext: true | ||
}); | ||
const subroutine = frame.allocator.get(funcaddr); | ||
if (typeof subroutine !== "object") { | ||
throw newRuntimeError( | ||
`Cannot call function at address ${funcaddr}: not a function` | ||
); | ||
} | ||
// 4. Invoke the function instance at address a | ||
// FIXME(sven): assert that res has type of resultType | ||
const [argTypes, resultType] = subroutine.type; | ||
const args = popArrayOfValTypes(argTypes); | ||
if (subroutine.isExternal === false) { | ||
createAndExecuteChildStackFrame(subroutine.code); | ||
} else { | ||
const res = subroutine.code(args.map(arg => arg.value)); | ||
if (typeof res !== "undefined") { | ||
pushResult(castIntoStackLocalOfType(resultType, res)); | ||
} | ||
} | ||
} | ||
break; | ||
} | ||
break; | ||
} | ||
case "block": { | ||
// https://webassembly.github.io/spec/core/exec/instructions.html#blocks | ||
case "drop": { | ||
// https://webassembly.github.io/spec/core/exec/instructions.html#exec-drop | ||
const block = instruction; | ||
// 1. Assert: due to validation, a value is on the top of the stack. | ||
assertNItemsOnStack(frame.values, 1); | ||
/** | ||
* Used to keep track of the number of values added on top of the stack | ||
* because we need to remove the label after the execution of this block. | ||
*/ | ||
let numberOfValuesAddedOnTopOfTheStack = 0; | ||
// 2. Pop the value valval from the stack. | ||
pop1(); | ||
// 2. Enter the block instr∗ with label | ||
frame.labels.push({ | ||
value: block, | ||
arity: 0, | ||
id: block.label | ||
}); | ||
break; | ||
} | ||
if (block.label.type === "Identifier") { | ||
pushResult(label.createValue(block.label.value)); | ||
} else { | ||
throw newRuntimeError("Block has no id"); | ||
} | ||
case "call": { | ||
// According to the spec call doesn't support an Identifier as argument | ||
// but the Script syntax supports it. | ||
// https://webassembly.github.io/spec/core/exec/instructions.html#exec-call | ||
assert({ | ||
COND: | ||
typeof block.instr === "object" && | ||
typeof block.instr.length !== "undefined" | ||
}); | ||
const call = instruction; | ||
if (block.instr.length > 0) { | ||
const oldStackSize = frame.values.length; | ||
if (call.index.type === "Identifier") { | ||
throw newRuntimeError( | ||
"Internal compiler error: Identifier argument in call must be " + | ||
"transformed to a NumberLiteral node" | ||
createAndExecuteChildStackFrame(block.instr, { | ||
passCurrentContext: true | ||
}); | ||
numberOfValuesAddedOnTopOfTheStack = | ||
frame.values.length - oldStackSize; | ||
} | ||
/** | ||
* Wen exiting the block | ||
* | ||
* > Let m be the number of values on the top of the stack | ||
* | ||
* The Stack (values) are seperated by StackFrames and we are running on | ||
* one single thread, there's no need to check if values were added. | ||
* | ||
* We tracked it in numberOfValuesAddedOnTopOfTheStack anyway. | ||
*/ | ||
const topOfTheStack = frame.values.slice( | ||
frame.values.length - numberOfValuesAddedOnTopOfTheStack | ||
); | ||
} | ||
// WASM | ||
if (call.index.type === "NumberLiteral") { | ||
const index = call.index.value; | ||
frame.values.splice( | ||
frame.values.length - numberOfValuesAddedOnTopOfTheStack | ||
); | ||
assert(typeof frame.originatingModule !== "undefined"); | ||
// 3. Assert: due to validation, the label LL is now on the top of the stack. | ||
// 4. Pop the label from the stack. | ||
pop1OfType("label"); | ||
// 2. Assert: due to validation, F.module.funcaddrs[x] exists. | ||
const funcaddr = frame.originatingModule.funcaddrs[index]; | ||
frame.values = [...frame.values, ...topOfTheStack]; | ||
if (typeof funcaddr === "undefined") { | ||
throw newRuntimeError( | ||
`No function were found in module at address ${index}` | ||
); | ||
} | ||
// Remove label | ||
frame.labels = frame.labels.filter(x => { | ||
if (x.id == null) { | ||
return true; | ||
} | ||
// 3. Let a be the function address F.module.funcaddrs[x] | ||
return x.id.value !== block.label.value; | ||
}); | ||
const subroutine = frame.allocator.get(funcaddr); | ||
break; | ||
} | ||
if (typeof subroutine !== "object") { | ||
case "br": { | ||
// https://webassembly.github.io/spec/core/exec/instructions.html#exec-br | ||
const [label, ...children] = instruction.args; | ||
if (label.type === "Identifier") { | ||
throw newRuntimeError( | ||
`Cannot call function at address ${funcaddr}: not a function` | ||
"Internal compiler error: Identifier argument in br must be " + | ||
"transformed to a NumberLiteral node" | ||
); | ||
} | ||
// 4. Invoke the function instance at address a | ||
const l = label.value; | ||
// FIXME(sven): assert that res has type of resultType | ||
const [argTypes, resultType] = subroutine.type; | ||
// 1. Assert: due to validation, the stack contains at least l+1 labels. | ||
assertNItemsOnStack({ N: l + 1 }); | ||
const args = popArrayOfValTypes(argTypes); | ||
// 2. Let L be the l-th label appearing on the stack, starting from the top and counting from zero. | ||
let seenLabels = 0; | ||
let labelidx = { value: "unknown" }; | ||
// for (var i = 0, len = frame.values.length; i < len; i++) { | ||
for (let i = frame.values.length; i--; ) { | ||
if (frame.values[i].type === "label") { | ||
if (seenLabels === l) { | ||
labelidx = frame.values[i]; | ||
break; | ||
} | ||
if (subroutine.isExternal === false) { | ||
createAndExecuteChildStackFrame(subroutine.code); | ||
} else { | ||
const res = subroutine.code(args.map(arg => arg.value)); | ||
if (typeof res !== "undefined") { | ||
pushResult(castIntoStackLocalOfType(resultType, res)); | ||
seenLabels++; | ||
} | ||
} | ||
} | ||
break; | ||
} | ||
// $FlowIgnore | ||
const L = frame.labels.find(x => x.id.value === labelidx.value); | ||
case "block": { | ||
// https://webassembly.github.io/spec/core/exec/instructions.html#blocks | ||
if (typeof L === "undefined") { | ||
throw newRuntimeError(`br: unknown label ${labelidx.value}`); | ||
} | ||
const block = instruction; | ||
// 3. Let n be the arity of L. | ||
const n = L.arity; | ||
/** | ||
* Used to keep track of the number of values added on top of the stack | ||
* because we need to remove the label after the execution of this block. | ||
*/ | ||
let numberOfValuesAddedOnTopOfTheStack = 0; | ||
// 4. Assert: due to validation, there are at least nn values on the top of the stack. | ||
assertNItemsOnStack({ N: n }); | ||
// 2. Enter the block instr∗ with label | ||
frame.labels.push({ | ||
value: block, | ||
arity: 0, | ||
id: block.label | ||
}); | ||
// 5. Pop the values valn from the stack | ||
const val = frame.values[n]; | ||
if (block.label.type === "Identifier") { | ||
pushResult(label.createValue(block.label.value)); | ||
} else { | ||
throw newRuntimeError("Block has no id"); | ||
} | ||
const bottomOfTheStack = frame.values.slice(0, n); | ||
const topOfTheStack = frame.values.slice(n + 1); | ||
assert( | ||
typeof block.instr === "object" && | ||
typeof block.instr.length !== "undefined" | ||
); | ||
frame.values = [...bottomOfTheStack, ...topOfTheStack]; | ||
if (block.instr.length > 0) { | ||
const oldStackSize = frame.values.length; | ||
// 6. Repeat l+1 times: | ||
for (let i = 0; i < l + 1; i++) { | ||
// a. While the top of the stack is a value, do: | ||
// i. Pop the value from the stack | ||
const value = frame.values[frame.values.length - 1]; | ||
createAndExecuteChildStackFrame(block.instr, { | ||
if (typeof value === "undefined") { | ||
break; | ||
} | ||
if (value.type !== "label") { | ||
pop1(); | ||
} | ||
} | ||
// b. Assert: due to validation, the top of the stack now is a label. | ||
// c. Pop the label from the stack. | ||
pop1OfType("label"); | ||
// 7. Push the values valn to the stack. | ||
pushResult(val); | ||
// 0 is the current frame, 1 is it's parent. | ||
stack = stack.slice(0, -(l + 1)); | ||
framepointer -= l + 1; | ||
// execute childrens | ||
addEndInstruction(children); | ||
createAndExecuteChildStackFrame(children, { | ||
passCurrentContext: true | ||
}); | ||
numberOfValuesAddedOnTopOfTheStack = | ||
frame.values.length - oldStackSize; | ||
return; | ||
} | ||
/** | ||
* Wen exiting the block | ||
* | ||
* > Let m be the number of values on the top of the stack | ||
* | ||
* The Stack (values) are seperated by StackFrames and we are running on | ||
* one single thread, there's no need to check if values were added. | ||
* | ||
* We tracked it in numberOfValuesAddedOnTopOfTheStack anyway. | ||
*/ | ||
const topOfTheStack = frame.values.slice( | ||
frame.values.length - numberOfValuesAddedOnTopOfTheStack | ||
); | ||
case "br_if": { | ||
const [label, ...children] = instruction.args; | ||
frame.values.splice( | ||
frame.values.length - numberOfValuesAddedOnTopOfTheStack | ||
); | ||
// execute childrens | ||
addEndInstruction(children); | ||
// 3. Assert: due to validation, the label LL is now on the top of the stack. | ||
// 4. Pop the label from the stack. | ||
pop1OfType("label"); | ||
createAndExecuteChildStackFrame(children, { | ||
passCurrentContext: true | ||
}); | ||
frame.values = [...frame.values, ...topOfTheStack]; | ||
// 1. Assert: due to validation, a value of type i32 is on the top of the stack. | ||
// 2. Pop the value ci32.const c from the stack. | ||
const c = pop1OfType("i32"); | ||
// Remove label | ||
frame.labels = frame.labels.filter(x => { | ||
if (x.id == null) { | ||
return true; | ||
if (!c.value.eqz().isTrue()) { | ||
// 3. If c is non-zero, then | ||
// 3. a. Execute the instruction (br l). | ||
const res = br(label); | ||
pushResult(res); | ||
} else { | ||
// 4. Else: | ||
// 4. a. Do nothing. | ||
} | ||
return x.id.value !== block.label.value; | ||
}); | ||
break; | ||
} | ||
break; | ||
} | ||
case "if": { | ||
if (instruction.test.length > 0) { | ||
createAndExecuteChildStackFrame(instruction.test); | ||
} | ||
case "br_if": { | ||
const [label] = instruction.args; | ||
// 1. Assert: due to validation, a value of value type i32 is on the top of the stack. | ||
// 2. Pop the value i32.const from the stack. | ||
const c = pop1OfType("i32"); | ||
// 1. Assert: due to validation, a value of type i32 is on the top of the stack. | ||
// 2. Pop the value ci32.const c from the stack. | ||
const c = pop1OfType("i32"); | ||
if (c.value.eqz().isTrue() === false) { | ||
/** | ||
* Execute consequent | ||
*/ | ||
createAndExecuteChildStackFrame(instruction.consequent); | ||
} else if ( | ||
typeof instruction.alternate !== "undefined" && | ||
instruction.alternate.length > 0 | ||
) { | ||
/** | ||
* Execute alternate | ||
*/ | ||
createAndExecuteChildStackFrame(instruction.alternate); | ||
} | ||
if (!c.value.eqz().isTrue()) { | ||
// 3. If c is non-zero, then | ||
// 3. a. Execute the instruction (br l). | ||
const res = br(label); | ||
break; | ||
} | ||
pushResult(res); | ||
} else { | ||
// 4. Else: | ||
// 4. a. Do nothing. | ||
/** | ||
* Administrative Instructions | ||
* | ||
* https://webassembly.github.io/spec/core/exec/runtime.html#administrative-instructions | ||
*/ | ||
case "unreachable": | ||
// https://webassembly.github.io/spec/core/exec/instructions.html#exec-unreachable | ||
case "trap": { | ||
// signalling abrupt termination | ||
// https://webassembly.github.io/spec/core/exec/runtime.html#syntax-trap | ||
throw createTrap(); | ||
} | ||
break; | ||
} | ||
case "local": { | ||
const [valtype] = instruction.args; | ||
case "if": { | ||
if (instruction.test.length > 0) { | ||
createAndExecuteChildStackFrame(instruction.test); | ||
const init = castIntoStackLocalOfType(valtype.name, 0); | ||
frame.locals.push(init); | ||
break; | ||
} | ||
// 1. Assert: due to validation, a value of value type i32 is on the top of the stack. | ||
// 2. Pop the value i32.const from the stack. | ||
const c = pop1OfType("i32"); | ||
/** | ||
* Memory Instructions | ||
* | ||
* https://webassembly.github.io/spec/core/exec/instructions.html#memory-instructions | ||
*/ | ||
case "get_local": { | ||
// https://webassembly.github.io/spec/core/exec/instructions.html#exec-get-local | ||
const index = instruction.args[0]; | ||
if (c.value.eqz().isTrue() === false) { | ||
/** | ||
* Execute consequent | ||
*/ | ||
createAndExecuteChildStackFrame(instruction.consequent); | ||
} else if ( | ||
typeof instruction.alternate !== "undefined" && | ||
instruction.alternate.length > 0 | ||
) { | ||
/** | ||
* Execute alternate | ||
*/ | ||
createAndExecuteChildStackFrame(instruction.alternate); | ||
if (typeof index === "undefined") { | ||
throw newRuntimeError( | ||
"get_local requires one argument, none given." | ||
); | ||
} | ||
if (index.type === "NumberLiteral" || index.type === "FloatLiteral") { | ||
getLocalByIndex(index.value); | ||
} else { | ||
throw newRuntimeError( | ||
"get_local: unsupported index of type: " + index.type | ||
); | ||
} | ||
break; | ||
} | ||
break; | ||
} | ||
case "set_local": { | ||
// https://webassembly.github.io/spec/core/exec/instructions.html#exec-set-local | ||
const index = instruction.args[0]; | ||
const init = instruction.args[1]; | ||
/** | ||
* Administrative Instructions | ||
* | ||
* https://webassembly.github.io/spec/core/exec/runtime.html#administrative-instructions | ||
*/ | ||
case "unreachable": | ||
// https://webassembly.github.io/spec/core/exec/instructions.html#exec-unreachable | ||
case "trap": { | ||
// signalling abrupt termination | ||
// https://webassembly.github.io/spec/core/exec/runtime.html#syntax-trap | ||
throw createTrap(); | ||
} | ||
if (typeof init !== "undefined" && init.type === "Instr") { | ||
// WAST | ||
case "local": { | ||
const [valtype] = instruction.args; | ||
const code = [init]; | ||
addEndInstruction(code); | ||
const init = castIntoStackLocalOfType(valtype.name, 0); | ||
frame.locals.push(init); | ||
createAndExecuteChildStackFrame(code, { | ||
passCurrentContext: true | ||
}); | ||
break; | ||
} | ||
const res = pop1(); | ||
setLocalByIndex(index.value, res); | ||
} else if (index.type === "NumberLiteral") { | ||
// WASM | ||
/** | ||
* Memory Instructions | ||
* | ||
* https://webassembly.github.io/spec/core/exec/instructions.html#memory-instructions | ||
*/ | ||
case "get_local": { | ||
// https://webassembly.github.io/spec/core/exec/instructions.html#exec-get-local | ||
const index = instruction.args[0]; | ||
// 4. Pop the value val from the stack | ||
const val = pop1(); | ||
if (typeof index === "undefined") { | ||
throw newRuntimeError("get_local requires one argument, none given."); | ||
} | ||
// 5. Replace F.locals[x] with the value val | ||
setLocalByIndex(index.value, val); | ||
} else { | ||
throw newRuntimeError( | ||
"set_local: unsupported index of type: " + index.type | ||
); | ||
} | ||
if (index.type === "NumberLiteral" || index.type === "FloatLiteral") { | ||
getLocalByIndex(index.value); | ||
} else { | ||
throw newRuntimeError( | ||
"get_local: unsupported index of type: " + index.type | ||
); | ||
break; | ||
} | ||
break; | ||
} | ||
case "tee_local": { | ||
// https://webassembly.github.io/spec/core/exec/instructions.html#exec-tee-local | ||
const index = instruction.args[0]; | ||
const init = instruction.args[1]; | ||
case "set_local": { | ||
// https://webassembly.github.io/spec/core/exec/instructions.html#exec-set-local | ||
const index = instruction.args[0]; | ||
const init = instruction.args[1]; | ||
if (typeof init !== "undefined" && init.type === "Instr") { | ||
// WAST | ||
if (typeof init !== "undefined" && init.type === "Instr") { | ||
// WAST | ||
const code = [init]; | ||
addEndInstruction(code); | ||
createAndExecuteChildStackFrame([init], { passCurrentContext: true }); | ||
createAndExecuteChildStackFrame(code, { | ||
passCurrentContext: true | ||
}); | ||
const res = pop1(); | ||
setLocalByIndex(index.value, res); | ||
} else if (index.type === "NumberLiteral") { | ||
// WASM | ||
const res = pop1(); | ||
setLocalByIndex(index.value, res); | ||
// 4. Pop the value val from the stack | ||
const val = pop1(); | ||
pushResult(res); | ||
} else if (index.type === "NumberLiteral") { | ||
// WASM | ||
// 5. Replace F.locals[x] with the value val | ||
setLocalByIndex(index.value, val); | ||
} else { | ||
throw newRuntimeError( | ||
"set_local: unsupported index of type: " + index.type | ||
); | ||
} | ||
// 1. Assert: due to validation, a value is on the top of the stack. | ||
// 2. Pop the value val from the stack. | ||
const val = pop1(); | ||
break; | ||
} | ||
// 3. Push the value valval to the stack. | ||
pushResult(val); | ||
case "tee_local": { | ||
// https://webassembly.github.io/spec/core/exec/instructions.html#exec-tee-local | ||
const index = instruction.args[0]; | ||
const init = instruction.args[1]; | ||
// 4. Push the value valval to the stack. | ||
pushResult(val); | ||
if (typeof init !== "undefined" && init.type === "Instr") { | ||
// WAST | ||
// 5. Execute the instruction (set_local x). | ||
// 5. 4. Pop the value val from the stack | ||
const val2 = pop1(); | ||
createAndExecuteChildStackFrame([init], { passCurrentContext: true }); | ||
// 5. 5. Replace F.locals[x] with the value val | ||
setLocalByIndex(index.value, val2); | ||
} else { | ||
throw newRuntimeError( | ||
"tee_local: unsupported index of type: " + index.type | ||
); | ||
} | ||
const res = pop1(); | ||
setLocalByIndex(index.value, res); | ||
break; | ||
} | ||
pushResult(res); | ||
} else if (index.type === "NumberLiteral") { | ||
// WASM | ||
case "set_global": { | ||
// https://webassembly.github.io/spec/core/exec/instructions.html#exec-set-global | ||
const [index, right] = instruction.args; | ||
// 1. Assert: due to validation, a value is on the top of the stack. | ||
// 2. Pop the value val from the stack. | ||
const val = pop1(); | ||
// Interpret right branch first if it's a child instruction | ||
if (typeof right !== "undefined") { | ||
const code = [right]; | ||
addEndInstruction(code); | ||
// 3. Push the value valval to the stack. | ||
pushResult(val); | ||
createAndExecuteChildStackFrame(code, { | ||
passCurrentContext: true | ||
}); | ||
} | ||
// 4. Push the value valval to the stack. | ||
pushResult(val); | ||
// 2. Assert: due to validation, F.module.globaladdrs[x] exists. | ||
const globaladdr = frame.originatingModule.globaladdrs[index.value]; | ||
// 5. Execute the instruction (set_local x). | ||
// 5. 4. Pop the value val from the stack | ||
const val2 = pop1(); | ||
if (typeof globaladdr === "undefined") { | ||
throw newRuntimeError(`Global address ${index.value} not found`); | ||
} | ||
// 5. 5. Replace F.locals[x] with the value val | ||
setLocalByIndex(index.value, val2); | ||
} else { | ||
throw newRuntimeError( | ||
"tee_local: unsupported index of type: " + index.type | ||
); | ||
} | ||
// 4. Assert: due to validation, S.globals[a] exists. | ||
const globalinst = frame.allocator.get(globaladdr); | ||
break; | ||
} | ||
if (typeof globalinst !== "object") { | ||
throw newRuntimeError( | ||
`Unexpected data for global at ${globaladdr}` | ||
); | ||
} | ||
case "set_global": { | ||
// https://webassembly.github.io/spec/core/exec/instructions.html#exec-set-global | ||
const [index, right] = instruction.args; | ||
// 7. Pop the value val from the stack. | ||
const val = pop1(); | ||
// Interpret right branch first if it's a child instruction | ||
if (typeof right !== "undefined") { | ||
createAndExecuteChildStackFrame([right], { | ||
passCurrentContext: true | ||
}); | ||
} | ||
// 8. Replace glob.value with the value val. | ||
globalinst.value = val.value; | ||
// 2. Assert: due to validation, F.module.globaladdrs[x] exists. | ||
const globaladdr = frame.originatingModule.globaladdrs[index.value]; | ||
frame.allocator.set(globaladdr, globalinst); | ||
if (typeof globaladdr === "undefined") { | ||
throw newRuntimeError(`Global address ${index.value} not found`); | ||
break; | ||
} | ||
// 4. Assert: due to validation, S.globals[a] exists. | ||
const globalinst = frame.allocator.get(globaladdr); | ||
case "get_global": { | ||
// https://webassembly.github.io/spec/core/exec/instructions.html#exec-get-global | ||
const index = instruction.args[0]; | ||
if (typeof globalinst !== "object") { | ||
throw newRuntimeError(`Unexpected data for global at ${globaladdr}`); | ||
} | ||
// 2. Assert: due to validation, F.module.globaladdrs[x] exists. | ||
const globaladdr = frame.originatingModule.globaladdrs[index.value]; | ||
// 7. Pop the value val from the stack. | ||
const val = pop1(); | ||
if (typeof globaladdr === "undefined") { | ||
throw newRuntimeError(`Unknown global at index: ${index.value}`); | ||
} | ||
// 8. Replace glob.value with the value val. | ||
globalinst.value = val.value; | ||
// 4. Assert: due to validation, S.globals[a] exists. | ||
const globalinst = frame.allocator.get(globaladdr); | ||
frame.allocator.set(globaladdr, globalinst); | ||
if (typeof globalinst !== "object") { | ||
throw newRuntimeError( | ||
`Unexpected data for global at ${globaladdr}` | ||
); | ||
} | ||
break; | ||
} | ||
// 7. Pop the value val from the stack. | ||
pushResult(globalinst); | ||
case "get_global": { | ||
// https://webassembly.github.io/spec/core/exec/instructions.html#exec-get-global | ||
const index = instruction.args[0]; | ||
break; | ||
} | ||
// 2. Assert: due to validation, F.module.globaladdrs[x] exists. | ||
const globaladdr = frame.originatingModule.globaladdrs[index.value]; | ||
case "return": { | ||
const { args } = instruction; | ||
if (typeof globaladdr === "undefined") { | ||
throw newRuntimeError(`Unknown global at index: ${index.value}`); | ||
if (args.length > 0) { | ||
addEndInstruction(args); | ||
createAndExecuteChildStackFrame(args, { passCurrentContext: true }); | ||
} | ||
// Abort execution and return the first item on the stack | ||
returnRegister = [pop1()]; | ||
return; | ||
} | ||
// 4. Assert: due to validation, S.globals[a] exists. | ||
const globalinst = frame.allocator.get(globaladdr); | ||
/** | ||
* Memory operations | ||
*/ | ||
if (typeof globalinst !== "object") { | ||
throw newRuntimeError(`Unexpected data for global at ${globaladdr}`); | ||
} | ||
// https://webassembly.github.io/spec/core/exec/instructions.html#exec-storen | ||
case "store": | ||
case "store8": | ||
case "store16": | ||
case "store32": { | ||
const { id, object, args } = instruction; | ||
// 7. Pop the value val from the stack. | ||
pushResult(globalinst); | ||
// Interpret children first | ||
// only WAST | ||
if (typeof args !== "undefined" && args.length > 0) { | ||
addEndInstruction(args); | ||
createAndExecuteChildStackFrame(args, { passCurrentContext: true }); | ||
} | ||
break; | ||
} | ||
const memory = getMemory(); | ||
case "return": { | ||
const { args } = instruction; | ||
const [c1, c2] = pop2("i32", object); | ||
const ptr = c1.value.toNumber() + getMemoryOffset(instruction); | ||
let valueBuffer = c2.value.toByteArray(); | ||
if (args.length > 0) { | ||
createAndExecuteChildStackFrame(args, { passCurrentContext: true }); | ||
} | ||
switch (id) { | ||
case "store8": | ||
valueBuffer = valueBuffer.slice(0, 1); | ||
break; | ||
case "store16": | ||
valueBuffer = valueBuffer.slice(0, 2); | ||
break; | ||
case "store32": | ||
valueBuffer = valueBuffer.slice(0, 4); | ||
break; | ||
} | ||
// Abort execution and return the first item on the stack | ||
return pop1(); | ||
} | ||
if (ptr + valueBuffer.length > memory.buffer.byteLength) { | ||
throw newRuntimeError("memory access out of bounds"); | ||
} | ||
/** | ||
* Memory operations | ||
*/ | ||
const memoryBuffer = new Uint8Array(memory.buffer); | ||
// https://webassembly.github.io/spec/core/exec/instructions.html#exec-storen | ||
case "store": | ||
case "store8": | ||
case "store16": | ||
case "store32": { | ||
const { id, object, args } = instruction; | ||
// Interpret children first | ||
// only WAST | ||
if (typeof args !== "undefined" && args.length > 0) { | ||
createAndExecuteChildStackFrame(args, { passCurrentContext: true }); | ||
// load / store use little-endian order | ||
for (let ptrOffset = 0; ptrOffset < valueBuffer.length; ptrOffset++) { | ||
memoryBuffer[ptr + ptrOffset] = valueBuffer[ptrOffset]; | ||
} | ||
break; | ||
} | ||
const memory = getMemory(); | ||
// https://webassembly.github.io/spec/core/exec/instructions.html#and | ||
case "load": | ||
case "load16_s": | ||
case "load16_u": | ||
case "load8_s": | ||
case "load8_u": | ||
case "load32_s": | ||
case "load32_u": { | ||
const { id, object, args } = instruction; | ||
const [c1, c2] = pop2("i32", object); | ||
const ptr = c1.value.toNumber() + getMemoryOffset(instruction); | ||
let valueBuffer = c2.value.toByteArray(); | ||
// Interpret children first | ||
// only WAST | ||
if (typeof args !== "undefined" && args.length > 0) { | ||
addEndInstruction(args); | ||
createAndExecuteChildStackFrame(args, { passCurrentContext: true }); | ||
} | ||
switch (id) { | ||
case "store8": | ||
valueBuffer = valueBuffer.slice(0, 1); | ||
break; | ||
case "store16": | ||
valueBuffer = valueBuffer.slice(0, 2); | ||
break; | ||
case "store32": | ||
valueBuffer = valueBuffer.slice(0, 4); | ||
break; | ||
} | ||
const memory = getMemory(); | ||
if (ptr + valueBuffer.length > memory.buffer.byteLength) { | ||
throw newRuntimeError("memory access out of bounds"); | ||
} | ||
const ptr = | ||
pop1OfType("i32").value.toNumber() + getMemoryOffset(instruction); | ||
const memoryBuffer = new Uint8Array(memory.buffer); | ||
// for i32 / i64 ops, handle extended load | ||
let extend = 0; | ||
// for i64 values, increase the bitshift by 4 bytes | ||
const extendOffset = object === "i32" ? 0 : 32; | ||
let signed = false; | ||
switch (id) { | ||
case "load16_s": | ||
extend = 16 + extendOffset; | ||
signed = true; | ||
break; | ||
case "load16_u": | ||
extend = 16 + extendOffset; | ||
signed = false; | ||
break; | ||
case "load8_s": | ||
extend = 24 + extendOffset; | ||
signed = true; | ||
break; | ||
case "load8_u": | ||
extend = 24 + extendOffset; | ||
signed = false; | ||
break; | ||
case "load32_u": | ||
extend = 0 + extendOffset; | ||
signed = false; | ||
break; | ||
case "load32_s": | ||
extend = 0 + extendOffset; | ||
signed = true; | ||
break; | ||
} | ||
// load / store use little-endian order | ||
for (let ptrOffset = 0; ptrOffset < valueBuffer.length; ptrOffset++) { | ||
memoryBuffer[ptr + ptrOffset] = valueBuffer[ptrOffset]; | ||
} | ||
break; | ||
} | ||
// check for memory access out of bounds | ||
switch (object) { | ||
case "i32": | ||
case "f32": | ||
if (ptr + 4 > memory.buffer.byteLength) { | ||
throw newRuntimeError("memory access out of bounds"); | ||
} | ||
break; | ||
case "i64": | ||
case "f64": | ||
if (ptr + 8 > memory.buffer.byteLength) { | ||
throw newRuntimeError("memory access out of bounds"); | ||
} | ||
break; | ||
} | ||
// https://webassembly.github.io/spec/core/exec/instructions.html#and | ||
case "load": | ||
case "load16_s": | ||
case "load16_u": | ||
case "load8_s": | ||
case "load8_u": | ||
case "load32_s": | ||
case "load32_u": { | ||
const { id, object, args } = instruction; | ||
switch (object) { | ||
case "i32": | ||
pushResult( | ||
i32.createValueFromArrayBuffer( | ||
memory.buffer, | ||
ptr, | ||
extend, | ||
signed | ||
) | ||
); | ||
break; | ||
case "i64": | ||
pushResult( | ||
i64.createValueFromArrayBuffer( | ||
memory.buffer, | ||
ptr, | ||
extend, | ||
signed | ||
) | ||
); | ||
break; | ||
case "f32": | ||
pushResult(f32.createValueFromArrayBuffer(memory.buffer, ptr)); | ||
break; | ||
case "f64": | ||
pushResult(f64.createValueFromArrayBuffer(memory.buffer, ptr)); | ||
break; | ||
} | ||
// Interpret children first | ||
// only WAST | ||
if (typeof args !== "undefined" && args.length > 0) { | ||
createAndExecuteChildStackFrame(args, { passCurrentContext: true }); | ||
break; | ||
} | ||
const memory = getMemory(); | ||
/** | ||
* Binary operations | ||
*/ | ||
case "add": | ||
case "mul": | ||
case "sub": | ||
/** | ||
* There are two seperated operation for both signed and unsigned integer, | ||
* but since the host environment will handle that, we don't have too :) | ||
*/ | ||
case "div_s": | ||
case "div_u": | ||
case "rem_s": | ||
case "rem_u": | ||
case "shl": | ||
case "shr_s": | ||
case "shr_u": | ||
case "rotl": | ||
case "rotr": | ||
case "div": | ||
case "min": | ||
case "max": | ||
case "copysign": | ||
case "or": | ||
case "xor": | ||
case "and": | ||
case "eq": | ||
case "ne": | ||
case "lt_s": | ||
case "lt_u": | ||
case "le_s": | ||
case "le_u": | ||
case "gt": | ||
case "gt_s": | ||
case "gt_u": | ||
case "ge_s": | ||
case "ge_u": { | ||
let binopFn; | ||
switch (instruction.object) { | ||
case "i32": | ||
binopFn = binopi32; | ||
break; | ||
case "i64": | ||
binopFn = binopi64; | ||
break; | ||
case "f32": | ||
binopFn = binopf32; | ||
break; | ||
case "f64": | ||
binopFn = binopf64; | ||
break; | ||
default: | ||
throw createTrap( | ||
"Unsupported operation " + | ||
instruction.id + | ||
" on " + | ||
instruction.object | ||
); | ||
} | ||
const ptr = | ||
pop1OfType("i32").value.toNumber() + getMemoryOffset(instruction); | ||
const [left, right] = instruction.args; | ||
// for i32 / i64 ops, handle extended load | ||
let extend = 0; | ||
// for i64 values, increase the bitshift by 4 bytes | ||
const extendOffset = object === "i32" ? 0 : 32; | ||
let signed = false; | ||
switch (id) { | ||
case "load16_s": | ||
extend = 16 + extendOffset; | ||
signed = true; | ||
break; | ||
case "load16_u": | ||
extend = 16 + extendOffset; | ||
signed = false; | ||
break; | ||
case "load8_s": | ||
extend = 24 + extendOffset; | ||
signed = true; | ||
break; | ||
case "load8_u": | ||
extend = 24 + extendOffset; | ||
signed = false; | ||
break; | ||
case "load32_u": | ||
extend = 0 + extendOffset; | ||
signed = false; | ||
break; | ||
case "load32_s": | ||
extend = 0 + extendOffset; | ||
signed = true; | ||
break; | ||
} | ||
// Interpret left branch first if it's a child instruction | ||
if (typeof left !== "undefined") { | ||
const code = [left]; | ||
addEndInstruction(code); | ||
// check for memory access out of bounds | ||
switch (object) { | ||
case "i32": | ||
case "f32": | ||
if (ptr + 4 > memory.buffer.byteLength) { | ||
throw newRuntimeError("memory access out of bounds"); | ||
} | ||
break; | ||
case "i64": | ||
case "f64": | ||
if (ptr + 8 > memory.buffer.byteLength) { | ||
throw newRuntimeError("memory access out of bounds"); | ||
} | ||
break; | ||
} | ||
createAndExecuteChildStackFrame(code, { | ||
passCurrentContext: true | ||
}); | ||
} | ||
switch (object) { | ||
case "i32": | ||
pushResult( | ||
i32.createValueFromArrayBuffer(memory.buffer, ptr, extend, signed) | ||
); | ||
break; | ||
case "i64": | ||
pushResult( | ||
i64.createValueFromArrayBuffer(memory.buffer, ptr, extend, signed) | ||
); | ||
break; | ||
case "f32": | ||
pushResult(f32.createValueFromArrayBuffer(memory.buffer, ptr)); | ||
break; | ||
case "f64": | ||
pushResult(f64.createValueFromArrayBuffer(memory.buffer, ptr)); | ||
break; | ||
} | ||
// Interpret right branch first if it's a child instruction | ||
if (typeof right !== "undefined") { | ||
const code = [right]; | ||
addEndInstruction(code); | ||
break; | ||
} | ||
createAndExecuteChildStackFrame(code, { | ||
passCurrentContext: true | ||
}); | ||
} | ||
/** | ||
* Binary operations | ||
*/ | ||
case "add": | ||
case "mul": | ||
case "sub": | ||
/** | ||
* There are two seperated operation for both signed and unsigned integer, | ||
* but since the host environment will handle that, we don't have too :) | ||
*/ | ||
case "div_s": | ||
case "div_u": | ||
case "rem_s": | ||
case "rem_u": | ||
case "shl": | ||
case "shr_s": | ||
case "shr_u": | ||
case "rotl": | ||
case "rotr": | ||
case "div": | ||
case "min": | ||
case "max": | ||
case "copysign": | ||
case "or": | ||
case "xor": | ||
case "and": | ||
case "eq": | ||
case "ne": | ||
case "lt_s": | ||
case "lt_u": | ||
case "le_s": | ||
case "le_u": | ||
case "gt": | ||
case "gt_s": | ||
case "gt_u": | ||
case "ge_s": | ||
case "ge_u": { | ||
let binopFn; | ||
switch (instruction.object) { | ||
case "i32": | ||
binopFn = binopi32; | ||
break; | ||
case "i64": | ||
binopFn = binopi64; | ||
break; | ||
case "f32": | ||
binopFn = binopf32; | ||
break; | ||
case "f64": | ||
binopFn = binopf64; | ||
break; | ||
default: | ||
throw createTrap( | ||
"Unsupported operation " + | ||
instruction.id + | ||
" on " + | ||
instruction.object | ||
); | ||
const [c1, c2] = pop2(instruction.object, instruction.object); | ||
pushResult(binopFn(c1, c2, instruction.id)); | ||
break; | ||
} | ||
const [left, right] = instruction.args; | ||
/** | ||
* Unary operations | ||
*/ | ||
case "abs": | ||
case "neg": | ||
case "clz": | ||
case "ctz": | ||
case "popcnt": | ||
case "eqz": | ||
case "reinterpret/f32": | ||
case "reinterpret/f64": { | ||
let unopFn; | ||
// Interpret left branch first if it's a child instruction | ||
if (typeof left !== "undefined") { | ||
createAndExecuteChildStackFrame([left], { | ||
passCurrentContext: true | ||
}); | ||
} | ||
// for conversion operations, the operand type appears after the forward-slash | ||
// e.g. with i32.reinterpret/f32, the oprand is f32, and the resultant is i32 | ||
const opType = | ||
instruction.id.indexOf("/") !== -1 | ||
? instruction.id.split("/")[1] | ||
: instruction.object; | ||
// Interpret right branch first if it's a child instruction | ||
if (typeof right !== "undefined") { | ||
createAndExecuteChildStackFrame([right], { | ||
passCurrentContext: true | ||
}); | ||
} | ||
switch (opType) { | ||
case "i32": | ||
unopFn = unopi32; | ||
break; | ||
case "i64": | ||
unopFn = unopi64; | ||
break; | ||
case "f32": | ||
unopFn = unopf32; | ||
break; | ||
case "f64": | ||
unopFn = unopf64; | ||
break; | ||
default: | ||
throw createTrap( | ||
"Unsupported operation " + instruction.id + " on " + opType | ||
); | ||
} | ||
const [c1, c2] = pop2(instruction.object, instruction.object); | ||
pushResult(binopFn(c1, c2, instruction.id)); | ||
const [operand] = instruction.args; | ||
break; | ||
} | ||
// Interpret argument first if it's a child instruction | ||
if (typeof operand !== "undefined") { | ||
const code = [operand]; | ||
addEndInstruction(code); | ||
/** | ||
* Unary operations | ||
*/ | ||
case "abs": | ||
case "neg": | ||
case "clz": | ||
case "ctz": | ||
case "popcnt": | ||
case "eqz": | ||
case "reinterpret/f32": | ||
case "reinterpret/f64": { | ||
let unopFn; | ||
createAndExecuteChildStackFrame(code, { | ||
passCurrentContext: true | ||
}); | ||
} | ||
// for conversion operations, the operand type appears after the forward-slash | ||
// e.g. with i32.reinterpret/f32, the oprand is f32, and the resultant is i32 | ||
const opType = | ||
instruction.id.indexOf("/") !== -1 | ||
? instruction.id.split("/")[1] | ||
: instruction.object; | ||
const c = pop1OfType(opType); | ||
switch (opType) { | ||
case "i32": | ||
unopFn = unopi32; | ||
break; | ||
case "i64": | ||
unopFn = unopi64; | ||
break; | ||
case "f32": | ||
unopFn = unopf32; | ||
break; | ||
case "f64": | ||
unopFn = unopf64; | ||
break; | ||
default: | ||
throw createTrap( | ||
"Unsupported operation " + instruction.id + " on " + opType | ||
); | ||
pushResult(unopFn(c, instruction.id)); | ||
break; | ||
} | ||
const [operand] = instruction.args; | ||
case "end": { | ||
// Pop active frame from the stack | ||
stack.pop(); | ||
framepointer--; | ||
// Interpret argument first if it's a child instruction | ||
if (typeof operand !== "undefined") { | ||
createAndExecuteChildStackFrame([operand], { | ||
passCurrentContext: true | ||
}); | ||
} | ||
// Return the item on top of the values/stack; | ||
if (frame.values.length > 0) { | ||
const res = pop1(); | ||
const c = pop1OfType(opType); | ||
if (res.type !== "label") { | ||
returnRegister = [res]; | ||
} else { | ||
// Push label back | ||
pushResult(res); | ||
} | ||
} | ||
pushResult(unopFn(c, instruction.id)); | ||
break; | ||
return; | ||
} | ||
} | ||
} | ||
if (typeof frame.trace === "function") { | ||
frame.trace(depth, frame._pc, instruction, frame); | ||
} | ||
frame._pc++; | ||
} | ||
// Return the item on top of the values/stack; | ||
if (frame.values.length > 0) { | ||
const res = pop1(); | ||
run(); | ||
if (res.type !== "label") { | ||
return res; | ||
} else { | ||
// Push label back | ||
pushResult(res); | ||
} | ||
if (returnRegister !== null) { | ||
// FIXME(sven): handle multiple results in hostfunc | ||
return returnRegister[0]; | ||
} | ||
} | ||
function assertNItemsOnStack(stack: Array<any>, numberOfItem: number) { | ||
if (stack.length < numberOfItem) { | ||
throw new RuntimeError( | ||
"Assertion error: expected " + | ||
numberOfItem + | ||
" on the stack, found " + | ||
stack.length | ||
); | ||
} | ||
} |
@@ -22,3 +22,4 @@ // @flow | ||
// TODO(sven): find a specification reference for that | ||
if (node.init.length > 1 || node.init.length === 0) { | ||
// FIXME(sven): +1 because of the implicit end, change the order of validations | ||
if (node.init.length > 2 || node.init.length === 1) { | ||
throw new CompileError("type mismatch"); | ||
@@ -25,0 +26,0 @@ } |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
242986
6968
+ Added@webassemblyjs/ast@1.2.4(transitive)
+ Added@webassemblyjs/floating-point-hex-parser@1.2.4(transitive)
+ Added@webassemblyjs/helper-code-frame@1.2.4(transitive)
+ Added@webassemblyjs/helper-fsm@1.2.4(transitive)
+ Added@webassemblyjs/helper-wasm-bytecode@1.2.4(transitive)
+ Added@webassemblyjs/leb128@1.2.4(transitive)
+ Added@webassemblyjs/wasm-parser@1.2.4(transitive)
+ Added@webassemblyjs/wast-parser@1.2.4(transitive)
+ Added@webassemblyjs/wast-printer@1.2.4(transitive)
- Removed@babel/code-frame@7.26.2(transitive)
- Removed@babel/helper-validator-identifier@7.25.9(transitive)
- Removed@webassemblyjs/ast@1.2.3(transitive)
- Removed@webassemblyjs/floating-point-hex-parser@1.2.3(transitive)
- Removed@webassemblyjs/helper-fsm@1.2.3(transitive)
- Removed@webassemblyjs/helper-wasm-bytecode@1.2.3(transitive)
- Removed@webassemblyjs/leb128@1.2.3(transitive)
- Removed@webassemblyjs/wasm-parser@1.2.3(transitive)
- Removed@webassemblyjs/wast-parser@1.2.3(transitive)
- Removedjs-tokens@4.0.0(transitive)
- Removedpicocolors@1.1.1(transitive)
Updated@webassemblyjs/ast@1.2.4