"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.executeStackFrame = executeStackFrame;
var _long = _interopRequireDefault(require("@xtuc/long"));
var _memory2 = require("../runtime/values/memory");
var _errors = require("../../errors");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
function _iterableToArrayLimit(arr, i) { var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"]; if (_i == null) return; var _arr = []; var _n = true; var _d = false; var _s, _e; try { for (_i = _i.call(arr); !(_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 _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); }
var _require = require("./instruction/binop"),
binopi32 = _require.binopi32,
binopi64 = _require.binopi64,
binopf32 = _require.binopf32,
binopf64 = _require.binopf64;
var _require2 = require("./instruction/unop"),
unopi32 = _require2.unopi32,
unopi64 = _require2.unopi64,
unopf32 = _require2.unopf32,
unopf64 = _require2.unopf64;
var _require3 = require("../runtime/castIntoStackLocalOfType"),
castIntoStackLocalOfType = _require3.castIntoStackLocalOfType;
var i32 = require("../runtime/values/i32");
var i64 = require("../runtime/values/i64");
var f32 = require("../runtime/values/f32");
var f64 = require("../runtime/values/f64");
var label = require("../runtime/values/label");
var _require4 = require("./signals"),
createTrap = _require4.createTrap;
var _require5 = require("./instruction/comparison"),
compare = _require5.compare;
function executeStackFrame(_ref, offset, firstFrame) {
var program = _ref.program;
if (!(_typeof(program) === "object")) {
throw new _errors.RuntimeError('typeof program === "object"' + " error: " + (undefined || "unknown"));
}
var callStack = [firstFrame]; // because it's used a macros
// eslint-disable-next-line prefer-const
var framepointer = 0;
function getLocalByIndex(frame, index) {
var local = frame.locals[index];
if (typeof local === "undefined") {
throw newRuntimeError("Assertion error: no local value at index " + index);
}
frame.values.push(local);
}
function setLocalByIndex(frame, index, value) {
if (!(typeof index === "number")) {
throw new _errors.RuntimeError('typeof index === "number"' + " error: " + (undefined || "unknown"));
}
frame.locals[index] = value;
}
function pushResult(frame, res) {
if (typeof res === "undefined") {
return;
}
frame.values.push(res);
}
function popArrayOfValTypes(frame, types) {
if (frame.values.length < types.length) {
throw new _errors.RuntimeError("Assertion error: expected " + JSON.stringify(types.length) + " on the stack, found " + frame.values.length);
}
return types.map(function (type) {
return pop1OfType(frame, type);
});
}
function valueTypeEq(l, r) {
// compatibility with our parser
if (l === "u32") {
l = "i32";
}
if (l === "u64") {
l = "i64";
}
if (r === "u32") {
r = "i32";
}
if (r === "u64") {
r = "i64";
}
return l === r;
}
function pop1OfType(frame, type) {
if (frame.values.length < 1) {
throw new _errors.RuntimeError("Assertion error: expected " + JSON.stringify(1) + " on the stack, found " + frame.values.length);
}
var v = frame.values.pop();
if (typeof type === "string" && valueTypeEq(v.type, type) === false) {
throw newRuntimeError("Internal failure: expected value of type " + type + " on top of the stack, type given: " + v.type);
}
return v;
}
function pop1(frame) {
if (frame.values.length < 1) {
throw new _errors.RuntimeError("Assertion error: expected " + JSON.stringify(1) + " on the stack, found " + frame.values.length);
}
return frame.values.pop();
}
function pop2(frame, type1, type2) {
if (frame.values.length < 2) {
throw new _errors.RuntimeError("Assertion error: expected " + JSON.stringify(2) + " on the stack, found " + frame.values.length);
}
var c2 = frame.values.pop();
var c1 = frame.values.pop();
if (valueTypeEq(c2.type, type2) === false) {
throw newRuntimeError("Internal failure: expected c2 value of type " + type2 + " on top of the stack, given type: " + c2.type);
}
if (valueTypeEq(c1.type, type1) === false) {
throw newRuntimeError("Internal failure: expected c1 value of type " + type1 + " on top of the stack, given type: " + c1.type);
}
return [c1, c2];
}
function getMemoryOffset(frame, instruction) {
if (instruction.namedArgs && instruction.namedArgs.offset) {
// $FlowIgnore
var _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;
}
}
function getMemory(frame) {
if (frame.originatingModule.memaddrs.length !== 1) {
throw newRuntimeError("unknown memory");
}
var memAddr = frame.originatingModule.memaddrs[0];
return frame.allocator.get(memAddr);
}
function newRuntimeError(msg) {
return new _errors.RuntimeError(msg);
}
function getActiveStackFrame() {
if (!(framepointer > -1)) {
throw new _errors.RuntimeError('framepointer > -1' + " error: " + ("call stack underflow" || "unknown"));
}
var frame = callStack[framepointer];
if (!(frame !== undefined)) {
throw new _errors.RuntimeError('frame !== undefined' + " error: " + ("no frame at " + framepointer || "unknown"));
}
return frame;
}
var offsets = Object.keys(program);
var pc = offsets.indexOf(String(offset));
while (true) {
var frame = getActiveStackFrame();
var instruction = program[parseInt(offsets[pc])];
if (!(instruction !== undefined)) {
throw new _errors.RuntimeError('instruction !== undefined' + " error: " + ("no instruction at pc ".concat(pc, " in frame ").concat(framepointer) || "unknown"));
}
if (typeof frame.trace === "function") {
frame.trace(framepointer, pc, instruction, frame);
}
pc++;
switch (instruction.type) {
// $FlowIgnore
case "InternalEndAndReturn":
{
if (frame.returnAddress !== -1) {
pc = frame.returnAddress; // raw goto
var activeFrame = getActiveStackFrame(); // pass the result of the previous call into the new active fame
var res = void 0;
if (activeFrame.values.length > 0) {
res = pop1(activeFrame);
} // Pop active frame from the stack
callStack.pop();
framepointer--;
var newStackFrame = getActiveStackFrame();
if (res !== undefined && newStackFrame !== undefined) {
pushResult(newStackFrame, res);
}
break;
} else {
var activeFrame = getActiveStackFrame();
if (activeFrame.values.length > 0) {
return pop1(activeFrame);
} else {
return;
}
}
}
// $FlowIgnore
case "InternalGoto":
{
var target = instruction.target;
pc = offsets.indexOf(String(target));
break;
}
// $FlowIgnore
case "InternalCallExtern":
{
var _target = instruction.target; // 2. Assert: due to validation, F.module.funcaddrs[x] exists.
var funcaddr = frame.originatingModule.funcaddrs[_target];
if (typeof funcaddr === "undefined") {
throw newRuntimeError("No function was found in module at address ".concat(_target));
} // 3. Let a be the function address F.module.funcaddrs[x]
var subroutine = frame.allocator.get(funcaddr);
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$type = _slicedToArray(subroutine.type, 2),
argTypes = _subroutine$type[0],
resultType = _subroutine$type[1];
var args = popArrayOfValTypes(frame, argTypes);
if (!subroutine.isExternal) {
throw new _errors.RuntimeError('subroutine.isExternal' + " error: " + (undefined || "unknown"));
}
var _res = subroutine.code(args.map(function (arg) {
return arg.value;
}));
if (typeof _res !== "undefined") {
pushResult(frame, castIntoStackLocalOfType(resultType, _res));
}
break;
}
}
switch (instruction.id) {
case "const":
{
// https://webassembly.github.io/spec/core/exec/instructions.html#exec-const
// $FlowIgnore
var n = instruction.args[0];
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(frame, // $FlowIgnore
castIntoStackLocalOfType(instruction.object, n.value));
break;
}
/**
* 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;
}
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.
if (frame.values.length < 1) {
throw new _errors.RuntimeError("Assertion error: expected " + JSON.stringify(1) + " on the stack, found " + frame.values.length);
}
// 2. Pop the value valval from the stack.
pop1(frame);
break;
}
case "call":
{
// FIXME(sven): check spec compliancy
// $FlowIgnore
var index = instruction.index.value;
var stackframe = require("./stackframe");
var activeFrame = getActiveStackFrame();
var newStackFrame = stackframe.createChildStackFrame(activeFrame, pc); // move active frame
framepointer++;
if (framepointer >= 300) {
throw new _errors.RuntimeError("Maximum call stack depth reached");
} // Push the frame on top of the stack
callStack[framepointer] = newStackFrame;
pc = offsets.indexOf(String(index));
break;
}
case "end":
{
// 3. Assert: due to validation, the label L is now on the top of the stack.
// 4. Pop the label from the stack.
var found = false;
var index = frame.values.slice(0).reverse().findIndex(function (_ref2) {
var type = _ref2.type;
return type === "label";
}); // some expression like inittializer don't have labels currently, so this is
// guarantee to fail
// assertRuntimeError(index !== -1, "POP_LABEL: label not found")
if (index !== -1) {
var initialOrderIndex = frame.values.length - 1 - index;
frame.values.splice(initialOrderIndex, 1);
}
break;
}
case "loop":
case "block":
{
// https://webassembly.github.io/spec/core/exec/instructions.html#blocks
// FIXME(sven): check spec compliancy
var block = instruction; // 2. Enter the block instr∗ with label
frame.labels.push({
value: block,
arity: 0,
// $FlowIgnore
id: block.label
}); // $FlowIgnore
pushResult(frame, label.createValue(block.label.value)); // $FlowIgnore
break;
}
case "br":
{
// FIXME(sven): check spec compliancy
// $FlowIgnore
var _label = instruction.args[0]; // $FlowIgnore
pc = offsets.indexOf(String(_label.value));
break;
}
case "br_if":
{
// $FlowIgnore
var _label2 = 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 c = pop1OfType(frame, "i32");
if (c.value.eqz().isTrue() === false) {
// 3. If c is non-zero, then
// 3. a. Execute the instruction (br l).
// $FlowIgnore
pc = offsets.indexOf(String(_label2.value));
} else {// 4. Else:
// 4. a. Do nothing.
}
break;
}
/**
* 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();
}
case "local":
{
// $FlowIgnore
var _instruction$args = _slicedToArray(instruction.args, 1),
valtype = _instruction$args[0];
if (valtype.name === "i64") {
var init = castIntoStackLocalOfType(valtype.name, new _long["default"](0, 0));
frame.locals.push(init);
} else {
// $FlowIgnore
var _init = castIntoStackLocalOfType(valtype.name, 0);
frame.locals.push(_init);
} // $FlowIgnore
break;
}
/**
* 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
// $FlowIgnore
var _index = instruction.args[0];
if (typeof _index === "undefined") {
throw newRuntimeError("get_local requires one argument, none given.");
}
if (_index.type === "NumberLiteral" || _index.type === "FloatLiteral") {
getLocalByIndex(frame, _index.value);
} else {
throw newRuntimeError("get_local: unsupported index of type: " + _index.type);
}
break;
}
case "set_local":
{
// https://webassembly.github.io/spec/core/exec/instructions.html#exec-set-local
// $FlowIgnore
var _index2 = instruction.args[0];
if (_index2.type === "NumberLiteral") {
// WASM
// 4. Pop the value val from the stack
var val = pop1(frame); // 5. Replace F.locals[x] with the value val
setLocalByIndex(frame, _index2.value, val);
} else {
throw newRuntimeError("set_local: unsupported index of type: " + _index2.type);
}
break;
}
case "tee_local":
{
// https://webassembly.github.io/spec/core/exec/instructions.html#exec-tee-local
// $FlowIgnore
var _index3 = instruction.args[0];
if (_index3.type === "NumberLiteral") {
// 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(frame); // 3. Push the value valval to the stack.
pushResult(frame, _val); // 4. Push the value valval to the stack.
pushResult(frame, _val); // 5. Execute the instruction (set_local x).
// 5. 4. Pop the value val from the stack
var val2 = pop1(frame); // 5. 5. Replace F.locals[x] with the value val
setLocalByIndex(frame, _index3.value, val2);
} else {
throw newRuntimeError("tee_local: unsupported index of type: " + _index3.type);
}
break;
}
case "set_global":
{
// https://webassembly.github.io/spec/core/exec/instructions.html#exec-set-global
// $FlowIgnore
var _index4 = instruction.args[0]; // 2. Assert: due to validation, F.module.globaladdrs[x] exists.
// $FlowIgnore
var globaladdr = frame.originatingModule.globaladdrs[_index4.value];
if (typeof globaladdr === "undefined") {
// $FlowIgnore
throw newRuntimeError("Global address ".concat(_index4.value, " not found"));
} // 4. Assert: due to validation, S.globals[a] exists.
var globalinst = frame.allocator.get(globaladdr);
if (_typeof(globalinst) !== "object") {
throw newRuntimeError("Unexpected data for global at ".concat(globaladdr.toString()));
} // 7. Pop the value val from the stack.
var _val2 = pop1(frame); // 8. Replace glob.value with the value val.
globalinst.value = _val2.value;
frame.allocator.set(globaladdr, globalinst);
break;
}
case "get_global":
{
// https://webassembly.github.io/spec/core/exec/instructions.html#exec-get-global
// $FlowIgnore
var _index5 = instruction.args[0]; // 2. Assert: due to validation, F.module.globaladdrs[x] exists.
// $FlowIgnore
var _globaladdr = frame.originatingModule.globaladdrs[_index5.value];
if (typeof _globaladdr === "undefined") {
throw newRuntimeError( // $FlowIgnore
"Unknown global at index: ".concat(_index5.value.toString()));
} // 4. Assert: due to validation, S.globals[a] exists.
var _globalinst = frame.allocator.get(_globaladdr);
if (_typeof(_globalinst) !== "object") {
throw newRuntimeError("Unexpected data for global at ".concat(_globaladdr.toString()));
} // 7. Pop the value val from the stack.
pushResult(frame, _globalinst);
break;
}
/**
* Memory operations
*/
// https://webassembly.github.io/spec/core/exec/instructions.html#exec-storen
case "store":
case "store8":
case "store16":
case "store32":
{
// $FlowIgnore
var id = instruction.id,
object = instruction.object;
var memory = getMemory(frame); // $FlowIgnore
var _pop = pop2(frame, "i32", object),
_pop2 = _slicedToArray(_pop, 2),
c1 = _pop2[0],
c2 = _pop2[1];
var ptr = c1.value.toNumber() + getMemoryOffset(frame, instruction);
var valueBuffer = c2.value.toByteArray();
switch (id) {
case "store":
break;
case "store8":
valueBuffer = valueBuffer.slice(0, 1);
break;
case "store16":
valueBuffer = valueBuffer.slice(0, 2);
break;
case "store32":
valueBuffer = valueBuffer.slice(0, 4);
break;
default:
throw newRuntimeError("illegal operation: " + id);
}
if (ptr + valueBuffer.length > memory.buffer.byteLength) {
throw newRuntimeError("memory access out of bounds");
}
var memoryBuffer = new Uint8Array(memory.buffer); // load / store use little-endian order
for (var ptrOffset = 0; ptrOffset < valueBuffer.length; ptrOffset++) {
memoryBuffer[ptr + ptrOffset] = valueBuffer[ptrOffset];
}
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":
{
// $FlowIgnore
var _id = instruction.id,
_object = instruction.object;
var _memory = getMemory(frame);
var _ptr = pop1OfType(frame, "i32").value.toNumber() + getMemoryOffset(frame, instruction); // for i32 / i64 ops, handle extended load
var extend = 0; // for i64 values, increase the bitshift by 4 bytes
var extendOffset = _object === "i32" ? 0 : 32;
var 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;
} // check for memory access out of bounds
switch (_object) {
case "u32":
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;
}
default:
// $FlowIgnore
throw new _errors.RuntimeError("Unsupported " + _object + " load");
}
switch (_object) {
case "i32":
case "u32":
pushResult(frame, i32.createValueFromArrayBuffer(_memory.buffer, _ptr, extend, signed));
break;
case "i64":
pushResult(frame, i64.createValueFromArrayBuffer(_memory.buffer, _ptr, extend, signed));
break;
case "f32":
pushResult(frame, f32.createValueFromArrayBuffer(_memory.buffer, _ptr));
break;
case "f64":
pushResult(frame, f64.createValueFromArrayBuffer(_memory.buffer, _ptr));
break;
default:
throw new _errors.RuntimeError("Unsupported " + _object + " load");
}
break;
}
/**
* 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":
{
var binopFn = void 0; // $FlowIgnore
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 " + // $FlowIgnore
instruction.object);
}
var _pop3 = pop2(frame, instruction.object, instruction.object),
_pop4 = _slicedToArray(_pop3, 2),
_c = _pop4[0],
_c2 = _pop4[1]; // $FlowIgnore
pushResult(frame, binopFn(_c, _c2, instruction.id));
break;
}
/**
* Comparison operations
*/
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":
{
// $FlowIgnore
var _pop5 = pop2(frame, instruction.object, instruction.object),
_pop6 = _slicedToArray(_pop5, 2),
_c3 = _pop6[0],
_c4 = _pop6[1]; // $FlowIgnore
pushResult(frame, compare(_c3, _c4, instruction.id));
break;
}
/**
* 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
var opType = instruction.id.indexOf("/") !== -1 ? // $FlowIgnore
instruction.id.split("/")[1] : // $FlowIgnore
instruction.object;
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( // $FlowIgnore
"Unsupported operation " + instruction.id + " on " + opType);
}
var _c5 = pop1OfType(frame, opType); // $FlowIgnore
pushResult(frame, unopFn(_c5, instruction.id));
break;
}
case "return":
{
if (frame.returnAddress !== -1) {
pc = frame.returnAddress; // raw goto
var activeFrame = getActiveStackFrame(); // pass the result of the previous call into the new active fame
var res = void 0;
if (activeFrame.values.length > 0) {
res = pop1(activeFrame);
} // Pop active frame from the stack
callStack.pop();
framepointer--;
var newStackFrame = getActiveStackFrame();
if (res !== undefined && newStackFrame !== undefined) {
pushResult(newStackFrame, res);
}
}
var activeFrame = getActiveStackFrame();
if (activeFrame.values.length > 0) {
return pop1(activeFrame);
} else {
return;
}
}
}
}
} |