blob: 8b9c408f1da3114a848b6de363569596e38523d1 [file] [log] [blame] [edit]
/*
* Copyright 2015 WebAssembly Community Group participants
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
//
// Simple WebAssembly interpreter. This operates directly on the AST,
// for simplicity and clarity. A goal is for it to be possible for
// people to read this code and understand WebAssembly semantics.
//
#ifndef wasm_wasm_interpreter_h
#define wasm_wasm_interpreter_h
#include <limits.h>
#include <sstream>
#include "support/bits.h"
#include "wasm.h"
namespace wasm {
using namespace cashew;
// Utilities
IString WASM("wasm"),
RETURN_FLOW("*return:)*");
enum {
pageSize = 64*1024,
maxCallDepth = 250
};
// Stuff that flows around during executing expressions: a literal, or a change in control flow
class Flow {
public:
Flow() {}
Flow(Literal value) : value(value) {}
Flow(IString breakTo) : breakTo(breakTo) {}
Literal value;
IString breakTo; // if non-null, a break is going on
bool breaking() { return breakTo.is(); }
void clearIf(IString target) {
if (breakTo == target) {
breakTo.clear();
}
}
friend std::ostream& operator<<(std::ostream& o, Flow& flow) {
o << "(flow " << (flow.breakTo.is() ? flow.breakTo.str : "-") << " : " << flow.value << ')';
return o;
}
};
//
// An instance of a WebAssembly module, which can execute it via AST interpretation.
//
// To embed this interpreter, you need to provide an ExternalInterface instance
// (see below) which provides the embedding-specific details, that is, how to
// connect to the embedding implementation.
//
// To call into the interpreter, use callExport.
//
class ModuleInstance {
public:
typedef std::vector<Literal> LiteralList;
//
// You need to implement one of these to create a concrete interpreter. The
// ExternalInterface provides embedding-specific functionality like calling
// an imported function or accessing memory.
//
struct ExternalInterface {
virtual void init(Module& wasm) {}
virtual Literal callImport(Import* import, LiteralList& arguments) = 0;
virtual Literal load(Load* load, size_t addr) = 0;
virtual void store(Store* store, size_t addr, Literal value) = 0;
virtual void growMemory(size_t oldSize, size_t newSize) = 0;
virtual void trap(const char* why) = 0;
};
Module& wasm;
ModuleInstance(Module& wasm, ExternalInterface* externalInterface) : wasm(wasm), externalInterface(externalInterface) {
memorySize = wasm.memory.initial;
externalInterface->init(wasm);
}
Literal callExport(IString name, LiteralList& arguments) {
Export *export_ = wasm.exportsMap[name];
if (!export_) externalInterface->trap("callExport not found");
return callFunction(export_->value, arguments);
}
private:
size_t callDepth = 0;
#ifdef WASM_INTERPRETER_DEBUG
int indent = 0;
#endif
//
// Calls a function. This can be used both internally (calls from
// the interpreter to another method), or when you want to call into
// the module.
//
Literal callFunction(IString name, LiteralList& arguments) {
class FunctionScope {
public:
std::map<IString, Literal> locals;
Function* function;
FunctionScope(Function* function, LiteralList& arguments)
: function(function) {
if (function->params.size() != arguments.size()) {
std::cerr << "Function `" << function->name << "` expects "
<< function->params.size() << " parameters, got "
<< arguments.size() << " arguments." << std::endl;
abort();
}
for (size_t i = 0; i < arguments.size(); i++) {
if (function->params[i].type != arguments[i].type) {
std::cerr << "Function `" << function->name << "` expects type "
<< printWasmType(function->params[i].type)
<< " for parameter " << i << ", got "
<< printWasmType(arguments[i].type) << "." << std::endl;
abort();
}
locals[function->params[i].name] = arguments[i];
}
for (auto& local : function->locals) {
locals[local.name].type = local.type;
}
}
};
#ifdef WASM_INTERPRETER_DEBUG
struct IndentHandler {
int& indent;
const char *name;
IndentHandler(int& indent, const char *name, Expression *expression) : indent(indent), name(name) {
doIndent(std::cout, indent);
std::cout << "visit " << name << " :\n";
indent++;
#if WASM_INTERPRETER_DEBUG == 2
doIndent(std::cout, indent);
expression->print(std::cout, indent) << '\n';
indent++;
#endif
}
~IndentHandler() {
#if WASM_INTERPRETER_DEBUG == 2
indent--;
#endif
indent--;
doIndent(std::cout, indent);
std::cout << "exit " << name << '\n';
}
};
#define NOTE_ENTER(x) IndentHandler indentHandler(instance.indent, x, curr);
#define NOTE_NAME(p0) { doIndent(std::cout, instance.indent); std::cout << "name in " << indentHandler.name << '(' << Name(p0) << ")\n"; }
#define NOTE_EVAL1(p0) { doIndent(std::cout, instance.indent); std::cout << "eval in " << indentHandler.name << '(' << p0 << ")\n"; }
#define NOTE_EVAL2(p0, p1) { doIndent(std::cout, instance.indent); std::cout << "eval in " << indentHandler.name << '(' << p0 << ", " << p1 << ")\n"; }
#else
#define NOTE_ENTER(x)
#define NOTE_NAME(p0)
#define NOTE_EVAL1(p0)
#define NOTE_EVAL2(p0, p1)
#endif
// Execute a statement
class ExpressionRunner : public WasmVisitor<ExpressionRunner, Flow> {
ModuleInstance& instance;
FunctionScope& scope;
public:
ExpressionRunner(ModuleInstance& instance, FunctionScope& scope) : instance(instance), scope(scope) {}
Flow visitBlock(Block *curr) {
NOTE_ENTER("Block");
Flow flow;
for (auto expression : curr->list) {
flow = visit(expression);
if (flow.breaking()) {
flow.clearIf(curr->name);
return flow;
}
}
return flow;
}
Flow visitIf(If *curr) {
NOTE_ENTER("If");
Flow flow = visit(curr->condition);
if (flow.breaking()) return flow;
NOTE_EVAL1(flow.value);
if (flow.value.geti32()) {
Flow flow = visit(curr->ifTrue);
if (!flow.breaking() && !curr->ifFalse) flow.value = Literal(); // if_else returns a value, but if does not
return flow;
}
if (curr->ifFalse) return visit(curr->ifFalse);
return Flow();
}
Flow visitLoop(Loop *curr) {
NOTE_ENTER("Loop");
while (1) {
Flow flow = visit(curr->body);
if (flow.breaking()) {
if (flow.breakTo == curr->in) continue; // lol
flow.clearIf(curr->out);
}
return flow; // loop does not loop automatically, only continue achieves that
}
}
Flow visitBreak(Break *curr) {
NOTE_ENTER("Break");
bool condition = true;
if (curr->condition) {
Flow flow = visit(curr->condition);
if (flow.breaking()) return flow;
condition = flow.value.getInteger();
}
Flow flow(curr->name);
if (curr->value) {
flow = visit(curr->value);
if (flow.breaking()) return flow;
flow.breakTo = curr->name;
}
return condition ? flow : Flow();
}
Flow visitSwitch(Switch *curr) {
NOTE_ENTER("Switch");
Flow flow = visit(curr->value);
if (flow.breaking()) {
flow.clearIf(curr->name);
return flow;
}
NOTE_EVAL1(flow.value);
int64_t index = flow.value.getInteger();
Name target = curr->default_;
if (index >= 0 && (size_t)index < curr->targets.size()) {
target = curr->targets[index];
}
// This is obviously very inefficient. This should be a cached data structure
std::map<Name, size_t> caseMap; // name => index in cases
for (size_t i = 0; i < curr->cases.size(); i++) {
caseMap[curr->cases[i].name] = i;
}
auto iter = caseMap.find(target);
if (iter == caseMap.end()) {
// not in the cases, so this is a break
Flow flow(target);
flow.clearIf(curr->name);
return flow;
}
size_t caseIndex = iter->second;
assert(caseIndex < curr->cases.size());
while (caseIndex < curr->cases.size()) {
Switch::Case& c = curr->cases[caseIndex];
flow = visit(c.body);
if (flow.breaking()) {
flow.clearIf(curr->name);
break;
}
caseIndex++;
}
return flow;
}
Flow generateArguments(const ExpressionList& operands, LiteralList& arguments) {
arguments.reserve(operands.size());
for (auto expression : operands) {
Flow flow = visit(expression);
if (flow.breaking()) return flow;
arguments.push_back(flow.value);
}
return Flow();
}
Flow visitCall(Call *curr) {
NOTE_ENTER("Call");
NOTE_NAME(curr->target);
LiteralList arguments;
Flow flow = generateArguments(curr->operands, arguments);
if (flow.breaking()) return flow;
Flow ret = instance.callFunction(curr->target, arguments);
#ifdef WASM_INTERPRETER_DEBUG
std::cout << "(returned to " << scope.function->name << ")\n";
#endif
return ret;
}
Flow visitCallImport(CallImport *curr) {
NOTE_ENTER("CallImport");
LiteralList arguments;
Flow flow = generateArguments(curr->operands, arguments);
if (flow.breaking()) return flow;
return instance.externalInterface->callImport(instance.wasm.importsMap[curr->target], arguments);
}
Flow visitCallIndirect(CallIndirect *curr) {
NOTE_ENTER("CallIndirect");
Flow target = visit(curr->target);
if (target.breaking()) return target;
size_t index = target.value.geti32();
if (index >= instance.wasm.table.names.size()) trap("callIndirect: overflow");
Name name = instance.wasm.table.names[index];
Function *func = instance.wasm.functionsMap[name];
if (func->type.is() && func->type != curr->fullType->name) trap("callIndirect: bad type");
LiteralList arguments;
Flow flow = generateArguments(curr->operands, arguments);
if (flow.breaking()) return flow;
return instance.callFunction(name, arguments);
}
Flow visitGetLocal(GetLocal *curr) {
NOTE_ENTER("GetLocal");
IString name = curr->name;
NOTE_NAME(name);
NOTE_EVAL1(scope.locals[name]);
return scope.locals[name];
}
Flow visitSetLocal(SetLocal *curr) {
NOTE_ENTER("SetLocal");
IString name = curr->name;
Flow flow = visit(curr->value);
if (flow.breaking()) return flow;
NOTE_NAME(name);
NOTE_EVAL1(flow.value);
assert(flow.value.type == curr->type);
scope.locals[name] = flow.value;
return flow;
}
Flow visitLoad(Load *curr) {
NOTE_ENTER("Load");
Flow flow = visit(curr->ptr);
if (flow.breaking()) return flow;
return instance.externalInterface->load(curr, instance.getFinalAddress(curr, flow.value));
}
Flow visitStore(Store *curr) {
NOTE_ENTER("Store");
Flow ptr = visit(curr->ptr);
if (ptr.breaking()) return ptr;
Flow value = visit(curr->value);
if (value.breaking()) return value;
instance.externalInterface->store(curr, instance.getFinalAddress(curr, ptr.value), value.value);
return value;
}
Flow visitConst(Const *curr) {
NOTE_ENTER("Const");
NOTE_EVAL1(curr->value);
return Flow(curr->value); // heh
}
Flow visitUnary(Unary *curr) {
NOTE_ENTER("Unary");
Flow flow = visit(curr->value);
if (flow.breaking()) return flow;
Literal value = flow.value;
NOTE_EVAL1(value);
if (value.type == i32) {
int32_t v = value.geti32();
switch (curr->op) {
case Clz: return Literal((int32_t)CountLeadingZeroes(v));
case Ctz: return Literal((int32_t)CountTrailingZeroes(v));
case Popcnt: return Literal((int32_t)PopCount(v));
case ReinterpretInt: {
float v = value.reinterpretf32();
if (isnan(v)) {
return Literal(Literal(value.geti32() | 0x7f800000).reinterpretf32());
}
return Literal(value.reinterpretf32());
}
case ExtendSInt32: return Literal(int64_t(value.geti32()));
case ExtendUInt32: return Literal(uint64_t((uint32_t)value.geti32()));
case ConvertUInt32: return curr->type == f32 ? Literal(float(uint32_t(value.geti32()))) : Literal(double(uint32_t(value.geti32())));
case ConvertSInt32: return curr->type == f32 ? Literal(float(int32_t(value.geti32()))) : Literal(double(int32_t(value.geti32())));
default: abort();
}
}
if (value.type == i64) {
int64_t v = value.geti64();
switch (curr->op) {
case Clz: return Literal((int64_t)CountLeadingZeroes(v));
case Ctz: return Literal((int64_t)CountTrailingZeroes(v));
case Popcnt: return Literal((int64_t)PopCount(v));
case WrapInt64: return Literal(int32_t(value.geti64()));
case ReinterpretInt: {
return Literal(value.reinterpretf64());
}
case ConvertUInt64: return curr->type == f32 ? Literal(float((uint64_t)value.geti64())) : Literal(double((uint64_t)value.geti64()));
case ConvertSInt64: return curr->type == f32 ? Literal(float(value.geti64())) : Literal(double(value.geti64()));
default: abort();
}
}
if (value.type == f32) {
float v = value.getf32();
float ret;
switch (curr->op) {
case Neg: ret = -v; break;
case Abs: ret = std::abs(v); break;
case Ceil: ret = std::ceil(v); break;
case Floor: ret = std::floor(v); break;
case Trunc: ret = std::trunc(v); break;
case Nearest: ret = std::nearbyint(v); break;
case Sqrt: ret = std::sqrt(v); break;
case TruncSFloat32: return truncSFloat(curr, value);
case TruncUFloat32: return truncUFloat(curr, value);
case ReinterpretFloat: return Literal(value.reinterpreti32());
case PromoteFloat32: return Literal(double(value.getf32()));
default: abort();
}
return Literal(fixNaN(v, ret));
}
if (value.type == f64) {
double v = value.getf64();
double ret;
switch (curr->op) {
case Neg: ret = -v; break;
case Abs: ret = std::abs(v); break;
case Ceil: ret = std::ceil(v); break;
case Floor: ret = std::floor(v); break;
case Trunc: ret = std::trunc(v); break;
case Nearest: ret = std::nearbyint(v); break;
case Sqrt: ret = std::sqrt(v); break;
case TruncSFloat64: return truncSFloat(curr, value);
case TruncUFloat64: return truncUFloat(curr, value);
case ReinterpretFloat: return Literal(value.reinterpreti64());
case DemoteFloat64: return Literal(float(value.getf64()));
default: abort();
}
return Literal(fixNaN(v, ret));
}
abort();
}
Flow visitBinary(Binary *curr) {
NOTE_ENTER("Binary");
Flow flow = visit(curr->left);
if (flow.breaking()) return flow;
Literal left = flow.value;
flow = visit(curr->right);
if (flow.breaking()) return flow;
Literal right = flow.value;
NOTE_EVAL2(left, right);
assert(left.type == curr->left->type);
assert(right.type == curr->right->type);
if (left.type == i32) {
int32_t l = left.geti32(), r = right.geti32();
switch (curr->op) {
case Add: return Literal(l + r);
case Sub: return Literal(l - r);
case Mul: return Literal(l * r);
case DivS: {
if (r == 0) trap("i32.div_s by 0");
if (l == INT32_MIN && r == -1) trap("i32.div_s overflow"); // signed division overflow
return Literal(l / r);
}
case DivU: {
if (r == 0) trap("i32.div_u by 0");
return Literal(int32_t(uint32_t(l) / uint32_t(r)));
}
case RemS: {
if (r == 0) trap("i32.rem_s by 0");
if (l == INT32_MIN && r == -1) return Literal(int32_t(0));
return Literal(l % r);
}
case RemU: {
if (r == 0) trap("i32.rem_u by 0");
return Literal(int32_t(uint32_t(l) % uint32_t(r)));
}
case And: return Literal(l & r);
case Or: return Literal(l | r);
case Xor: return Literal(l ^ r);
case Shl: {
r = r & 31;
return Literal(l << r);
}
case ShrU: {
r = r & 31;
return Literal(int32_t(uint32_t(l) >> uint32_t(r)));
}
case ShrS: {
r = r & 31;
return Literal(l >> r);
}
case Eq: return Literal(l == r);
case Ne: return Literal(l != r);
case LtS: return Literal(l < r);
case LtU: return Literal(uint32_t(l) < uint32_t(r));
case LeS: return Literal(l <= r);
case LeU: return Literal(uint32_t(l) <= uint32_t(r));
case GtS: return Literal(l > r);
case GtU: return Literal(uint32_t(l) > uint32_t(r));
case GeS: return Literal(l >= r);
case GeU: return Literal(uint32_t(l) >= uint32_t(r));
default: abort();
}
} else if (left.type == i64) {
int64_t l = left.geti64(), r = right.geti64();
switch (curr->op) {
case Add: return Literal(l + r);
case Sub: return Literal(l - r);
case Mul: return Literal(l * r);
case DivS: {
if (r == 0) trap("i64.div_s by 0");
if (l == LLONG_MIN && r == -1) trap("i64.div_s overflow"); // signed division overflow
return Literal(l / r);
}
case DivU: {
if (r == 0) trap("i64.div_u by 0");
return Literal(int64_t(uint64_t(l) / uint64_t(r)));
}
case RemS: {
if (r == 0) trap("i64.rem_s by 0");
if (l == LLONG_MIN && r == -1) return Literal(int64_t(0));
return Literal(l % r);
}
case RemU: {
if (r == 0) trap("i64.rem_u by 0");
return Literal(int64_t(uint64_t(l) % uint64_t(r)));
}
case And: return Literal(l & r);
case Or: return Literal(l | r);
case Xor: return Literal(l ^ r);
case Shl: {
r = r & 63;
return Literal(l << r);
}
case ShrU: {
r = r & 63;
return Literal(int64_t(uint64_t(l) >> uint64_t(r)));
}
case ShrS: {
r = r & 63;
return Literal(l >> r);
}
case Eq: return Literal(l == r);
case Ne: return Literal(l != r);
case LtS: return Literal(l < r);
case LtU: return Literal(uint64_t(l) < uint64_t(r));
case LeS: return Literal(l <= r);
case LeU: return Literal(uint64_t(l) <= uint64_t(r));
case GtS: return Literal(l > r);
case GtU: return Literal(uint64_t(l) > uint64_t(r));
case GeS: return Literal(l >= r);
case GeU: return Literal(uint64_t(l) >= uint64_t(r));
default: abort();
}
} else if (left.type == f32) {
float l = left.getf32(), r = right.getf32();
float ret;
switch (curr->op) {
case Add: ret = l + r; break;
case Sub: ret = l - r; break;
case Mul: ret = l * r; break;
case Div: ret = l / r; break;
case CopySign: {
ret = std::copysign(l, r);
return Literal(ret);
}
case Min: {
if (l == r && l == 0) ret = 1/l < 0 ? l : r;
else ret = std::min(l, r);
break;
}
case Max: {
if (l == r && l == 0) ret = 1/l < 0 ? r : l;
else ret = std::max(l, r);
break;
}
case Eq: return Literal(l == r);
case Ne: return Literal(l != r);
case Lt: return Literal(l < r);
case Le: return Literal(l <= r);
case Gt: return Literal(l > r);
case Ge: return Literal(l >= r);
default: abort();
}
return Literal(fixNaN(l, r, ret));
} else if (left.type == f64) {
double l = left.getf64(), r = right.getf64();
double ret;
switch (curr->op) {
case Add: ret = l + r; break;
case Sub: ret = l - r; break;
case Mul: ret = l * r; break;
case Div: ret = l / r; break;
case CopySign: {
ret = std::copysign(l, r);
return Literal(ret);
}
case Min: {
if (l == r && l == 0) ret = 1/l < 0 ? l : r;
else ret = std::min(l, r);
break;
}
case Max: {
if (l == r && l == 0) ret = 1/l < 0 ? r : l;
else ret = std::max(l, r);
break;
}
case Eq: return Literal(l == r);
case Ne: return Literal(l != r);
case Lt: return Literal(l < r);
case Le: return Literal(l <= r);
case Gt: return Literal(l > r);
case Ge: return Literal(l >= r);
default: abort();
}
return Literal(fixNaN(l, r, ret));
}
abort();
}
Flow visitSelect(Select *curr) {
NOTE_ENTER("Select");
Flow condition = visit(curr->condition);
if (condition.breaking()) return condition;
NOTE_EVAL1(condition.value);
Flow ifTrue = visit(curr->ifTrue);
if (ifTrue.breaking()) return ifTrue;
Flow ifFalse = visit(curr->ifFalse);
if (ifFalse.breaking()) return ifFalse;
return condition.value.geti32() ? ifTrue : ifFalse; // ;-)
}
Flow visitReturn(Return *curr) {
NOTE_ENTER("Return");
Flow flow;
if (curr->value) {
flow = visit(curr->value);
if (flow.breaking()) return flow;
NOTE_EVAL1(flow.value);
}
flow.breakTo = RETURN_FLOW;
return flow;
}
Flow visitHost(Host *curr) {
NOTE_ENTER("Host");
switch (curr->op) {
case PageSize: return Literal((int32_t)pageSize);
case MemorySize: return Literal((int32_t)instance.memorySize);
case GrowMemory: {
Flow flow = visit(curr->operands[0]);
if (flow.breaking()) return flow;
uint32_t delta = flow.value.geti32();
if (delta % pageSize != 0) trap("growMemory: delta not multiple");
if (delta > uint32_t(-1) - pageSize) trap("growMemory: delta relatively too big");
if (instance.memorySize >= uint32_t(-1) - delta) trap("growMemory: delta objectively too big");
uint32_t newSize = instance.memorySize + delta;
if (newSize > instance.wasm.memory.max) trap("growMemory: exceeds max");
instance.externalInterface->growMemory(instance.memorySize, newSize);
instance.memorySize = newSize;
return Literal();
}
case HasFeature: {
IString id = curr->nameOperand;
if (id == WASM) return Literal(1);
return Literal((int32_t)0);
}
default: abort();
}
}
Flow visitNop(Nop *curr) {
NOTE_ENTER("Nop");
return Flow();
}
Flow visitUnreachable(Unreachable *curr) {
NOTE_ENTER("Unreachable");
trap("unreachable");
return Flow();
}
float fixNaN(float u, float result) {
if (!isnan(result)) return result;
bool unan = isnan(u);
if (!unan) {
return Literal((int32_t)0x7fc00000).reinterpretf32();
}
return result;
}
double fixNaN(double u, double result) {
if (!isnan(result)) return result;
bool unan = isnan(u);
if (!unan) {
return Literal((int64_t)0x7ff8000000000000LL).reinterpretf64();
}
return result;
}
float fixNaN(float l, float r, float result) {
bool lnan = isnan(l), rnan = isnan(r);
if (!isnan(result) && !lnan && !rnan) return result;
if (!lnan && !rnan) {
return Literal((int32_t)0x7fc00000).reinterpretf32();
}
return Literal(Literal(lnan ? l : r).reinterpreti32() | 0xc00000).reinterpretf32();
}
double fixNaN(double l, double r, double result) {
bool lnan = isnan(l), rnan = isnan(r);
if (!isnan(result) && !lnan && !rnan) return result;
if (!lnan && !rnan) {
return Literal((int64_t)0x7ff8000000000000LL).reinterpretf64();
}
return Literal(int64_t(Literal(lnan ? l : r).reinterpreti64() | 0x8000000000000LL)).reinterpretf64();
}
Literal truncSFloat(Unary* curr, Literal value) {
double val = curr->op == TruncSFloat32 ? value.getf32() : value.getf64();
if (isnan(val)) trap("truncSFloat of nan");
if (curr->type == i32) {
if (val > (double)INT_MAX || val < (double)INT_MIN) trap("i32.truncSFloat overflow");
return Literal(int32_t(val));
} else {
int64_t converted = val;
if ((val >= 1 && converted <= 0) || val < (double)LLONG_MIN) trap("i32.truncSFloat overflow");
return Literal(converted);
}
}
Literal truncUFloat(Unary* curr, Literal value) {
double val = curr->op == TruncUFloat32 ? value.getf32() : value.getf64();
if (isnan(val)) trap("truncUFloat of nan");
if (curr->type == i32) {
if (val > (double)UINT_MAX || val <= (double)-1) trap("i64.truncUFloat overflow");
return Literal(uint32_t(val));
} else {
uint64_t converted = val;
if (converted < val - 1 || val <= (double)-1) trap("i64.truncUFloat overflow");
return Literal(converted);
}
}
void trap(const char* why) {
instance.externalInterface->trap(why);
}
};
if (callDepth > maxCallDepth) externalInterface->trap("stack limit");
callDepth++;
Function *function = wasm.functionsMap[name];
assert(function);
FunctionScope scope(function, arguments);
#ifdef WASM_INTERPRETER_DEBUG
std::cout << "entering " << function->name << '\n';
#endif
Flow flow = ExpressionRunner(*this, scope).visit(function->body);
assert(!flow.breaking() || flow.breakTo == RETURN_FLOW); // cannot still be breaking, it means we missed our stop
Literal ret = flow.value;
if (function->result == none) ret = Literal();
assert(function->result == ret.type);
callDepth--;
#ifdef WASM_INTERPRETER_DEBUG
std::cout << "exiting " << function->name << " with " << ret << '\n';
#endif
return ret;
}
size_t memorySize;
template <class LS>
size_t getFinalAddress(LS* curr, Literal ptr) {
auto trapIfGt = [this](size_t lhs, size_t rhs, const char* msg) {
if (lhs > rhs) {
std::stringstream ss;
ss << msg << ": " << lhs << " > " << rhs;
externalInterface->trap(ss.str().c_str());
}
};
uint64_t addr = ptr.type == i32 ? ptr.geti32() : ptr.geti64();
trapIfGt(curr->offset, memorySize, "offset > memory");
trapIfGt(addr, memorySize - curr->offset, "final > memory");
addr += curr->offset;
trapIfGt(curr->bytes, memorySize, "bytes > memory");
trapIfGt(addr, memorySize - curr->bytes, "highest > memory");
return addr;
}
ExternalInterface* externalInterface;
};
} // namespace wasm
#endif // wasm_wasm_interpreter_h