diff options
Diffstat (limited to 'src/wasm/wasm-interpreter.cc')
-rw-r--r-- | src/wasm/wasm-interpreter.cc | 600 |
1 files changed, 333 insertions, 267 deletions
diff --git a/src/wasm/wasm-interpreter.cc b/src/wasm/wasm-interpreter.cc index 6e049ffd..f32b5e61 100644 --- a/src/wasm/wasm-interpreter.cc +++ b/src/wasm/wasm-interpreter.cc @@ -2,12 +2,18 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include <type_traits> + #include "src/wasm/wasm-interpreter.h" +#include "src/conversions.h" +#include "src/objects-inl.h" #include "src/utils.h" -#include "src/wasm/ast-decoder.h" #include "src/wasm/decoder.h" +#include "src/wasm/function-body-decoder-impl.h" +#include "src/wasm/function-body-decoder.h" #include "src/wasm/wasm-external-refs.h" +#include "src/wasm/wasm-limits.h" #include "src/wasm/wasm-module.h" #include "src/zone/accounting-allocator.h" @@ -62,6 +68,7 @@ namespace wasm { V(I64GtS, int64_t, >) \ V(I64GeS, int64_t, >=) \ V(F32Add, float, +) \ + V(F32Sub, float, -) \ V(F32Eq, float, ==) \ V(F32Ne, float, !=) \ V(F32Lt, float, <) \ @@ -69,17 +76,16 @@ namespace wasm { V(F32Gt, float, >) \ V(F32Ge, float, >=) \ V(F64Add, double, +) \ + V(F64Sub, double, -) \ V(F64Eq, double, ==) \ V(F64Ne, double, !=) \ V(F64Lt, double, <) \ V(F64Le, double, <=) \ V(F64Gt, double, >) \ - V(F64Ge, double, >=) - -#define FOREACH_SIMPLE_BINOP_NAN(V) \ - V(F32Mul, float, *) \ - V(F64Mul, double, *) \ - V(F32Div, float, /) \ + V(F64Ge, double, >=) \ + V(F32Mul, float, *) \ + V(F64Mul, double, *) \ + V(F32Div, float, /) \ V(F64Div, double, /) #define FOREACH_OTHER_BINOP(V) \ @@ -101,14 +107,10 @@ namespace wasm { V(I32Rol, int32_t) \ V(I64Ror, int64_t) \ V(I64Rol, int64_t) \ - V(F32Sub, float) \ V(F32Min, float) \ V(F32Max, float) \ - V(F32CopySign, float) \ V(F64Min, double) \ V(F64Max, double) \ - V(F64Sub, double) \ - V(F64CopySign, double) \ V(I32AsmjsDivS, int32_t) \ V(I32AsmjsDivU, uint32_t) \ V(I32AsmjsRemS, int32_t) \ @@ -158,15 +160,11 @@ namespace wasm { V(F64UConvertI64, uint64_t) \ V(F64ConvertF32, float) \ V(F64ReinterpretI64, int64_t) \ - V(I32ReinterpretF32, float) \ - V(I64ReinterpretF64, double) \ V(I32AsmjsSConvertF32, float) \ V(I32AsmjsUConvertF32, float) \ V(I32AsmjsSConvertF64, double) \ - V(I32AsmjsUConvertF64, double) - -#define FOREACH_OTHER_UNOP_NAN(V) \ - V(F32Sqrt, float) \ + V(I32AsmjsUConvertF64, double) \ + V(F32Sqrt, float) \ V(F64Sqrt, double) static inline int32_t ExecuteI32DivS(int32_t a, int32_t b, TrapReason* trap) { @@ -293,41 +291,6 @@ static inline uint64_t ExecuteI64Rol(uint64_t a, uint64_t b, TrapReason* trap) { return (a << shift) | (a >> (64 - shift)); } -static float quiet(float a) { - static const uint32_t kSignalingBit = 1 << 22; - uint32_t q = bit_cast<uint32_t>(std::numeric_limits<float>::quiet_NaN()); - if ((q & kSignalingBit) != 0) { - // On some machines, the signaling bit set indicates it's a quiet NaN. - return bit_cast<float>(bit_cast<uint32_t>(a) | kSignalingBit); - } else { - // On others, the signaling bit set indicates it's a signaling NaN. - return bit_cast<float>(bit_cast<uint32_t>(a) & ~kSignalingBit); - } -} - -static double quiet(double a) { - static const uint64_t kSignalingBit = 1ULL << 51; - uint64_t q = bit_cast<uint64_t>(std::numeric_limits<double>::quiet_NaN()); - if ((q & kSignalingBit) != 0) { - // On some machines, the signaling bit set indicates it's a quiet NaN. - return bit_cast<double>(bit_cast<uint64_t>(a) | kSignalingBit); - } else { - // On others, the signaling bit set indicates it's a signaling NaN. - return bit_cast<double>(bit_cast<uint64_t>(a) & ~kSignalingBit); - } -} - -static inline float ExecuteF32Sub(float a, float b, TrapReason* trap) { - float result = a - b; - // Some architectures (e.g. MIPS) need extra checking to preserve the payload - // of a NaN operand. - if (result - result != 0) { - if (std::isnan(a)) return quiet(a); - if (std::isnan(b)) return quiet(b); - } - return result; -} - static inline float ExecuteF32Min(float a, float b, TrapReason* trap) { return JSMin(a, b); } @@ -340,17 +303,6 @@ static inline float ExecuteF32CopySign(float a, float b, TrapReason* trap) { return copysignf(a, b); } -static inline double ExecuteF64Sub(double a, double b, TrapReason* trap) { - double result = a - b; - // Some architectures (e.g. MIPS) need extra checking to preserve the payload - // of a NaN operand. - if (result - result != 0) { - if (std::isnan(a)) return quiet(a); - if (std::isnan(b)) return quiet(b); - } - return result; -} - static inline double ExecuteF64Min(double a, double b, TrapReason* trap) { return JSMin(a, b); } @@ -651,19 +603,20 @@ static inline double ExecuteF64ReinterpretI64(int64_t a, TrapReason* trap) { return bit_cast<double>(a); } -static inline int32_t ExecuteI32ReinterpretF32(float a, TrapReason* trap) { - return bit_cast<int32_t>(a); +static inline int32_t ExecuteI32ReinterpretF32(WasmVal a) { + return a.to_unchecked<int32_t>(); } -static inline int64_t ExecuteI64ReinterpretF64(double a, TrapReason* trap) { - return bit_cast<int64_t>(a); +static inline int64_t ExecuteI64ReinterpretF64(WasmVal a) { + return a.to_unchecked<int64_t>(); } static inline int32_t ExecuteGrowMemory(uint32_t delta_pages, WasmInstance* instance) { // TODO(ahaas): Move memory allocation to wasm-module.cc for better // encapsulation. - if (delta_pages > wasm::WasmModule::kV8MaxPages) { + if (delta_pages > FLAG_wasm_max_mem_pages || + delta_pages > instance->module->max_mem_pages) { return -1; } uint32_t old_size = instance->mem_size; @@ -679,8 +632,9 @@ static inline int32_t ExecuteGrowMemory(uint32_t delta_pages, } else { DCHECK_NOT_NULL(instance->mem_start); new_size = old_size + delta_pages * wasm::WasmModule::kPageSize; - if (new_size > - wasm::WasmModule::kV8MaxPages * wasm::WasmModule::kPageSize) { + if (new_size / wasm::WasmModule::kPageSize > FLAG_wasm_max_mem_pages || + new_size / wasm::WasmModule::kPageSize > + instance->module->max_mem_pages) { return -1; } new_mem_start = static_cast<byte*>(realloc(instance->mem_start, new_size)); @@ -721,8 +675,8 @@ class ControlTransfers : public ZoneObject { public: ControlTransferMap map_; - ControlTransfers(Zone* zone, ModuleEnv* env, AstLocalDecls* locals, - const byte* start, const byte* end) + ControlTransfers(Zone* zone, BodyLocalDecls* locals, const byte* start, + const byte* end) : map_(zone) { // Represents a control flow label. struct CLabel : public ZoneObject { @@ -872,7 +826,7 @@ class ControlTransfers : public ZoneObject { // Code and metadata needed to execute a function. struct InterpreterCode { const WasmFunction* function; // wasm function - AstLocalDecls locals; // local declarations + BodyLocalDecls locals; // local declarations const byte* orig_start; // start of original code const byte* orig_end; // end of original code byte* start; // start of (maybe altered) code @@ -890,14 +844,13 @@ class CodeMap { const WasmModule* module_; ZoneVector<InterpreterCode> interpreter_code_; - CodeMap(const WasmModule* module, Zone* zone) + CodeMap(const WasmModule* module, const uint8_t* module_start, Zone* zone) : zone_(zone), module_(module), interpreter_code_(zone) { if (module == nullptr) return; for (size_t i = 0; i < module->functions.size(); ++i) { const WasmFunction* function = &module->functions[i]; - const byte* code_start = - module->module_start + function->code_start_offset; - const byte* code_end = module->module_start + function->code_end_offset; + const byte* code_start = module_start + function->code_start_offset; + const byte* code_end = module_start + function->code_end_offset; AddFunction(function, code_start, code_end); } } @@ -929,10 +882,9 @@ class CodeMap { InterpreterCode* Preprocess(InterpreterCode* code) { if (code->targets == nullptr && code->start) { // Compute the control targets map and the local declarations. - CHECK(DecodeLocalDecls(code->locals, code->start, code->end)); - ModuleEnv env = {module_, nullptr, kWasmOrigin}; + CHECK(DecodeLocalDecls(&code->locals, code->start, code->end)); code->targets = new (zone_) ControlTransfers( - zone_, &env, &code->locals, code->orig_start, code->orig_end); + zone_, &code->locals, code->orig_start, code->orig_end); } return code; } @@ -940,7 +892,7 @@ class CodeMap { int AddFunction(const WasmFunction* function, const byte* code_start, const byte* code_end) { InterpreterCode code = { - function, AstLocalDecls(zone_), code_start, + function, BodyLocalDecls(zone_), code_start, code_end, const_cast<byte*>(code_start), const_cast<byte*>(code_end), nullptr}; @@ -963,31 +915,27 @@ class CodeMap { } }; +namespace { // Responsible for executing code directly. -class ThreadImpl : public WasmInterpreter::Thread { +class ThreadImpl { public: ThreadImpl(Zone* zone, CodeMap* codemap, WasmInstance* instance) : codemap_(codemap), instance_(instance), stack_(zone), frames_(zone), - blocks_(zone), - state_(WasmInterpreter::STOPPED), - break_pc_(kInvalidPc), - trap_reason_(kTrapCount), - possible_nondeterminism_(false) {} - - virtual ~ThreadImpl() {} + blocks_(zone) {} //========================================================================== // Implementation of public interface for WasmInterpreter::Thread. //========================================================================== - virtual WasmInterpreter::State state() { return state_; } + WasmInterpreter::State state() { return state_; } - virtual void PushFrame(const WasmFunction* function, WasmVal* args) { + void PushFrame(const WasmFunction* function, WasmVal* args) { InterpreterCode* code = codemap()->FindCode(function); CHECK_NOT_NULL(code); + ++num_interpreted_calls_; frames_.push_back({code, 0, 0, stack_.size()}); for (size_t i = 0; i < function->sig->parameter_count(); ++i) { stack_.push_back(args[i]); @@ -1000,7 +948,7 @@ class ThreadImpl : public WasmInterpreter::Thread { frames_.back().ret_pc); } - virtual WasmInterpreter::State Run() { + WasmInterpreter::State Run() { do { TRACE(" => Run()\n"); if (state_ == WasmInterpreter::STOPPED || @@ -1012,7 +960,7 @@ class ThreadImpl : public WasmInterpreter::Thread { return state_; } - virtual WasmInterpreter::State Step() { + WasmInterpreter::State Step() { TRACE(" => Step()\n"); if (state_ == WasmInterpreter::STOPPED || state_ == WasmInterpreter::PAUSED) { @@ -1022,9 +970,9 @@ class ThreadImpl : public WasmInterpreter::Thread { return state_; } - virtual void Pause() { UNIMPLEMENTED(); } + void Pause() { UNIMPLEMENTED(); } - virtual void Reset() { + void Reset() { TRACE("----- RESET -----\n"); stack_.clear(); frames_.clear(); @@ -1033,33 +981,40 @@ class ThreadImpl : public WasmInterpreter::Thread { possible_nondeterminism_ = false; } - virtual int GetFrameCount() { return static_cast<int>(frames_.size()); } - - virtual const WasmFrame* GetFrame(int index) { - UNIMPLEMENTED(); - return nullptr; + int GetFrameCount() { + DCHECK_GE(kMaxInt, frames_.size()); + return static_cast<int>(frames_.size()); } - virtual WasmFrame* GetMutableFrame(int index) { - UNIMPLEMENTED(); - return nullptr; + template <typename FrameCons> + InterpretedFrame GetMutableFrame(int index, FrameCons frame_cons) { + DCHECK_LE(0, index); + DCHECK_GT(frames_.size(), index); + Frame* frame = &frames_[index]; + DCHECK_GE(kMaxInt, frame->ret_pc); + DCHECK_GE(kMaxInt, frame->sp); + DCHECK_GE(kMaxInt, frame->llimit()); + return frame_cons(frame->code->function, static_cast<int>(frame->ret_pc), + static_cast<int>(frame->sp), + static_cast<int>(frame->llimit())); } - virtual WasmVal GetReturnValue(int index) { + WasmVal GetReturnValue(int index) { if (state_ == WasmInterpreter::TRAPPED) return WasmVal(0xdeadbeef); CHECK_EQ(WasmInterpreter::FINISHED, state_); CHECK_LT(static_cast<size_t>(index), stack_.size()); return stack_[index]; } - virtual pc_t GetBreakpointPc() { return break_pc_; } + pc_t GetBreakpointPc() { return break_pc_; } - virtual bool PossibleNondeterminism() { return possible_nondeterminism_; } + bool PossibleNondeterminism() { return possible_nondeterminism_; } - bool Terminated() { - return state_ == WasmInterpreter::TRAPPED || - state_ == WasmInterpreter::FINISHED; - } + uint64_t NumInterpretedCalls() { return num_interpreted_calls_; } + + void AddBreakFlags(uint8_t flags) { break_flags_ |= flags; } + + void ClearBreakFlags() { break_flags_ = WasmInterpreter::BreakFlag::None; } private: // Entries on the stack of functions being evaluated. @@ -1072,7 +1027,7 @@ class ThreadImpl : public WasmInterpreter::Thread { // Limit of parameters. sp_t plimit() { return sp + code->function->sig->parameter_count(); } // Limit of locals. - sp_t llimit() { return plimit() + code->locals.total_local_count; } + sp_t llimit() { return plimit() + code->locals.type_list.size(); } }; struct Block { @@ -1087,10 +1042,12 @@ class ThreadImpl : public WasmInterpreter::Thread { ZoneVector<WasmVal> stack_; ZoneVector<Frame> frames_; ZoneVector<Block> blocks_; - WasmInterpreter::State state_; - pc_t break_pc_; - TrapReason trap_reason_; - bool possible_nondeterminism_; + WasmInterpreter::State state_ = WasmInterpreter::STOPPED; + pc_t break_pc_ = kInvalidPc; + TrapReason trap_reason_ = kTrapCount; + bool possible_nondeterminism_ = false; + uint8_t break_flags_ = 0; // a combination of WasmInterpreter::BreakFlag + uint64_t num_interpreted_calls_ = 0; CodeMap* codemap() { return codemap_; } WasmInstance* instance() { return instance_; } @@ -1106,6 +1063,7 @@ class ThreadImpl : public WasmInterpreter::Thread { void PushFrame(InterpreterCode* code, pc_t call_pc, pc_t ret_pc) { CHECK_NOT_NULL(code); DCHECK(!frames_.empty()); + ++num_interpreted_calls_; frames_.back().call_pc = call_pc; frames_.back().ret_pc = ret_pc; size_t arity = code->function->sig->parameter_count(); @@ -1121,28 +1079,28 @@ class ThreadImpl : public WasmInterpreter::Thread { } pc_t InitLocals(InterpreterCode* code) { - for (auto p : code->locals.local_types) { + for (auto p : code->locals.type_list) { WasmVal val; - switch (p.first) { - case kAstI32: + switch (p) { + case kWasmI32: val = WasmVal(static_cast<int32_t>(0)); break; - case kAstI64: + case kWasmI64: val = WasmVal(static_cast<int64_t>(0)); break; - case kAstF32: + case kWasmF32: val = WasmVal(static_cast<float>(0)); break; - case kAstF64: + case kWasmF64: val = WasmVal(static_cast<double>(0)); break; default: UNREACHABLE(); break; } - stack_.insert(stack_.end(), p.second, val); + stack_.push_back(val); } - return code->locals.decls_encoded_size; + return code->locals.encoded_size; } void CommitPc(pc_t pc) { @@ -1173,7 +1131,7 @@ class ThreadImpl : public WasmInterpreter::Thread { } bool DoReturn(InterpreterCode** code, pc_t* pc, pc_t* limit, size_t arity) { - DCHECK_GT(frames_.size(), 0u); + DCHECK_GT(frames_.size(), 0); // Pop all blocks for this frame. while (!blocks_.empty() && blocks_.back().fp == frames_.size()) { blocks_.pop_back(); @@ -1222,42 +1180,73 @@ class ThreadImpl : public WasmInterpreter::Thread { stack_.resize(stack_.size() - pop_count); } + template <typename ctype, typename mtype> + bool ExecuteLoad(Decoder* decoder, InterpreterCode* code, pc_t pc, int& len) { + MemoryAccessOperand operand(decoder, code->at(pc), sizeof(ctype)); + uint32_t index = Pop().to<uint32_t>(); + size_t effective_mem_size = instance()->mem_size - sizeof(mtype); + if (operand.offset > effective_mem_size || + index > (effective_mem_size - operand.offset)) { + DoTrap(kTrapMemOutOfBounds, pc); + return false; + } + byte* addr = instance()->mem_start + operand.offset + index; + WasmVal result(static_cast<ctype>(ReadLittleEndianValue<mtype>(addr))); + + Push(pc, result); + len = 1 + operand.length; + return true; + } + + template <typename ctype, typename mtype> + bool ExecuteStore(Decoder* decoder, InterpreterCode* code, pc_t pc, + int& len) { + MemoryAccessOperand operand(decoder, code->at(pc), sizeof(ctype)); + WasmVal val = Pop(); + + uint32_t index = Pop().to<uint32_t>(); + size_t effective_mem_size = instance()->mem_size - sizeof(mtype); + if (operand.offset > effective_mem_size || + index > (effective_mem_size - operand.offset)) { + DoTrap(kTrapMemOutOfBounds, pc); + return false; + } + byte* addr = instance()->mem_start + operand.offset + index; + WriteLittleEndianValue<mtype>(addr, static_cast<mtype>(val.to<ctype>())); + len = 1 + operand.length; + + if (std::is_same<float, ctype>::value) { + possible_nondeterminism_ |= std::isnan(val.to<float>()); + } else if (std::is_same<double, ctype>::value) { + possible_nondeterminism_ |= std::isnan(val.to<double>()); + } + return true; + } + void Execute(InterpreterCode* code, pc_t pc, int max) { Decoder decoder(code->start, code->end); pc_t limit = code->end - code->start; - while (true) { - if (max-- <= 0) { - // Maximum number of instructions reached. - state_ = WasmInterpreter::PAUSED; - return CommitPc(pc); - } + while (--max >= 0) { +#define PAUSE_IF_BREAK_FLAG(flag) \ + if (V8_UNLIKELY(break_flags_ & WasmInterpreter::BreakFlag::flag)) max = 0; - if (pc >= limit) { - // Fell off end of code; do an implicit return. - TRACE("@%-3zu: ImplicitReturn\n", pc); - if (!DoReturn(&code, &pc, &limit, code->function->sig->return_count())) - return; - decoder.Reset(code->start, code->end); - continue; - } + DCHECK_GT(limit, pc); const char* skip = " "; int len = 1; byte opcode = code->start[pc]; byte orig = opcode; - if (opcode == kInternalBreakpoint) { + if (V8_UNLIKELY(opcode == kInternalBreakpoint)) { orig = code->orig_start[pc]; if (SkipBreakpoint(code, pc)) { // skip breakpoint by switching on original code. skip = "[skip] "; } else { - state_ = WasmInterpreter::PAUSED; TRACE("@%-3zu: [break] %-24s:", pc, WasmOpcodes::OpcodeName(static_cast<WasmOpcode>(orig))); TraceValueStack(); TRACE("\n"); - break_pc_ = pc; - return CommitPc(pc); + break; } } @@ -1347,6 +1336,7 @@ class ThreadImpl : public WasmInterpreter::Thread { size_t arity = code->function->sig->return_count(); if (!DoReturn(&code, &pc, &limit, arity)) return; decoder.Reset(code->start, code->end); + PAUSE_IF_BREAK_FLAG(AfterReturn); continue; } case kExprUnreachable: { @@ -1357,12 +1347,6 @@ class ThreadImpl : public WasmInterpreter::Thread { blocks_.pop_back(); break; } - case kExprI8Const: { - ImmI8Operand operand(&decoder, code->at(pc)); - Push(pc, WasmVal(operand.value)); - len = 1 + operand.length; - break; - } case kExprI32Const: { ImmI32Operand operand(&decoder, code->at(pc)); Push(pc, WasmVal(operand.value)); @@ -1418,6 +1402,7 @@ class ThreadImpl : public WasmInterpreter::Thread { DoCall(target, &pc, pc + 1 + operand.length, &limit); code = target; decoder.Reset(code->start, code->end); + PAUSE_IF_BREAK_FLAG(AfterCall); continue; } case kExprCallIndirect: { @@ -1444,21 +1429,22 @@ class ThreadImpl : public WasmInterpreter::Thread { DoCall(target, &pc, pc + 1 + operand.length, &limit); code = target; decoder.Reset(code->start, code->end); + PAUSE_IF_BREAK_FLAG(AfterCall); continue; } case kExprGetGlobal: { GlobalIndexOperand operand(&decoder, code->at(pc)); const WasmGlobal* global = &module()->globals[operand.index]; byte* ptr = instance()->globals_start + global->offset; - LocalType type = global->type; + ValueType type = global->type; WasmVal val; - if (type == kAstI32) { + if (type == kWasmI32) { val = WasmVal(*reinterpret_cast<int32_t*>(ptr)); - } else if (type == kAstI64) { + } else if (type == kWasmI64) { val = WasmVal(*reinterpret_cast<int64_t*>(ptr)); - } else if (type == kAstF32) { + } else if (type == kWasmF32) { val = WasmVal(*reinterpret_cast<float*>(ptr)); - } else if (type == kAstF64) { + } else if (type == kWasmF64) { val = WasmVal(*reinterpret_cast<double*>(ptr)); } else { UNREACHABLE(); @@ -1471,15 +1457,15 @@ class ThreadImpl : public WasmInterpreter::Thread { GlobalIndexOperand operand(&decoder, code->at(pc)); const WasmGlobal* global = &module()->globals[operand.index]; byte* ptr = instance()->globals_start + global->offset; - LocalType type = global->type; + ValueType type = global->type; WasmVal val = Pop(); - if (type == kAstI32) { + if (type == kWasmI32) { *reinterpret_cast<int32_t*>(ptr) = val.to<int32_t>(); - } else if (type == kAstI64) { + } else if (type == kWasmI64) { *reinterpret_cast<int64_t*>(ptr) = val.to<int64_t>(); - } else if (type == kAstF32) { + } else if (type == kWasmF32) { *reinterpret_cast<float*>(ptr) = val.to<float>(); - } else if (type == kAstF64) { + } else if (type == kWasmF64) { *reinterpret_cast<double*>(ptr) = val.to<double>(); } else { UNREACHABLE(); @@ -1488,20 +1474,10 @@ class ThreadImpl : public WasmInterpreter::Thread { break; } -#define LOAD_CASE(name, ctype, mtype) \ - case kExpr##name: { \ - MemoryAccessOperand operand(&decoder, code->at(pc), sizeof(ctype)); \ - uint32_t index = Pop().to<uint32_t>(); \ - size_t effective_mem_size = instance()->mem_size - sizeof(mtype); \ - if (operand.offset > effective_mem_size || \ - index > (effective_mem_size - operand.offset)) { \ - return DoTrap(kTrapMemOutOfBounds, pc); \ - } \ - byte* addr = instance()->mem_start + operand.offset + index; \ - WasmVal result(static_cast<ctype>(ReadLittleEndianValue<mtype>(addr))); \ - Push(pc, result); \ - len = 1 + operand.length; \ - break; \ +#define LOAD_CASE(name, ctype, mtype) \ + case kExpr##name: { \ + if (!ExecuteLoad<ctype, mtype>(&decoder, code, pc, len)) return; \ + break; \ } LOAD_CASE(I32LoadMem8S, int32_t, int8_t); @@ -1520,20 +1496,10 @@ class ThreadImpl : public WasmInterpreter::Thread { LOAD_CASE(F64LoadMem, double, double); #undef LOAD_CASE -#define STORE_CASE(name, ctype, mtype) \ - case kExpr##name: { \ - MemoryAccessOperand operand(&decoder, code->at(pc), sizeof(ctype)); \ - WasmVal val = Pop(); \ - uint32_t index = Pop().to<uint32_t>(); \ - size_t effective_mem_size = instance()->mem_size - sizeof(mtype); \ - if (operand.offset > effective_mem_size || \ - index > (effective_mem_size - operand.offset)) { \ - return DoTrap(kTrapMemOutOfBounds, pc); \ - } \ - byte* addr = instance()->mem_start + operand.offset + index; \ - WriteLittleEndianValue<mtype>(addr, static_cast<mtype>(val.to<ctype>())); \ - len = 1 + operand.length; \ - break; \ +#define STORE_CASE(name, ctype, mtype) \ + case kExpr##name: { \ + if (!ExecuteStore<ctype, mtype>(&decoder, code, pc, len)) return; \ + break; \ } STORE_CASE(I32StoreMem8, int32_t, int8_t); @@ -1605,6 +1571,23 @@ class ThreadImpl : public WasmInterpreter::Thread { len = 1 + operand.length; break; } + // We need to treat kExprI32ReinterpretF32 and kExprI64ReinterpretF64 + // specially to guarantee that the quiet bit of a NaN is preserved on + // ia32 by the reinterpret casts. + case kExprI32ReinterpretF32: { + WasmVal val = Pop(); + WasmVal result(ExecuteI32ReinterpretF32(val)); + Push(pc, result); + possible_nondeterminism_ |= std::isnan(val.to<float>()); + break; + } + case kExprI64ReinterpretF64: { + WasmVal val = Pop(); + WasmVal result(ExecuteI64ReinterpretF64(val)); + Push(pc, result); + possible_nondeterminism_ |= std::isnan(val.to<double>()); + break; + } #define EXECUTE_SIMPLE_BINOP(name, ctype, op) \ case kExpr##name: { \ WasmVal rval = Pop(); \ @@ -1616,19 +1599,6 @@ class ThreadImpl : public WasmInterpreter::Thread { FOREACH_SIMPLE_BINOP(EXECUTE_SIMPLE_BINOP) #undef EXECUTE_SIMPLE_BINOP -#define EXECUTE_SIMPLE_BINOP_NAN(name, ctype, op) \ - case kExpr##name: { \ - WasmVal rval = Pop(); \ - WasmVal lval = Pop(); \ - ctype result = lval.to<ctype>() op rval.to<ctype>(); \ - possible_nondeterminism_ |= std::isnan(result); \ - WasmVal result_val(result); \ - Push(pc, result_val); \ - break; \ - } - FOREACH_SIMPLE_BINOP_NAN(EXECUTE_SIMPLE_BINOP_NAN) -#undef EXECUTE_SIMPLE_BINOP_NAN - #define EXECUTE_OTHER_BINOP(name, ctype) \ case kExpr##name: { \ TrapReason trap = kTrapCount; \ @@ -1642,6 +1612,28 @@ class ThreadImpl : public WasmInterpreter::Thread { FOREACH_OTHER_BINOP(EXECUTE_OTHER_BINOP) #undef EXECUTE_OTHER_BINOP + case kExprF32CopySign: { + // Handle kExprF32CopySign separately because it may introduce + // observable non-determinism. + TrapReason trap = kTrapCount; + volatile float rval = Pop().to<float>(); + volatile float lval = Pop().to<float>(); + WasmVal result(ExecuteF32CopySign(lval, rval, &trap)); + Push(pc, result); + possible_nondeterminism_ |= std::isnan(rval); + break; + } + case kExprF64CopySign: { + // Handle kExprF32CopySign separately because it may introduce + // observable non-determinism. + TrapReason trap = kTrapCount; + volatile double rval = Pop().to<double>(); + volatile double lval = Pop().to<double>(); + WasmVal result(ExecuteF64CopySign(lval, rval, &trap)); + Push(pc, result); + possible_nondeterminism_ |= std::isnan(rval); + break; + } #define EXECUTE_OTHER_UNOP(name, ctype) \ case kExpr##name: { \ TrapReason trap = kTrapCount; \ @@ -1654,20 +1646,6 @@ class ThreadImpl : public WasmInterpreter::Thread { FOREACH_OTHER_UNOP(EXECUTE_OTHER_UNOP) #undef EXECUTE_OTHER_UNOP -#define EXECUTE_OTHER_UNOP_NAN(name, ctype) \ - case kExpr##name: { \ - TrapReason trap = kTrapCount; \ - volatile ctype val = Pop().to<ctype>(); \ - ctype result = Execute##name(val, &trap); \ - possible_nondeterminism_ |= std::isnan(result); \ - WasmVal result_val(result); \ - if (trap != kTrapCount) return DoTrap(trap, pc); \ - Push(pc, result_val); \ - break; \ - } - FOREACH_OTHER_UNOP_NAN(EXECUTE_OTHER_UNOP_NAN) -#undef EXECUTE_OTHER_UNOP_NAN - default: V8_Fatal(__FILE__, __LINE__, "Unknown or unimplemented opcode #%d:%s", code->start[pc], OpcodeName(code->start[pc])); @@ -1675,13 +1653,25 @@ class ThreadImpl : public WasmInterpreter::Thread { } pc += len; + if (pc == limit) { + // Fell off end of code; do an implicit return. + TRACE("@%-3zu: ImplicitReturn\n", pc); + if (!DoReturn(&code, &pc, &limit, code->function->sig->return_count())) + return; + decoder.Reset(code->start, code->end); + PAUSE_IF_BREAK_FLAG(AfterReturn); + } } - UNREACHABLE(); // above decoding loop should run forever. + // Set break_pc_, even though we might have stopped because max was reached. + // We don't want to stop after executing zero instructions next time. + break_pc_ = pc; + state_ = WasmInterpreter::PAUSED; + CommitPc(pc); } WasmVal Pop() { - DCHECK_GT(stack_.size(), 0u); - DCHECK_GT(frames_.size(), 0u); + DCHECK_GT(stack_.size(), 0); + DCHECK_GT(frames_.size(), 0); DCHECK_GT(stack_.size(), frames_.back().llimit()); // can't pop into locals WasmVal val = stack_.back(); stack_.pop_back(); @@ -1689,8 +1679,8 @@ class ThreadImpl : public WasmInterpreter::Thread { } void PopN(int n) { - DCHECK_GE(stack_.size(), static_cast<size_t>(n)); - DCHECK_GT(frames_.size(), 0u); + DCHECK_GE(stack_.size(), n); + DCHECK_GT(frames_.size(), 0); size_t nsize = stack_.size() - n; DCHECK_GE(nsize, frames_.back().llimit()); // can't pop into locals stack_.resize(nsize); @@ -1698,13 +1688,13 @@ class ThreadImpl : public WasmInterpreter::Thread { WasmVal PopArity(size_t arity) { if (arity == 0) return WasmVal(); - CHECK_EQ(1u, arity); + CHECK_EQ(1, arity); return Pop(); } void Push(pc_t pc, WasmVal val) { // TODO(titzer): store PC as well? - if (val.type != kAstStmt) stack_.push_back(val); + if (val.type != kWasmStmt) stack_.push_back(val); } void TraceStack(const char* phase, pc_t pc) { @@ -1716,6 +1706,7 @@ class ThreadImpl : public WasmInterpreter::Thread { } void TraceValueStack() { +#ifdef DEBUG Frame* top = frames_.size() > 0 ? &frames_.back() : nullptr; sp_t sp = top ? top->sp : 0; sp_t plimit = top ? top->plimit() : 0; @@ -1730,19 +1721,19 @@ class ThreadImpl : public WasmInterpreter::Thread { PrintF(" s%zu:", i); WasmVal val = stack_[i]; switch (val.type) { - case kAstI32: + case kWasmI32: PrintF("i32:%d", val.to<int32_t>()); break; - case kAstI64: + case kWasmI64: PrintF("i64:%" PRId64 "", val.to<int64_t>()); break; - case kAstF32: + case kWasmF32: PrintF("f32:%f", val.to<float>()); break; - case kAstF64: + case kWasmF64: PrintF("f64:%lf", val.to<double>()); break; - case kAstStmt: + case kWasmStmt: PrintF("void"); break; default: @@ -1751,45 +1742,114 @@ class ThreadImpl : public WasmInterpreter::Thread { } } } +#endif // DEBUG } }; +// Converters between WasmInterpreter::Thread and WasmInterpreter::ThreadImpl. +// Thread* is the public interface, without knowledge of the object layout. +// This cast is potentially risky, but as long as we always cast it back before +// accessing any data, it should be fine. UBSan is not complaining. +WasmInterpreter::Thread* ToThread(ThreadImpl* impl) { + return reinterpret_cast<WasmInterpreter::Thread*>(impl); +} +static ThreadImpl* ToImpl(WasmInterpreter::Thread* thread) { + return reinterpret_cast<ThreadImpl*>(thread); +} +} // namespace + +//============================================================================ +// Implementation of the pimpl idiom for WasmInterpreter::Thread. +// Instead of placing a pointer to the ThreadImpl inside of the Thread object, +// we just reinterpret_cast them. ThreadImpls are only allocated inside this +// translation unit anyway. +//============================================================================ +WasmInterpreter::State WasmInterpreter::Thread::state() { + return ToImpl(this)->state(); +} +void WasmInterpreter::Thread::PushFrame(const WasmFunction* function, + WasmVal* args) { + return ToImpl(this)->PushFrame(function, args); +} +WasmInterpreter::State WasmInterpreter::Thread::Run() { + return ToImpl(this)->Run(); +} +WasmInterpreter::State WasmInterpreter::Thread::Step() { + return ToImpl(this)->Step(); +} +void WasmInterpreter::Thread::Pause() { return ToImpl(this)->Pause(); } +void WasmInterpreter::Thread::Reset() { return ToImpl(this)->Reset(); } +pc_t WasmInterpreter::Thread::GetBreakpointPc() { + return ToImpl(this)->GetBreakpointPc(); +} +int WasmInterpreter::Thread::GetFrameCount() { + return ToImpl(this)->GetFrameCount(); +} +const InterpretedFrame WasmInterpreter::Thread::GetFrame(int index) { + return GetMutableFrame(index); +} +InterpretedFrame WasmInterpreter::Thread::GetMutableFrame(int index) { + // We have access to the constructor of InterpretedFrame, but ThreadImpl has + // not. So pass it as a lambda (should all get inlined). + auto frame_cons = [](const WasmFunction* function, int pc, int fp, int sp) { + return InterpretedFrame(function, pc, fp, sp); + }; + return ToImpl(this)->GetMutableFrame(index, frame_cons); +} +WasmVal WasmInterpreter::Thread::GetReturnValue(int index) { + return ToImpl(this)->GetReturnValue(index); +} +bool WasmInterpreter::Thread::PossibleNondeterminism() { + return ToImpl(this)->PossibleNondeterminism(); +} +uint64_t WasmInterpreter::Thread::NumInterpretedCalls() { + return ToImpl(this)->NumInterpretedCalls(); +} +void WasmInterpreter::Thread::AddBreakFlags(uint8_t flags) { + ToImpl(this)->AddBreakFlags(flags); +} +void WasmInterpreter::Thread::ClearBreakFlags() { + ToImpl(this)->ClearBreakFlags(); +} + //============================================================================ // The implementation details of the interpreter. //============================================================================ class WasmInterpreterInternals : public ZoneObject { public: WasmInstance* instance_; + // Create a copy of the module bytes for the interpreter, since the passed + // pointer might be invalidated after constructing the interpreter. + const ZoneVector<uint8_t> module_bytes_; CodeMap codemap_; - ZoneVector<ThreadImpl*> threads_; - - WasmInterpreterInternals(Zone* zone, WasmInstance* instance) - : instance_(instance), - codemap_(instance_ ? instance_->module : nullptr, zone), + ZoneVector<ThreadImpl> threads_; + + WasmInterpreterInternals(Zone* zone, const ModuleBytesEnv& env) + : instance_(env.module_env.instance), + module_bytes_(env.wire_bytes.start(), env.wire_bytes.end(), zone), + codemap_( + env.module_env.instance ? env.module_env.instance->module : nullptr, + module_bytes_.data(), zone), threads_(zone) { - threads_.push_back(new ThreadImpl(zone, &codemap_, instance)); + threads_.emplace_back(zone, &codemap_, env.module_env.instance); } - void Delete() { - // TODO(titzer): CFI doesn't like threads in the ZoneVector. - for (auto t : threads_) delete t; - threads_.resize(0); - } + void Delete() { threads_.clear(); } }; //============================================================================ // Implementation of the public interface of the interpreter. //============================================================================ -WasmInterpreter::WasmInterpreter(WasmInstance* instance, +WasmInterpreter::WasmInterpreter(const ModuleBytesEnv& env, AccountingAllocator* allocator) : zone_(allocator, ZONE_NAME), - internals_(new (&zone_) WasmInterpreterInternals(&zone_, instance)) {} + internals_(new (&zone_) WasmInterpreterInternals(&zone_, env)) {} WasmInterpreter::~WasmInterpreter() { internals_->Delete(); } -void WasmInterpreter::Run() { internals_->threads_[0]->Run(); } +void WasmInterpreter::Run() { internals_->threads_[0].Run(); } -void WasmInterpreter::Pause() { internals_->threads_[0]->Pause(); } +void WasmInterpreter::Pause() { internals_->threads_[0].Pause(); } bool WasmInterpreter::SetBreakpoint(const WasmFunction* function, pc_t pc, bool enabled) { @@ -1797,7 +1857,7 @@ bool WasmInterpreter::SetBreakpoint(const WasmFunction* function, pc_t pc, if (!code) return false; size_t size = static_cast<size_t>(code->end - code->start); // Check bounds for {pc}. - if (pc < code->locals.decls_encoded_size || pc >= size) return false; + if (pc < code->locals.encoded_size || pc >= size) return false; // Make a copy of the code before enabling a breakpoint. if (enabled && code->orig_start == code->start) { code->start = reinterpret_cast<byte*>(zone_.New(size)); @@ -1818,7 +1878,7 @@ bool WasmInterpreter::GetBreakpoint(const WasmFunction* function, pc_t pc) { if (!code) return false; size_t size = static_cast<size_t>(code->end - code->start); // Check bounds for {pc}. - if (pc < code->locals.decls_encoded_size || pc >= size) return false; + if (pc < code->locals.encoded_size || pc >= size) return false; // Check if a breakpoint is present at that place in the code. return code->start[pc] == kInternalBreakpoint; } @@ -1834,30 +1894,7 @@ int WasmInterpreter::GetThreadCount() { WasmInterpreter::Thread* WasmInterpreter::GetThread(int id) { CHECK_EQ(0, id); // only one thread for now. - return internals_->threads_[id]; -} - -WasmVal WasmInterpreter::GetLocalVal(const WasmFrame* frame, int index) { - CHECK_GE(index, 0); - UNIMPLEMENTED(); - WasmVal none; - none.type = kAstStmt; - return none; -} - -WasmVal WasmInterpreter::GetExprVal(const WasmFrame* frame, int pc) { - UNIMPLEMENTED(); - WasmVal none; - none.type = kAstStmt; - return none; -} - -void WasmInterpreter::SetLocalVal(WasmFrame* frame, int index, WasmVal val) { - UNIMPLEMENTED(); -} - -void WasmInterpreter::SetExprVal(WasmFrame* frame, int pc, WasmVal val) { - UNIMPLEMENTED(); + return ToThread(&internals_->threads_[id]); } size_t WasmInterpreter::GetMemorySize() { @@ -1885,10 +1922,39 @@ bool WasmInterpreter::SetFunctionCodeForTesting(const WasmFunction* function, ControlTransferMap WasmInterpreter::ComputeControlTransfersForTesting( Zone* zone, const byte* start, const byte* end) { - ControlTransfers targets(zone, nullptr, nullptr, start, end); + ControlTransfers targets(zone, nullptr, start, end); return targets.map_; } +//============================================================================ +// Implementation of the frame inspection interface. +//============================================================================ +int InterpretedFrame::GetParameterCount() const { + USE(fp_); + USE(sp_); + // TODO(clemensh): Return the correct number of parameters. + return 0; +} + +WasmVal InterpretedFrame::GetLocalVal(int index) const { + CHECK_GE(index, 0); + UNIMPLEMENTED(); + WasmVal none; + none.type = kWasmStmt; + return none; +} + +WasmVal InterpretedFrame::GetExprVal(int pc) const { + UNIMPLEMENTED(); + WasmVal none; + none.type = kWasmStmt; + return none; +} + +void InterpretedFrame::SetLocalVal(int index, WasmVal val) { UNIMPLEMENTED(); } + +void InterpretedFrame::SetExprVal(int pc, WasmVal val) { UNIMPLEMENTED(); } + } // namespace wasm } // namespace internal } // namespace v8 |