diff options
Diffstat (limited to 'deps/v8/src/wasm/function-body-decoder-impl.h')
-rw-r--r-- | deps/v8/src/wasm/function-body-decoder-impl.h | 1734 |
1 files changed, 998 insertions, 736 deletions
diff --git a/deps/v8/src/wasm/function-body-decoder-impl.h b/deps/v8/src/wasm/function-body-decoder-impl.h index 0e49ee7e97..578a0ff5b7 100644 --- a/deps/v8/src/wasm/function-body-decoder-impl.h +++ b/deps/v8/src/wasm/function-body-decoder-impl.h @@ -61,6 +61,9 @@ struct WasmException; (message))) #define ATOMIC_OP_LIST(V) \ + V(AtomicWake, Uint32) \ + V(I32AtomicWait, Uint32) \ + V(I64AtomicWait, Uint32) \ V(I32AtomicLoad, Uint32) \ V(I64AtomicLoad, Uint64) \ V(I32AtomicLoad8U, Uint8) \ @@ -127,17 +130,12 @@ struct WasmException; V(I64AtomicStore16U, Uint16) \ V(I64AtomicStore32U, Uint32) -template <typename T, typename Allocator> -Vector<T> vec2vec(std::vector<T, Allocator>& vec) { - return Vector<T>(vec.data(), vec.size()); -} - // Helpers for decoding different kinds of immediates which follow bytecodes. template <Decoder::ValidateFlag validate> struct LocalIndexImmediate { uint32_t index; ValueType type = kWasmStmt; - unsigned length; + uint32_t length; inline LocalIndexImmediate(Decoder* decoder, const byte* pc) { index = decoder->read_u32v<validate>(pc + 1, &length, "local index"); @@ -148,7 +146,7 @@ template <Decoder::ValidateFlag validate> struct ExceptionIndexImmediate { uint32_t index; const WasmException* exception = nullptr; - unsigned length; + uint32_t length; inline ExceptionIndexImmediate(Decoder* decoder, const byte* pc) { index = decoder->read_u32v<validate>(pc + 1, &length, "exception index"); @@ -158,7 +156,7 @@ struct ExceptionIndexImmediate { template <Decoder::ValidateFlag validate> struct ImmI32Immediate { int32_t value; - unsigned length; + uint32_t length; inline ImmI32Immediate(Decoder* decoder, const byte* pc) { value = decoder->read_i32v<validate>(pc + 1, &length, "immi32"); } @@ -167,7 +165,7 @@ struct ImmI32Immediate { template <Decoder::ValidateFlag validate> struct ImmI64Immediate { int64_t value; - unsigned length; + uint32_t length; inline ImmI64Immediate(Decoder* decoder, const byte* pc) { value = decoder->read_i64v<validate>(pc + 1, &length, "immi64"); } @@ -176,7 +174,7 @@ struct ImmI64Immediate { template <Decoder::ValidateFlag validate> struct ImmF32Immediate { float value; - unsigned length = 4; + uint32_t length = 4; inline ImmF32Immediate(Decoder* decoder, const byte* pc) { // Avoid bit_cast because it might not preserve the signalling bit of a NaN. uint32_t tmp = decoder->read_u32<validate>(pc + 1, "immf32"); @@ -187,7 +185,7 @@ struct ImmF32Immediate { template <Decoder::ValidateFlag validate> struct ImmF64Immediate { double value; - unsigned length = 8; + uint32_t length = 8; inline ImmF64Immediate(Decoder* decoder, const byte* pc) { // Avoid bit_cast because it might not preserve the signalling bit of a NaN. uint64_t tmp = decoder->read_u64<validate>(pc + 1, "immf64"); @@ -200,7 +198,7 @@ struct GlobalIndexImmediate { uint32_t index; ValueType type = kWasmStmt; const WasmGlobal* global = nullptr; - unsigned length; + uint32_t length; inline GlobalIndexImmediate(Decoder* decoder, const byte* pc) { index = decoder->read_u32v<validate>(pc + 1, &length, "global index"); @@ -209,7 +207,7 @@ struct GlobalIndexImmediate { template <Decoder::ValidateFlag validate> struct BlockTypeImmediate { - unsigned length = 1; + uint32_t length = 1; ValueType type = kWasmStmt; uint32_t sig_index = 0; FunctionSig* sig = nullptr; @@ -290,11 +288,11 @@ struct BlockTypeImmediate { }; template <Decoder::ValidateFlag validate> -struct BreakDepthImmediate { +struct BranchDepthImmediate { uint32_t depth; - unsigned length; - inline BreakDepthImmediate(Decoder* decoder, const byte* pc) { - depth = decoder->read_u32v<validate>(pc + 1, &length, "break depth"); + uint32_t length; + inline BranchDepthImmediate(Decoder* decoder, const byte* pc) { + depth = decoder->read_u32v<validate>(pc + 1, &length, "branch depth"); } }; @@ -303,9 +301,9 @@ struct CallIndirectImmediate { uint32_t table_index; uint32_t sig_index; FunctionSig* sig = nullptr; - unsigned length = 0; + uint32_t length = 0; inline CallIndirectImmediate(Decoder* decoder, const byte* pc) { - unsigned len = 0; + uint32_t len = 0; sig_index = decoder->read_u32v<validate>(pc + 1, &len, "signature index"); if (!VALIDATE(decoder->ok())) return; table_index = decoder->read_u8<validate>(pc + 1 + len, "table index"); @@ -321,7 +319,7 @@ template <Decoder::ValidateFlag validate> struct CallFunctionImmediate { uint32_t index; FunctionSig* sig = nullptr; - unsigned length; + uint32_t length; inline CallFunctionImmediate(Decoder* decoder, const byte* pc) { index = decoder->read_u32v<validate>(pc + 1, &length, "function index"); } @@ -330,7 +328,7 @@ struct CallFunctionImmediate { template <Decoder::ValidateFlag validate> struct MemoryIndexImmediate { uint32_t index; - unsigned length = 1; + uint32_t length = 1; inline MemoryIndexImmediate(Decoder* decoder, const byte* pc) { index = decoder->read_u8<validate>(pc + 1, "memory index"); if (!VALIDATE(index == 0)) { @@ -340,6 +338,18 @@ struct MemoryIndexImmediate { }; template <Decoder::ValidateFlag validate> +struct TableIndexImmediate { + uint32_t index; + unsigned length = 1; + inline TableIndexImmediate(Decoder* decoder, const byte* pc) { + index = decoder->read_u8<validate>(pc + 1, "table index"); + if (!VALIDATE(index == 0)) { + decoder->errorf(pc + 1, "expected table index 0, found %u", index); + } + } +}; + +template <Decoder::ValidateFlag validate> struct BranchTableImmediate { uint32_t table_count; const byte* start; @@ -347,7 +357,7 @@ struct BranchTableImmediate { inline BranchTableImmediate(Decoder* decoder, const byte* pc) { DCHECK_EQ(kExprBrTable, decoder->read_u8<validate>(pc, "opcode")); start = pc + 1; - unsigned len = 0; + uint32_t len = 0; table_count = decoder->read_u32v<validate>(pc + 1, &len, "table count"); table = pc + 1 + len; } @@ -357,12 +367,12 @@ struct BranchTableImmediate { template <Decoder::ValidateFlag validate> class BranchTableIterator { public: - unsigned cur_index() { return index_; } + uint32_t cur_index() { return index_; } bool has_next() { return VALIDATE(decoder_->ok()) && index_ <= table_count_; } uint32_t next() { DCHECK(has_next()); index_++; - unsigned length; + uint32_t length; uint32_t result = decoder_->read_u32v<validate>(pc_, &length, "branch table entry"); pc_ += length; @@ -370,9 +380,9 @@ class BranchTableIterator { } // length, including the length of the {BranchTableImmediate}, but not the // opcode. - unsigned length() { + uint32_t length() { while (has_next()) next(); - return static_cast<unsigned>(pc_ - start_); + return static_cast<uint32_t>(pc_ - start_); } const byte* pc() { return pc_; } @@ -395,10 +405,10 @@ template <Decoder::ValidateFlag validate> struct MemoryAccessImmediate { uint32_t alignment; uint32_t offset; - unsigned length = 0; + uint32_t length = 0; inline MemoryAccessImmediate(Decoder* decoder, const byte* pc, uint32_t max_alignment) { - unsigned alignment_length; + uint32_t alignment_length; alignment = decoder->read_u32v<validate>(pc + 1, &alignment_length, "alignment"); if (!VALIDATE(alignment <= max_alignment)) { @@ -408,7 +418,7 @@ struct MemoryAccessImmediate { max_alignment, alignment); } if (!VALIDATE(decoder->ok())) return; - unsigned offset_length; + uint32_t offset_length; offset = decoder->read_u32v<validate>(pc + 1 + alignment_length, &offset_length, "offset"); length = alignment_length + offset_length; @@ -419,7 +429,7 @@ struct MemoryAccessImmediate { template <Decoder::ValidateFlag validate> struct SimdLaneImmediate { uint8_t lane; - unsigned length = 1; + uint32_t length = 1; inline SimdLaneImmediate(Decoder* decoder, const byte* pc) { lane = decoder->read_u8<validate>(pc + 2, "lane"); @@ -430,7 +440,7 @@ struct SimdLaneImmediate { template <Decoder::ValidateFlag validate> struct SimdShiftImmediate { uint8_t shift; - unsigned length = 1; + uint32_t length = 1; inline SimdShiftImmediate(Decoder* decoder, const byte* pc) { shift = decoder->read_u8<validate>(pc + 2, "shift"); @@ -450,24 +460,73 @@ struct Simd8x16ShuffleImmediate { } }; +template <Decoder::ValidateFlag validate> +struct MemoryInitImmediate { + MemoryIndexImmediate<validate> memory; + uint32_t data_segment_index = 0; + unsigned length = 0; + + inline MemoryInitImmediate(Decoder* decoder, const byte* pc) + : memory(decoder, pc + 1) { + if (!VALIDATE(decoder->ok())) return; + uint32_t len = 0; + data_segment_index = decoder->read_i32v<validate>( + pc + 2 + memory.length, &len, "data segment index"); + length = memory.length + len; + } +}; + +template <Decoder::ValidateFlag validate> +struct MemoryDropImmediate { + uint32_t index; + unsigned length; + + inline MemoryDropImmediate(Decoder* decoder, const byte* pc) { + index = decoder->read_i32v<validate>(pc + 2, &length, "data segment index"); + } +}; + +template <Decoder::ValidateFlag validate> +struct TableInitImmediate { + TableIndexImmediate<validate> table; + uint32_t elem_segment_index = 0; + unsigned length = 0; + + inline TableInitImmediate(Decoder* decoder, const byte* pc) + : table(decoder, pc + 1) { + if (!VALIDATE(decoder->ok())) return; + uint32_t len = 0; + elem_segment_index = decoder->read_i32v<validate>( + pc + 2 + table.length, &len, "elem segment index"); + length = table.length + len; + } +}; + +template <Decoder::ValidateFlag validate> +struct TableDropImmediate { + uint32_t index; + unsigned length; + + inline TableDropImmediate(Decoder* decoder, const byte* pc) { + index = decoder->read_i32v<validate>(pc + 2, &length, "elem segment index"); + } +}; + // An entry on the value stack. struct ValueBase { - const byte* pc; - ValueType type; - - // Named constructors. - static ValueBase Unreachable(const byte* pc) { return {pc, kWasmVar}; } + const byte* pc = nullptr; + ValueType type = kWasmStmt; - static ValueBase New(const byte* pc, ValueType type) { return {pc, type}; } + ValueBase(const byte* pc, ValueType type) : pc(pc), type(type) {} }; template <typename Value> struct Merge { - uint32_t arity; - union { + uint32_t arity = 0; + union { // Either multiple values or a single value. Value* array; Value first; - } vals; // Either multiple values or a single value. + } vals = {nullptr}; // Initialize {array} with {nullptr}. // Tracks whether this merge was ever reached. Uses precise reachability, like // Reachability::kReachable. @@ -487,8 +546,7 @@ enum ControlKind : uint8_t { kControlBlock, kControlLoop, kControlTry, - kControlTryCatch, - kControlTryCatchAll + kControlTryCatch }; enum Reachability : uint8_t { @@ -503,18 +561,24 @@ enum Reachability : uint8_t { // An entry on the control stack (i.e. if, block, loop, or try). template <typename Value> struct ControlBase { - ControlKind kind; - uint32_t stack_depth; // stack height at the beginning of the construct. - const byte* pc; + ControlKind kind = kControlBlock; + uint32_t stack_depth = 0; // stack height at the beginning of the construct. + const uint8_t* pc = nullptr; Reachability reachability = kReachable; // Values merged into the start or end of this control construct. Merge<Value> start_merge; Merge<Value> end_merge; - ControlBase() = default; - ControlBase(ControlKind kind, uint32_t stack_depth, const byte* pc) - : kind(kind), stack_depth(stack_depth), pc(pc) {} + MOVE_ONLY_NO_DEFAULT_CONSTRUCTOR(ControlBase); + + ControlBase(ControlKind kind, uint32_t stack_depth, const uint8_t* pc, + Reachability reachability) + : kind(kind), + stack_depth(stack_depth), + pc(pc), + reachability(reachability), + start_merge(reachability == kReachable) {} // Check whether the current block is reachable. bool reachable() const { return reachability == kReachable; } @@ -537,67 +601,11 @@ struct ControlBase { bool is_loop() const { return kind == kControlLoop; } bool is_incomplete_try() const { return kind == kControlTry; } bool is_try_catch() const { return kind == kControlTryCatch; } - bool is_try_catchall() const { return kind == kControlTryCatchAll; } - bool is_try() const { - return is_incomplete_try() || is_try_catch() || is_try_catchall(); - } + bool is_try() const { return is_incomplete_try() || is_try_catch(); } inline Merge<Value>* br_merge() { return is_loop() ? &this->start_merge : &this->end_merge; } - - // Named constructors. - static ControlBase Block(const byte* pc, uint32_t stack_depth) { - return {kControlBlock, stack_depth, pc}; - } - - static ControlBase If(const byte* pc, uint32_t stack_depth) { - return {kControlIf, stack_depth, pc}; - } - - static ControlBase Loop(const byte* pc, uint32_t stack_depth) { - return {kControlLoop, stack_depth, pc}; - } - - static ControlBase Try(const byte* pc, uint32_t stack_depth) { - return {kControlTry, stack_depth, pc}; - } -}; - -#define CONCRETE_NAMED_CONSTRUCTOR(concrete_type, abstract_type, name) \ - template <typename... Args> \ - static concrete_type name(Args&&... args) { \ - concrete_type val; \ - static_cast<abstract_type&>(val) = \ - abstract_type::name(std::forward<Args>(args)...); \ - return val; \ - } - -// Provide the default named constructors, which default-initialize the -// ConcreteType and the initialize the fields of ValueBase correctly. -// Use like this: -// struct Value : public ValueWithNamedConstructors<Value> { int new_field; }; -template <typename ConcreteType> -struct ValueWithNamedConstructors : public ValueBase { - // Named constructors. - CONCRETE_NAMED_CONSTRUCTOR(ConcreteType, ValueBase, Unreachable) - CONCRETE_NAMED_CONSTRUCTOR(ConcreteType, ValueBase, New) -}; - -// Provide the default named constructors, which default-initialize the -// ConcreteType and the initialize the fields of ControlBase correctly. -// Use like this: -// struct Control : public ControlWithNamedConstructors<Control, Value> { -// int my_uninitialized_field; -// char* other_field = nullptr; -// }; -template <typename ConcreteType, typename Value> -struct ControlWithNamedConstructors : public ControlBase<Value> { - // Named constructors. - CONCRETE_NAMED_CONSTRUCTOR(ConcreteType, ControlBase<Value>, Block) - CONCRETE_NAMED_CONSTRUCTOR(ConcreteType, ControlBase<Value>, If) - CONCRETE_NAMED_CONSTRUCTOR(ConcreteType, ControlBase<Value>, Loop) - CONCRETE_NAMED_CONSTRUCTOR(ConcreteType, ControlBase<Value>, Try) }; // This is the list of callback functions that an interface for the @@ -614,21 +622,22 @@ struct ControlWithNamedConstructors : public ControlBase<Value> { F(Block, Control* block) \ F(Loop, Control* block) \ F(Try, Control* block) \ + F(Catch, Control* block, Value* exception) \ F(If, const Value& cond, Control* if_block) \ F(FallThruTo, Control* c) \ F(PopControl, Control* block) \ F(EndControl, Control* block) \ /* Instructions: */ \ - F(UnOp, WasmOpcode opcode, FunctionSig*, const Value& value, Value* result) \ - F(BinOp, WasmOpcode opcode, FunctionSig*, const Value& lhs, \ - const Value& rhs, Value* result) \ + F(UnOp, WasmOpcode opcode, const Value& value, Value* result) \ + F(BinOp, WasmOpcode opcode, const Value& lhs, const Value& rhs, \ + Value* result) \ F(I32Const, Value* result, int32_t value) \ F(I64Const, Value* result, int64_t value) \ F(F32Const, Value* result, float value) \ F(F64Const, Value* result, double value) \ F(RefNull, Value* result) \ F(Drop, const Value& value) \ - F(DoReturn, Vector<Value> values, bool implicit) \ + F(DoReturn, Vector<Value> values) \ F(GetLocal, Value* result, const LocalIndexImmediate<validate>& imm) \ F(SetLocal, const Value& value, const LocalIndexImmediate<validate>& imm) \ F(TeeLocal, const Value& value, Value* result, \ @@ -639,7 +648,7 @@ struct ControlWithNamedConstructors : public ControlBase<Value> { F(Select, const Value& cond, const Value& fval, const Value& tval, \ Value* result) \ F(Br, Control* target) \ - F(BrIf, const Value& cond, Control* target) \ + F(BrIf, const Value& cond, uint32_t depth) \ F(BrTable, const BranchTableImmediate<validate>& imm, const Value& key) \ F(Else, Control* if_block) \ F(LoadMem, LoadType type, const MemoryAccessImmediate<validate>& imm, \ @@ -647,7 +656,7 @@ struct ControlWithNamedConstructors : public ControlBase<Value> { F(StoreMem, StoreType type, const MemoryAccessImmediate<validate>& imm, \ const Value& index, const Value& value) \ F(CurrentMemoryPages, Value* result) \ - F(GrowMemory, const Value& value, Value* result) \ + F(MemoryGrow, const Value& value, Value* result) \ F(CallDirect, const CallFunctionImmediate<validate>& imm, \ const Value args[], Value returns[]) \ F(CallIndirect, const Value& index, \ @@ -660,12 +669,24 @@ struct ControlWithNamedConstructors : public ControlBase<Value> { const Value& input, Value* result) \ F(Simd8x16ShuffleOp, const Simd8x16ShuffleImmediate<validate>& imm, \ const Value& input0, const Value& input1, Value* result) \ - F(Throw, const ExceptionIndexImmediate<validate>&, Control* block, \ + F(Throw, const ExceptionIndexImmediate<validate>& imm, \ const Vector<Value>& args) \ - F(CatchException, const ExceptionIndexImmediate<validate>& imm, \ - Control* block, Vector<Value> caught_values) \ + F(Rethrow, const Value& exception) \ + F(BrOnException, const Value& exception, \ + const ExceptionIndexImmediate<validate>& imm, uint32_t depth, \ + Vector<Value> values) \ F(AtomicOp, WasmOpcode opcode, Vector<Value> args, \ - const MemoryAccessImmediate<validate>& imm, Value* result) + const MemoryAccessImmediate<validate>& imm, Value* result) \ + F(MemoryInit, const MemoryInitImmediate<validate>& imm, const Value& dst, \ + const Value& src, const Value& size) \ + F(MemoryDrop, const MemoryDropImmediate<validate>& imm) \ + F(MemoryCopy, const MemoryIndexImmediate<validate>& imm, const Value& dst, \ + const Value& src, const Value& size) \ + F(MemoryFill, const MemoryIndexImmediate<validate>& imm, const Value& dst, \ + const Value& value, const Value& size) \ + F(TableInit, const TableInitImmediate<validate>& imm, Vector<Value> args) \ + F(TableDrop, const TableDropImmediate<validate>& imm) \ + F(TableCopy, const TableIndexImmediate<validate>& imm, Vector<Value> args) // Generic Wasm bytecode decoder with utilities for decoding immediates, // lengths, etc. @@ -776,7 +797,7 @@ class WasmDecoder : public Decoder { // Iteratively process all AST nodes nested inside the loop. while (pc < decoder->end() && VALIDATE(decoder->ok())) { WasmOpcode opcode = static_cast<WasmOpcode>(*pc); - unsigned length = 1; + uint32_t length = 1; switch (opcode) { case kExprLoop: case kExprIf: @@ -796,7 +817,7 @@ class WasmDecoder : public Decoder { length = 1 + imm.length; break; } - case kExprGrowMemory: + case kExprMemoryGrow: case kExprCallFunction: case kExprCallIndirect: // Add instance cache nodes to the assigned set. @@ -826,16 +847,23 @@ class WasmDecoder : public Decoder { return true; } - inline bool Validate(const byte* pc, ExceptionIndexImmediate<validate>& imm) { + inline bool Complete(const byte* pc, ExceptionIndexImmediate<validate>& imm) { if (!VALIDATE(module_ != nullptr && imm.index < module_->exceptions.size())) { - errorf(pc + 1, "Invalid exception index: %u", imm.index); return false; } imm.exception = &module_->exceptions[imm.index]; return true; } + inline bool Validate(const byte* pc, ExceptionIndexImmediate<validate>& imm) { + if (!Complete(pc, imm)) { + errorf(pc + 1, "Invalid exception index: %u", imm.index); + return false; + } + return true; + } + inline bool Validate(const byte* pc, GlobalIndexImmediate<validate>& imm) { if (!VALIDATE(module_ != nullptr && imm.index < module_->globals.size())) { errorf(pc + 1, "invalid global index: %u", imm.index); @@ -884,10 +912,10 @@ class WasmDecoder : public Decoder { return true; } - inline bool Validate(const byte* pc, BreakDepthImmediate<validate>& imm, + inline bool Validate(const byte* pc, BranchDepthImmediate<validate>& imm, size_t control_depth) { if (!VALIDATE(imm.depth < control_depth)) { - errorf(pc + 1, "invalid break depth: %u", imm.depth); + errorf(pc + 1, "invalid branch depth: %u", imm.depth); return false; } return true; @@ -996,7 +1024,63 @@ class WasmDecoder : public Decoder { return true; } - static unsigned OpcodeLength(Decoder* decoder, const byte* pc) { + inline bool Validate(MemoryIndexImmediate<validate>& imm) { + if (!VALIDATE(module_ != nullptr && module_->has_memory)) { + errorf(pc_ + 1, "memory instruction with no memory"); + return false; + } + return true; + } + + inline bool Validate(MemoryInitImmediate<validate>& imm) { + if (!Validate(imm.memory)) return false; + if (!VALIDATE(module_ != nullptr && + imm.data_segment_index < + module_->num_declared_data_segments)) { + errorf(pc_ + 2, "invalid data segment index: %u", imm.data_segment_index); + return false; + } + return true; + } + + inline bool Validate(MemoryDropImmediate<validate>& imm) { + if (!VALIDATE(module_ != nullptr && + imm.index < module_->num_declared_data_segments)) { + errorf(pc_ + 2, "invalid data segment index: %u", imm.index); + return false; + } + return true; + } + + inline bool Validate(const byte* pc, TableIndexImmediate<validate>& imm) { + if (!VALIDATE(module_ != nullptr && imm.index < module_->tables.size())) { + errorf(pc_ + 1, "invalid table index: %u", imm.index); + return false; + } + return true; + } + + inline bool Validate(TableInitImmediate<validate>& imm) { + if (!Validate(pc_ + 1, imm.table)) return false; + if (!VALIDATE(module_ != nullptr && + imm.elem_segment_index < module_->elem_segments.size())) { + errorf(pc_ + 2, "invalid element segment index: %u", + imm.elem_segment_index); + return false; + } + return true; + } + + inline bool Validate(TableDropImmediate<validate>& imm) { + if (!VALIDATE(module_ != nullptr && + imm.index < module_->elem_segments.size())) { + errorf(pc_ + 2, "invalid element segment index: %u", imm.index); + return false; + } + return true; + } + + static uint32_t OpcodeLength(Decoder* decoder, const byte* pc) { WasmOpcode opcode = static_cast<WasmOpcode>(*pc); switch (opcode) { #define DECLARE_OPCODE_CASE(name, opcode, sig) case kExpr##name: @@ -1009,7 +1093,7 @@ class WasmDecoder : public Decoder { } case kExprBr: case kExprBrIf: { - BreakDepthImmediate<validate> imm(decoder, pc); + BranchDepthImmediate<validate> imm(decoder, pc); return 1 + imm.length; } case kExprSetGlobal: @@ -1035,12 +1119,18 @@ class WasmDecoder : public Decoder { return 1 + imm.length; } - case kExprThrow: - case kExprCatch: { + case kExprThrow: { ExceptionIndexImmediate<validate> imm(decoder, pc); return 1 + imm.length; } + case kExprBrOnExn: { + BranchDepthImmediate<validate> imm_br(decoder, pc); + if (!VALIDATE(decoder->ok())) return 1 + imm_br.length; + ExceptionIndexImmediate<validate> imm_idx(decoder, pc + imm_br.length); + return 1 + imm_br.length + imm_idx.length; + } + case kExprSetLocal: case kExprTeeLocal: case kExprGetLocal: { @@ -1063,7 +1153,7 @@ class WasmDecoder : public Decoder { case kExprRefNull: { return 1; } - case kExprGrowMemory: + case kExprMemoryGrow: case kExprMemorySize: { MemoryIndexImmediate<validate> imm(decoder, pc); return 1 + imm.length; @@ -1072,10 +1162,55 @@ class WasmDecoder : public Decoder { return 5; case kExprF64Const: return 9; - case kNumericPrefix: - return 2; + case kNumericPrefix: { + byte numeric_index = + decoder->read_u8<validate>(pc + 1, "numeric_index"); + if (!VALIDATE(decoder->ok())) return 2; + WasmOpcode opcode = + static_cast<WasmOpcode>(kNumericPrefix << 8 | numeric_index); + switch (opcode) { + case kExprI32SConvertSatF32: + case kExprI32UConvertSatF32: + case kExprI32SConvertSatF64: + case kExprI32UConvertSatF64: + case kExprI64SConvertSatF32: + case kExprI64UConvertSatF32: + case kExprI64SConvertSatF64: + case kExprI64UConvertSatF64: + return 2; + case kExprMemoryInit: { + MemoryInitImmediate<validate> imm(decoder, pc); + return 2 + imm.length; + } + case kExprMemoryDrop: { + MemoryDropImmediate<validate> imm(decoder, pc); + return 2 + imm.length; + } + case kExprMemoryCopy: + case kExprMemoryFill: { + MemoryIndexImmediate<validate> imm(decoder, pc + 1); + return 2 + imm.length; + } + case kExprTableInit: { + TableInitImmediate<validate> imm(decoder, pc); + return 2 + imm.length; + } + case kExprTableDrop: { + TableDropImmediate<validate> imm(decoder, pc); + return 2 + imm.length; + } + case kExprTableCopy: { + TableIndexImmediate<validate> imm(decoder, pc + 1); + return 2 + imm.length; + } + default: + decoder->error(pc, "invalid numeric opcode"); + return 2; + } + } case kSimdPrefix: { byte simd_index = decoder->read_u8<validate>(pc + 1, "simd_index"); + if (!VALIDATE(decoder->ok())) return 2; WasmOpcode opcode = static_cast<WasmOpcode>(kSimdPrefix << 8 | simd_index); switch (opcode) { @@ -1104,6 +1239,7 @@ class WasmDecoder : public Decoder { } case kAtomicPrefix: { byte atomic_index = decoder->read_u8<validate>(pc + 1, "atomic_index"); + if (!VALIDATE(decoder->ok())) return 2; WasmOpcode opcode = static_cast<WasmOpcode>(kAtomicPrefix << 8 | atomic_index); switch (opcode) { @@ -1140,7 +1276,7 @@ class WasmDecoder : public Decoder { return {2, 0}; FOREACH_LOAD_MEM_OPCODE(DECLARE_OPCODE_CASE) case kExprTeeLocal: - case kExprGrowMemory: + case kExprMemoryGrow: return {1, 1}; case kExprSetLocal: case kExprSetGlobal: @@ -1148,7 +1284,9 @@ class WasmDecoder : public Decoder { case kExprBrIf: case kExprBrTable: case kExprIf: + case kExprRethrow: return {1, 0}; + case kExprCatch: case kExprGetLocal: case kExprGetGlobal: case kExprI32Const: @@ -1170,11 +1308,19 @@ class WasmDecoder : public Decoder { return {imm.sig->parameter_count() + 1, imm.sig->return_count()}; } + case kExprThrow: { + ExceptionIndexImmediate<validate> imm(this, pc); + CHECK(Complete(pc, imm)); + DCHECK_EQ(0, imm.exception->sig->return_count()); + return {imm.exception->sig->parameter_count(), 0}; + } case kExprBr: case kExprBlock: case kExprLoop: case kExprEnd: case kExprElse: + case kExprTry: + case kExprBrOnExn: case kExprNop: case kExprReturn: case kExprUnreachable: @@ -1247,8 +1393,7 @@ class WasmFullDecoder : public WasmDecoder<validate> { local_type_vec_(zone), stack_(zone), control_(zone), - args_(zone), - last_end_found_(false) { + args_(zone) { this->local_types_ = &local_type_vec_; } @@ -1275,24 +1420,16 @@ class WasmFullDecoder : public WasmDecoder<validate> { DecodeFunctionBody(); if (!this->failed()) CALL_INTERFACE(FinishFunction); - if (this->failed()) return this->TraceFailed(); - - if (!control_.empty()) { - // Generate a better error message whether the unterminated control - // structure is the function body block or an innner structure. - if (control_.size() > 1) { - this->error(control_.back().pc, "unterminated control structure"); - } else { - this->error("function body must end with \"end\" opcode"); - } - return TraceFailed(); - } - - if (!last_end_found_) { + // Generate a better error message whether the unterminated control + // structure is the function body block or an innner structure. + if (control_.size() > 1) { + this->error(control_.back().pc, "unterminated control structure"); + } else if (control_.size() == 1) { this->error("function body must end with \"end\" opcode"); - return false; } + if (this->failed()) return this->TraceFailed(); + if (FLAG_trace_wasm_decode_time) { double ms = decode_timer.Elapsed().InMillisecondsF(); PrintF("wasm-decode %s (%0.3f ms)\n\n", @@ -1305,9 +1442,9 @@ class WasmFullDecoder : public WasmDecoder<validate> { } bool TraceFailed() { - TRACE("wasm-error module+%-6d func+%d: %s\n\n", this->error_offset_, - this->GetBufferRelativeOffset(this->error_offset_), - this->error_msg_.c_str()); + TRACE("wasm-error module+%-6d func+%d: %s\n\n", this->error_.offset(), + this->GetBufferRelativeOffset(this->error_.offset()), + this->error_.message().c_str()); return false; } @@ -1318,7 +1455,7 @@ class WasmFullDecoder : public WasmDecoder<validate> { inline Zone* zone() const { return zone_; } - inline uint32_t NumLocals() { + inline uint32_t num_locals() const { return static_cast<uint32_t>(local_type_vec_.size()); } @@ -1347,21 +1484,12 @@ class WasmFullDecoder : public WasmDecoder<validate> { } inline Value* stack_value(uint32_t depth) { - DCHECK_GT(stack_.size(), depth); - return &stack_[stack_.size() - depth - 1]; - } - - inline Value& GetMergeValueFromStack( - Control* c, Merge<Value>* merge, uint32_t i) { - DCHECK(merge == &c->start_merge || merge == &c->end_merge); - DCHECK_GT(merge->arity, i); - DCHECK_GE(stack_.size(), c->stack_depth + merge->arity); - return stack_[stack_.size() - merge->arity + i]; + DCHECK_LT(0, depth); + DCHECK_GE(stack_.size(), depth); + return &*(stack_.end() - depth); } private: - static constexpr size_t kErrorMsgSize = 128; - Zone* zone_; Interface interface_; @@ -1370,7 +1498,10 @@ class WasmFullDecoder : public WasmDecoder<validate> { ZoneVector<Value> stack_; // stack of values. ZoneVector<Control> control_; // stack of blocks, loops, and ifs. ZoneVector<Value> args_; // parameters of current block or call - bool last_end_found_; + + static Value UnreachableValue(const uint8_t* pc) { + return Value{pc, kWasmVar}; + } bool CheckHasMemory() { if (!VALIDATE(this->module_->has_memory)) { @@ -1423,17 +1554,18 @@ class WasmFullDecoder : public WasmDecoder<validate> { // Set up initial function block. { - auto* c = PushBlock(); + auto* c = PushControl(kControlBlock); InitMerge(&c->start_merge, 0, [](uint32_t) -> Value { UNREACHABLE(); }); InitMerge(&c->end_merge, static_cast<uint32_t>(this->sig_->return_count()), - [&] (uint32_t i) { - return Value::New(this->pc_, this->sig_->GetReturn(i)); }); + [&](uint32_t i) { + return Value{this->pc_, this->sig_->GetReturn(i)}; + }); CALL_INTERFACE(StartFunctionBody, c); } while (this->pc_ < this->end_) { // decoding loop. - unsigned len = 1; + uint32_t len = 1; WasmOpcode opcode = static_cast<WasmOpcode>(*this->pc_); CALL_INTERFACE_IF_REACHABLE(NextInstruction, opcode); @@ -1449,544 +1581,543 @@ class WasmFullDecoder : public WasmDecoder<validate> { #define TRACE_PART(...) #endif - FunctionSig* sig = const_cast<FunctionSig*>(kSimpleOpcodeSigs[opcode]); - if (sig) { - BuildSimpleOperator(opcode, sig); - } else { - // Complex bytecode. - switch (opcode) { - case kExprNop: - break; - case kExprBlock: { - BlockTypeImmediate<validate> imm(this->enabled_, this, this->pc_); - if (!this->Validate(imm)) break; - PopArgs(imm.sig); - auto* block = PushBlock(); - SetBlockType(block, imm); - CALL_INTERFACE_IF_REACHABLE(Block, block); - PushMergeValues(block, &block->start_merge); - len = 1 + imm.length; + switch (opcode) { +#define BUILD_SIMPLE_OPCODE(op, _, sig) \ + case kExpr##op: \ + BuildSimpleOperator_##sig(opcode); \ + break; + FOREACH_SIMPLE_OPCODE(BUILD_SIMPLE_OPCODE) +#undef BUILD_SIMPLE_OPCODE + case kExprNop: + break; + case kExprBlock: { + BlockTypeImmediate<validate> imm(this->enabled_, this, this->pc_); + if (!this->Validate(imm)) break; + PopArgs(imm.sig); + auto* block = PushControl(kControlBlock); + SetBlockType(block, imm); + CALL_INTERFACE_IF_REACHABLE(Block, block); + PushMergeValues(block, &block->start_merge); + len = 1 + imm.length; + break; + } + case kExprRethrow: { + CHECK_PROTOTYPE_OPCODE(eh); + auto exception = Pop(0, kWasmExceptRef); + CALL_INTERFACE_IF_REACHABLE(Rethrow, exception); + EndControl(); + break; + } + case kExprThrow: { + CHECK_PROTOTYPE_OPCODE(eh); + ExceptionIndexImmediate<validate> imm(this, this->pc_); + len = 1 + imm.length; + if (!this->Validate(this->pc_, imm)) break; + PopArgs(imm.exception->ToFunctionSig()); + CALL_INTERFACE_IF_REACHABLE(Throw, imm, VectorOf(args_)); + EndControl(); + break; + } + case kExprTry: { + CHECK_PROTOTYPE_OPCODE(eh); + BlockTypeImmediate<validate> imm(this->enabled_, this, this->pc_); + if (!this->Validate(imm)) break; + PopArgs(imm.sig); + auto* try_block = PushControl(kControlTry); + SetBlockType(try_block, imm); + len = 1 + imm.length; + CALL_INTERFACE_IF_REACHABLE(Try, try_block); + PushMergeValues(try_block, &try_block->start_merge); + break; + } + case kExprCatch: { + CHECK_PROTOTYPE_OPCODE(eh); + if (!VALIDATE(!control_.empty())) { + this->error("catch does not match any try"); break; } - case kExprRethrow: { - // TODO(kschimpf): Implement. - CHECK_PROTOTYPE_OPCODE(eh); - OPCODE_ERROR(opcode, "not implemented yet"); + Control* c = &control_.back(); + if (!VALIDATE(c->is_try())) { + this->error("catch does not match any try"); break; } - case kExprThrow: { - CHECK_PROTOTYPE_OPCODE(eh); - ExceptionIndexImmediate<validate> imm(this, this->pc_); - len = 1 + imm.length; - if (!this->Validate(this->pc_, imm)) break; - PopArgs(imm.exception->ToFunctionSig()); - CALL_INTERFACE_IF_REACHABLE(Throw, imm, &control_.back(), - vec2vec(args_)); - EndControl(); + if (!VALIDATE(c->is_incomplete_try())) { + this->error("catch already present for try"); break; } - case kExprTry: { - CHECK_PROTOTYPE_OPCODE(eh); - BlockTypeImmediate<validate> imm(this->enabled_, this, this->pc_); - if (!this->Validate(imm)) break; - PopArgs(imm.sig); - auto* try_block = PushTry(); - SetBlockType(try_block, imm); - len = 1 + imm.length; - CALL_INTERFACE_IF_REACHABLE(Try, try_block); - PushMergeValues(try_block, &try_block->start_merge); - break; + c->kind = kControlTryCatch; + FallThruTo(c); + stack_.erase(stack_.begin() + c->stack_depth, stack_.end()); + c->reachability = control_at(1)->innerReachability(); + auto* exception = Push(kWasmExceptRef); + CALL_INTERFACE_IF_PARENT_REACHABLE(Catch, c, exception); + break; + } + case kExprBrOnExn: { + CHECK_PROTOTYPE_OPCODE(eh); + BranchDepthImmediate<validate> imm_br(this, this->pc_); + if (!this->Validate(this->pc_, imm_br, control_.size())) break; + ExceptionIndexImmediate<validate> imm_idx(this, + this->pc_ + imm_br.length); + if (!this->Validate(this->pc_ + imm_br.length, imm_idx)) break; + Control* c = control_at(imm_br.depth); + auto exception = Pop(0, kWasmExceptRef); + const WasmExceptionSig* sig = imm_idx.exception->sig; + size_t value_count = sig->parameter_count(); + // TODO(mstarzinger): This operand stack mutation is an ugly hack to + // make both type checking here as well as environment merging in the + // graph builder interface work out of the box. We should introduce + // special handling for both and do minimal/no stack mutation here. + for (size_t i = 0; i < value_count; ++i) Push(sig->GetParam(i)); + Vector<Value> values(stack_.data() + c->stack_depth, value_count); + if (!TypeCheckBranch(c)) break; + if (control_.back().reachable()) { + CALL_INTERFACE(BrOnException, exception, imm_idx, imm_br.depth, + values); + c->br_merge()->reached = true; } - case kExprCatch: { - // TODO(kschimpf): Fix to use type signature of exception. - CHECK_PROTOTYPE_OPCODE(eh); - ExceptionIndexImmediate<validate> imm(this, this->pc_); - len = 1 + imm.length; - - if (!this->Validate(this->pc_, imm)) break; - - if (!VALIDATE(!control_.empty())) { - this->error("catch does not match any try"); - break; - } - - Control* c = &control_.back(); - if (!VALIDATE(c->is_try())) { - this->error("catch does not match any try"); - break; - } - - if (!VALIDATE(c->is_incomplete_try())) { - OPCODE_ERROR(opcode, "multiple catch blocks not implemented"); - break; - } - c->kind = kControlTryCatch; - FallThruTo(c); - stack_.resize(c->stack_depth); - const WasmExceptionSig* sig = imm.exception->sig; - for (size_t i = 0, e = sig->parameter_count(); i < e; ++i) { - Push(sig->GetParam(i)); - } - Vector<Value> values(stack_.data() + c->stack_depth, - sig->parameter_count()); - CALL_INTERFACE_IF_PARENT_REACHABLE(CatchException, imm, c, values); - c->reachability = control_at(1)->innerReachability(); + len = 1 + imm_br.length + imm_idx.length; + for (size_t i = 0; i < value_count; ++i) Pop(); + auto* pexception = Push(kWasmExceptRef); + *pexception = exception; + break; + } + case kExprLoop: { + BlockTypeImmediate<validate> imm(this->enabled_, this, this->pc_); + if (!this->Validate(imm)) break; + PopArgs(imm.sig); + auto* block = PushControl(kControlLoop); + SetBlockType(&control_.back(), imm); + len = 1 + imm.length; + CALL_INTERFACE_IF_REACHABLE(Loop, block); + PushMergeValues(block, &block->start_merge); + break; + } + case kExprIf: { + BlockTypeImmediate<validate> imm(this->enabled_, this, this->pc_); + if (!this->Validate(imm)) break; + auto cond = Pop(0, kWasmI32); + PopArgs(imm.sig); + if (!VALIDATE(this->ok())) break; + auto* if_block = PushControl(kControlIf); + SetBlockType(if_block, imm); + CALL_INTERFACE_IF_REACHABLE(If, cond, if_block); + len = 1 + imm.length; + PushMergeValues(if_block, &if_block->start_merge); + break; + } + case kExprElse: { + if (!VALIDATE(!control_.empty())) { + this->error("else does not match any if"); break; } - case kExprCatchAll: { - CHECK_PROTOTYPE_OPCODE(eh); - if (!VALIDATE(!control_.empty())) { - this->error("catch-all does not match any try"); - break; - } - Control* c = &control_.back(); - if (!VALIDATE(c->is_try())) { - this->error("catch-all does not match any try"); - break; - } - if (!VALIDATE(!c->is_try_catchall())) { - this->error("catch-all already present for try"); - break; - } - c->kind = kControlTryCatchAll; - // TODO(mstarzinger): Implement control flow for catch-all. + Control* c = &control_.back(); + if (!VALIDATE(c->is_if())) { + this->error(this->pc_, "else does not match an if"); break; } - case kExprLoop: { - BlockTypeImmediate<validate> imm(this->enabled_, this, this->pc_); - if (!this->Validate(imm)) break; - PopArgs(imm.sig); - auto* block = PushLoop(); - SetBlockType(&control_.back(), imm); - len = 1 + imm.length; - CALL_INTERFACE_IF_REACHABLE(Loop, block); - PushMergeValues(block, &block->start_merge); + if (c->is_if_else()) { + this->error(this->pc_, "else already present for if"); break; } - case kExprIf: { - BlockTypeImmediate<validate> imm(this->enabled_, this, this->pc_); - if (!this->Validate(imm)) break; - auto cond = Pop(0, kWasmI32); - PopArgs(imm.sig); - if (!VALIDATE(this->ok())) break; - auto* if_block = PushIf(); - SetBlockType(if_block, imm); - CALL_INTERFACE_IF_REACHABLE(If, cond, if_block); - len = 1 + imm.length; - PushMergeValues(if_block, &if_block->start_merge); + if (!TypeCheckFallThru(c)) break; + c->kind = kControlIfElse; + CALL_INTERFACE_IF_PARENT_REACHABLE(Else, c); + if (c->reachable()) c->end_merge.reached = true; + PushMergeValues(c, &c->start_merge); + c->reachability = control_at(1)->innerReachability(); + break; + } + case kExprEnd: { + if (!VALIDATE(!control_.empty())) { + this->error("end does not match any if, try, or block"); break; } - case kExprElse: { - if (!VALIDATE(!control_.empty())) { - this->error("else does not match any if"); - break; - } - Control* c = &control_.back(); - if (!VALIDATE(c->is_if())) { - this->error(this->pc_, "else does not match an if"); - break; - } - if (c->is_if_else()) { - this->error(this->pc_, "else already present for if"); - break; - } - FallThruTo(c); - c->kind = kControlIfElse; - CALL_INTERFACE_IF_PARENT_REACHABLE(Else, c); - PushMergeValues(c, &c->start_merge); - c->reachability = control_at(1)->innerReachability(); + Control* c = &control_.back(); + if (!VALIDATE(!c->is_incomplete_try())) { + this->error(this->pc_, "missing catch or catch-all in try"); break; } - case kExprEnd: { - if (!VALIDATE(!control_.empty())) { - this->error("end does not match any if, try, or block"); - return; - } - Control* c = &control_.back(); - if (!VALIDATE(!c->is_incomplete_try())) { - this->error(this->pc_, "missing catch or catch-all in try"); + if (c->is_onearmed_if()) { + if (!VALIDATE(c->end_merge.arity == c->start_merge.arity)) { + this->error( + c->pc, + "start-arity and end-arity of one-armed if must match"); break; } - if (c->is_onearmed_if()) { - // Emulate empty else arm. - FallThruTo(c); - if (this->failed()) break; - CALL_INTERFACE_IF_PARENT_REACHABLE(Else, c); - PushMergeValues(c, &c->start_merge); - c->reachability = control_at(1)->innerReachability(); - } - - FallThruTo(c); - // A loop just leaves the values on the stack. - if (!c->is_loop()) PushMergeValues(c, &c->end_merge); - - if (control_.size() == 1) { - // If at the last (implicit) control, check we are at end. - if (!VALIDATE(this->pc_ + 1 == this->end_)) { - this->error(this->pc_ + 1, "trailing code after function end"); - break; - } - last_end_found_ = true; - // The result of the block is the return value. - TRACE_PART("\n" TRACE_INST_FORMAT, startrel(this->pc_), - "(implicit) return"); - DoReturn(c, true); - } - - PopControl(c); - break; - } - case kExprSelect: { - auto cond = Pop(2, kWasmI32); - auto fval = Pop(); - auto tval = Pop(0, fval.type); - auto* result = Push(tval.type == kWasmVar ? fval.type : tval.type); - CALL_INTERFACE_IF_REACHABLE(Select, cond, fval, tval, result); - break; - } - case kExprBr: { - BreakDepthImmediate<validate> imm(this, this->pc_); - if (!this->Validate(this->pc_, imm, control_.size())) break; - Control* c = control_at(imm.depth); - if (!TypeCheckBreak(c)) break; - if (control_.back().reachable()) { - CALL_INTERFACE(Br, c); - c->br_merge()->reached = true; - } - len = 1 + imm.length; - EndControl(); - break; } - case kExprBrIf: { - BreakDepthImmediate<validate> imm(this, this->pc_); - auto cond = Pop(0, kWasmI32); - if (this->failed()) break; - if (!this->Validate(this->pc_, imm, control_.size())) break; - Control* c = control_at(imm.depth); - if (!TypeCheckBreak(c)) break; - if (control_.back().reachable()) { - CALL_INTERFACE(BrIf, cond, c); - c->br_merge()->reached = true; - } - len = 1 + imm.length; - break; - } - case kExprBrTable: { - BranchTableImmediate<validate> imm(this, this->pc_); - BranchTableIterator<validate> iterator(this, imm); - auto key = Pop(0, kWasmI32); - if (this->failed()) break; - if (!this->Validate(this->pc_, imm, control_.size())) break; - uint32_t br_arity = 0; - std::vector<bool> br_targets(control_.size()); - while (iterator.has_next()) { - const uint32_t i = iterator.cur_index(); - const byte* pos = iterator.pc(); - uint32_t target = iterator.next(); - if (!VALIDATE(target < control_.size())) { - this->errorf(pos, - "improper branch in br_table target %u (depth %u)", - i, target); - break; - } - // Avoid redundant break target checks. - if (br_targets[target]) continue; - br_targets[target] = true; - // Check that label types match up. - Control* c = control_at(target); - uint32_t arity = c->br_merge()->arity; - if (i == 0) { - br_arity = arity; - } else if (!VALIDATE(br_arity == arity)) { - this->errorf(pos, - "inconsistent arity in br_table target %u" - " (previous was %u, this one %u)", - i, br_arity, arity); - } - if (!TypeCheckBreak(c)) break; - } - if (this->failed()) break; - if (control_.back().reachable()) { - CALL_INTERFACE(BrTable, imm, key); + if (!TypeCheckFallThru(c)) break; - for (uint32_t depth = control_depth(); depth-- > 0;) { - if (!br_targets[depth]) continue; - control_at(depth)->br_merge()->reached = true; - } + if (control_.size() == 1) { + // If at the last (implicit) control, check we are at end. + if (!VALIDATE(this->pc_ + 1 == this->end_)) { + this->error(this->pc_ + 1, "trailing code after function end"); + break; } - - len = 1 + iterator.length(); - EndControl(); - break; - } - case kExprReturn: { - DoReturn(&control_.back(), false); - break; - } - case kExprUnreachable: { - CALL_INTERFACE_IF_REACHABLE(Unreachable); - EndControl(); - break; - } - case kExprI32Const: { - ImmI32Immediate<validate> imm(this, this->pc_); - auto* value = Push(kWasmI32); - CALL_INTERFACE_IF_REACHABLE(I32Const, value, imm.value); - len = 1 + imm.length; - break; - } - case kExprI64Const: { - ImmI64Immediate<validate> imm(this, this->pc_); - auto* value = Push(kWasmI64); - CALL_INTERFACE_IF_REACHABLE(I64Const, value, imm.value); - len = 1 + imm.length; - break; - } - case kExprF32Const: { - ImmF32Immediate<validate> imm(this, this->pc_); - auto* value = Push(kWasmF32); - CALL_INTERFACE_IF_REACHABLE(F32Const, value, imm.value); - len = 1 + imm.length; - break; - } - case kExprF64Const: { - ImmF64Immediate<validate> imm(this, this->pc_); - auto* value = Push(kWasmF64); - CALL_INTERFACE_IF_REACHABLE(F64Const, value, imm.value); - len = 1 + imm.length; - break; - } - case kExprRefNull: { - CHECK_PROTOTYPE_OPCODE(anyref); - auto* value = Push(kWasmAnyRef); - CALL_INTERFACE_IF_REACHABLE(RefNull, value); - len = 1; - break; - } - case kExprGetLocal: { - LocalIndexImmediate<validate> imm(this, this->pc_); - if (!this->Validate(this->pc_, imm)) break; - auto* value = Push(imm.type); - CALL_INTERFACE_IF_REACHABLE(GetLocal, value, imm); - len = 1 + imm.length; - break; - } - case kExprSetLocal: { - LocalIndexImmediate<validate> imm(this, this->pc_); - if (!this->Validate(this->pc_, imm)) break; - auto value = Pop(0, local_type_vec_[imm.index]); - CALL_INTERFACE_IF_REACHABLE(SetLocal, value, imm); - len = 1 + imm.length; + // The result of the block is the return value. + TRACE_PART("\n" TRACE_INST_FORMAT, startrel(this->pc_), + "(implicit) return"); + DoReturn(); + control_.clear(); break; } - case kExprTeeLocal: { - LocalIndexImmediate<validate> imm(this, this->pc_); - if (!this->Validate(this->pc_, imm)) break; - auto value = Pop(0, local_type_vec_[imm.index]); - auto* result = Push(value.type); - CALL_INTERFACE_IF_REACHABLE(TeeLocal, value, result, imm); - len = 1 + imm.length; - break; - } - case kExprDrop: { - auto value = Pop(); - CALL_INTERFACE_IF_REACHABLE(Drop, value); - break; + + PopControl(c); + break; + } + case kExprSelect: { + auto cond = Pop(2, kWasmI32); + auto fval = Pop(); + auto tval = Pop(0, fval.type); + auto* result = Push(tval.type == kWasmVar ? fval.type : tval.type); + CALL_INTERFACE_IF_REACHABLE(Select, cond, fval, tval, result); + break; + } + case kExprBr: { + BranchDepthImmediate<validate> imm(this, this->pc_); + if (!this->Validate(this->pc_, imm, control_.size())) break; + Control* c = control_at(imm.depth); + if (!TypeCheckBranch(c)) break; + if (imm.depth == control_.size() - 1) { + DoReturn(); + } else if (control_.back().reachable()) { + CALL_INTERFACE(Br, c); + c->br_merge()->reached = true; } - case kExprGetGlobal: { - GlobalIndexImmediate<validate> imm(this, this->pc_); - len = 1 + imm.length; - if (!this->Validate(this->pc_, imm)) break; - auto* result = Push(imm.type); - CALL_INTERFACE_IF_REACHABLE(GetGlobal, result, imm); - break; + len = 1 + imm.length; + EndControl(); + break; + } + case kExprBrIf: { + BranchDepthImmediate<validate> imm(this, this->pc_); + auto cond = Pop(0, kWasmI32); + if (this->failed()) break; + if (!this->Validate(this->pc_, imm, control_.size())) break; + Control* c = control_at(imm.depth); + if (!TypeCheckBranch(c)) break; + if (control_.back().reachable()) { + CALL_INTERFACE(BrIf, cond, imm.depth); + c->br_merge()->reached = true; } - case kExprSetGlobal: { - GlobalIndexImmediate<validate> imm(this, this->pc_); - len = 1 + imm.length; - if (!this->Validate(this->pc_, imm)) break; - if (!VALIDATE(imm.global->mutability)) { - this->errorf(this->pc_, "immutable global #%u cannot be assigned", - imm.index); + len = 1 + imm.length; + break; + } + case kExprBrTable: { + BranchTableImmediate<validate> imm(this, this->pc_); + BranchTableIterator<validate> iterator(this, imm); + auto key = Pop(0, kWasmI32); + if (this->failed()) break; + if (!this->Validate(this->pc_, imm, control_.size())) break; + uint32_t br_arity = 0; + std::vector<bool> br_targets(control_.size()); + while (iterator.has_next()) { + const uint32_t i = iterator.cur_index(); + const byte* pos = iterator.pc(); + uint32_t target = iterator.next(); + if (!VALIDATE(target < control_.size())) { + this->errorf(pos, + "improper branch in br_table target %u (depth %u)", + i, target); break; } - auto value = Pop(0, imm.type); - CALL_INTERFACE_IF_REACHABLE(SetGlobal, value, imm); - break; - } - case kExprI32LoadMem8S: - len = 1 + DecodeLoadMem(LoadType::kI32Load8S); - break; - case kExprI32LoadMem8U: - len = 1 + DecodeLoadMem(LoadType::kI32Load8U); - break; - case kExprI32LoadMem16S: - len = 1 + DecodeLoadMem(LoadType::kI32Load16S); - break; - case kExprI32LoadMem16U: - len = 1 + DecodeLoadMem(LoadType::kI32Load16U); - break; - case kExprI32LoadMem: - len = 1 + DecodeLoadMem(LoadType::kI32Load); - break; - case kExprI64LoadMem8S: - len = 1 + DecodeLoadMem(LoadType::kI64Load8S); - break; - case kExprI64LoadMem8U: - len = 1 + DecodeLoadMem(LoadType::kI64Load8U); - break; - case kExprI64LoadMem16S: - len = 1 + DecodeLoadMem(LoadType::kI64Load16S); - break; - case kExprI64LoadMem16U: - len = 1 + DecodeLoadMem(LoadType::kI64Load16U); - break; - case kExprI64LoadMem32S: - len = 1 + DecodeLoadMem(LoadType::kI64Load32S); - break; - case kExprI64LoadMem32U: - len = 1 + DecodeLoadMem(LoadType::kI64Load32U); - break; - case kExprI64LoadMem: - len = 1 + DecodeLoadMem(LoadType::kI64Load); - break; - case kExprF32LoadMem: - len = 1 + DecodeLoadMem(LoadType::kF32Load); - break; - case kExprF64LoadMem: - len = 1 + DecodeLoadMem(LoadType::kF64Load); - break; - case kExprI32StoreMem8: - len = 1 + DecodeStoreMem(StoreType::kI32Store8); - break; - case kExprI32StoreMem16: - len = 1 + DecodeStoreMem(StoreType::kI32Store16); - break; - case kExprI32StoreMem: - len = 1 + DecodeStoreMem(StoreType::kI32Store); - break; - case kExprI64StoreMem8: - len = 1 + DecodeStoreMem(StoreType::kI64Store8); - break; - case kExprI64StoreMem16: - len = 1 + DecodeStoreMem(StoreType::kI64Store16); - break; - case kExprI64StoreMem32: - len = 1 + DecodeStoreMem(StoreType::kI64Store32); - break; - case kExprI64StoreMem: - len = 1 + DecodeStoreMem(StoreType::kI64Store); - break; - case kExprF32StoreMem: - len = 1 + DecodeStoreMem(StoreType::kF32Store); - break; - case kExprF64StoreMem: - len = 1 + DecodeStoreMem(StoreType::kF64Store); - break; - case kExprGrowMemory: { - if (!CheckHasMemory()) break; - MemoryIndexImmediate<validate> imm(this, this->pc_); - len = 1 + imm.length; - DCHECK_NOT_NULL(this->module_); - if (!VALIDATE(this->module_->origin == kWasmOrigin)) { - this->error("grow_memory is not supported for asmjs modules"); - break; + // Avoid redundant branch target checks. + if (br_targets[target]) continue; + br_targets[target] = true; + // Check that label types match up. + Control* c = control_at(target); + uint32_t arity = c->br_merge()->arity; + if (i == 0) { + br_arity = arity; + } else if (!VALIDATE(br_arity == arity)) { + this->errorf(pos, + "inconsistent arity in br_table target %u" + " (previous was %u, this one %u)", + i, br_arity, arity); } - auto value = Pop(0, kWasmI32); - auto* result = Push(kWasmI32); - CALL_INTERFACE_IF_REACHABLE(GrowMemory, value, result); - break; + if (!TypeCheckBranch(c)) break; } - case kExprMemorySize: { - if (!CheckHasMemory()) break; - MemoryIndexImmediate<validate> imm(this, this->pc_); - auto* result = Push(kWasmI32); - len = 1 + imm.length; - CALL_INTERFACE_IF_REACHABLE(CurrentMemoryPages, result); - break; + if (this->failed()) break; + + if (control_.back().reachable()) { + CALL_INTERFACE(BrTable, imm, key); + + for (uint32_t depth = control_depth(); depth-- > 0;) { + if (!br_targets[depth]) continue; + control_at(depth)->br_merge()->reached = true; + } } - case kExprCallFunction: { - CallFunctionImmediate<validate> imm(this, this->pc_); - len = 1 + imm.length; - if (!this->Validate(this->pc_, imm)) break; - // TODO(clemensh): Better memory management. - PopArgs(imm.sig); - auto* returns = PushReturns(imm.sig); - CALL_INTERFACE_IF_REACHABLE(CallDirect, imm, args_.data(), returns); + + len = 1 + iterator.length(); + EndControl(); + break; + } + case kExprReturn: { + if (!TypeCheckReturn()) break; + DoReturn(); + EndControl(); + break; + } + case kExprUnreachable: { + CALL_INTERFACE_IF_REACHABLE(Unreachable); + EndControl(); + break; + } + case kExprI32Const: { + ImmI32Immediate<validate> imm(this, this->pc_); + auto* value = Push(kWasmI32); + CALL_INTERFACE_IF_REACHABLE(I32Const, value, imm.value); + len = 1 + imm.length; + break; + } + case kExprI64Const: { + ImmI64Immediate<validate> imm(this, this->pc_); + auto* value = Push(kWasmI64); + CALL_INTERFACE_IF_REACHABLE(I64Const, value, imm.value); + len = 1 + imm.length; + break; + } + case kExprF32Const: { + ImmF32Immediate<validate> imm(this, this->pc_); + auto* value = Push(kWasmF32); + CALL_INTERFACE_IF_REACHABLE(F32Const, value, imm.value); + len = 1 + imm.length; + break; + } + case kExprF64Const: { + ImmF64Immediate<validate> imm(this, this->pc_); + auto* value = Push(kWasmF64); + CALL_INTERFACE_IF_REACHABLE(F64Const, value, imm.value); + len = 1 + imm.length; + break; + } + case kExprRefNull: { + CHECK_PROTOTYPE_OPCODE(anyref); + auto* value = Push(kWasmAnyRef); + CALL_INTERFACE_IF_REACHABLE(RefNull, value); + len = 1; + break; + } + case kExprGetLocal: { + LocalIndexImmediate<validate> imm(this, this->pc_); + if (!this->Validate(this->pc_, imm)) break; + auto* value = Push(imm.type); + CALL_INTERFACE_IF_REACHABLE(GetLocal, value, imm); + len = 1 + imm.length; + break; + } + case kExprSetLocal: { + LocalIndexImmediate<validate> imm(this, this->pc_); + if (!this->Validate(this->pc_, imm)) break; + auto value = Pop(0, local_type_vec_[imm.index]); + CALL_INTERFACE_IF_REACHABLE(SetLocal, value, imm); + len = 1 + imm.length; + break; + } + case kExprTeeLocal: { + LocalIndexImmediate<validate> imm(this, this->pc_); + if (!this->Validate(this->pc_, imm)) break; + auto value = Pop(0, local_type_vec_[imm.index]); + auto* result = Push(value.type); + CALL_INTERFACE_IF_REACHABLE(TeeLocal, value, result, imm); + len = 1 + imm.length; + break; + } + case kExprDrop: { + auto value = Pop(); + CALL_INTERFACE_IF_REACHABLE(Drop, value); + break; + } + case kExprGetGlobal: { + GlobalIndexImmediate<validate> imm(this, this->pc_); + len = 1 + imm.length; + if (!this->Validate(this->pc_, imm)) break; + auto* result = Push(imm.type); + CALL_INTERFACE_IF_REACHABLE(GetGlobal, result, imm); + break; + } + case kExprSetGlobal: { + GlobalIndexImmediate<validate> imm(this, this->pc_); + len = 1 + imm.length; + if (!this->Validate(this->pc_, imm)) break; + if (!VALIDATE(imm.global->mutability)) { + this->errorf(this->pc_, "immutable global #%u cannot be assigned", + imm.index); break; } - case kExprCallIndirect: { - CallIndirectImmediate<validate> imm(this, this->pc_); - len = 1 + imm.length; - if (!this->Validate(this->pc_, imm)) break; - auto index = Pop(0, kWasmI32); - PopArgs(imm.sig); - auto* returns = PushReturns(imm.sig); - CALL_INTERFACE_IF_REACHABLE(CallIndirect, index, imm, args_.data(), - returns); + auto value = Pop(0, imm.type); + CALL_INTERFACE_IF_REACHABLE(SetGlobal, value, imm); + break; + } + case kExprI32LoadMem8S: + len = 1 + DecodeLoadMem(LoadType::kI32Load8S); + break; + case kExprI32LoadMem8U: + len = 1 + DecodeLoadMem(LoadType::kI32Load8U); + break; + case kExprI32LoadMem16S: + len = 1 + DecodeLoadMem(LoadType::kI32Load16S); + break; + case kExprI32LoadMem16U: + len = 1 + DecodeLoadMem(LoadType::kI32Load16U); + break; + case kExprI32LoadMem: + len = 1 + DecodeLoadMem(LoadType::kI32Load); + break; + case kExprI64LoadMem8S: + len = 1 + DecodeLoadMem(LoadType::kI64Load8S); + break; + case kExprI64LoadMem8U: + len = 1 + DecodeLoadMem(LoadType::kI64Load8U); + break; + case kExprI64LoadMem16S: + len = 1 + DecodeLoadMem(LoadType::kI64Load16S); + break; + case kExprI64LoadMem16U: + len = 1 + DecodeLoadMem(LoadType::kI64Load16U); + break; + case kExprI64LoadMem32S: + len = 1 + DecodeLoadMem(LoadType::kI64Load32S); + break; + case kExprI64LoadMem32U: + len = 1 + DecodeLoadMem(LoadType::kI64Load32U); + break; + case kExprI64LoadMem: + len = 1 + DecodeLoadMem(LoadType::kI64Load); + break; + case kExprF32LoadMem: + len = 1 + DecodeLoadMem(LoadType::kF32Load); + break; + case kExprF64LoadMem: + len = 1 + DecodeLoadMem(LoadType::kF64Load); + break; + case kExprI32StoreMem8: + len = 1 + DecodeStoreMem(StoreType::kI32Store8); + break; + case kExprI32StoreMem16: + len = 1 + DecodeStoreMem(StoreType::kI32Store16); + break; + case kExprI32StoreMem: + len = 1 + DecodeStoreMem(StoreType::kI32Store); + break; + case kExprI64StoreMem8: + len = 1 + DecodeStoreMem(StoreType::kI64Store8); + break; + case kExprI64StoreMem16: + len = 1 + DecodeStoreMem(StoreType::kI64Store16); + break; + case kExprI64StoreMem32: + len = 1 + DecodeStoreMem(StoreType::kI64Store32); + break; + case kExprI64StoreMem: + len = 1 + DecodeStoreMem(StoreType::kI64Store); + break; + case kExprF32StoreMem: + len = 1 + DecodeStoreMem(StoreType::kF32Store); + break; + case kExprF64StoreMem: + len = 1 + DecodeStoreMem(StoreType::kF64Store); + break; + case kExprMemoryGrow: { + if (!CheckHasMemory()) break; + MemoryIndexImmediate<validate> imm(this, this->pc_); + len = 1 + imm.length; + DCHECK_NOT_NULL(this->module_); + if (!VALIDATE(this->module_->origin == kWasmOrigin)) { + this->error("grow_memory is not supported for asmjs modules"); break; } - case kNumericPrefix: { + auto value = Pop(0, kWasmI32); + auto* result = Push(kWasmI32); + CALL_INTERFACE_IF_REACHABLE(MemoryGrow, value, result); + break; + } + case kExprMemorySize: { + if (!CheckHasMemory()) break; + MemoryIndexImmediate<validate> imm(this, this->pc_); + auto* result = Push(kWasmI32); + len = 1 + imm.length; + CALL_INTERFACE_IF_REACHABLE(CurrentMemoryPages, result); + break; + } + case kExprCallFunction: { + CallFunctionImmediate<validate> imm(this, this->pc_); + len = 1 + imm.length; + if (!this->Validate(this->pc_, imm)) break; + // TODO(clemensh): Better memory management. + PopArgs(imm.sig); + auto* returns = PushReturns(imm.sig); + CALL_INTERFACE_IF_REACHABLE(CallDirect, imm, args_.data(), returns); + break; + } + case kExprCallIndirect: { + CallIndirectImmediate<validate> imm(this, this->pc_); + len = 1 + imm.length; + if (!this->Validate(this->pc_, imm)) break; + auto index = Pop(0, kWasmI32); + PopArgs(imm.sig); + auto* returns = PushReturns(imm.sig); + CALL_INTERFACE_IF_REACHABLE(CallIndirect, index, imm, args_.data(), + returns); + break; + } + case kNumericPrefix: { + ++len; + byte numeric_index = + this->template read_u8<validate>(this->pc_ + 1, "numeric index"); + opcode = static_cast<WasmOpcode>(opcode << 8 | numeric_index); + if (opcode < kExprMemoryInit) { CHECK_PROTOTYPE_OPCODE(sat_f2i_conversions); - ++len; - byte numeric_index = this->template read_u8<validate>( - this->pc_ + 1, "numeric index"); - opcode = static_cast<WasmOpcode>(opcode << 8 | numeric_index); - TRACE_PART(TRACE_INST_FORMAT, startrel(this->pc_), - WasmOpcodes::OpcodeName(opcode)); - sig = WasmOpcodes::Signature(opcode); - if (sig == nullptr) { - this->errorf(this->pc_, "Unrecognized numeric opcode: %x\n", - opcode); - return; - } - BuildSimpleOperator(opcode, sig); - break; - } - case kSimdPrefix: { - CHECK_PROTOTYPE_OPCODE(simd); - len++; - byte simd_index = - this->template read_u8<validate>(this->pc_ + 1, "simd index"); - opcode = static_cast<WasmOpcode>(opcode << 8 | simd_index); - TRACE_PART(TRACE_INST_FORMAT, startrel(this->pc_), - WasmOpcodes::OpcodeName(opcode)); - len += DecodeSimdOpcode(opcode); - break; - } - case kAtomicPrefix: { - CHECK_PROTOTYPE_OPCODE(threads); - if (!CheckHasSharedMemory()) break; - len++; - byte atomic_index = - this->template read_u8<validate>(this->pc_ + 1, "atomic index"); - opcode = static_cast<WasmOpcode>(opcode << 8 | atomic_index); - TRACE_PART(TRACE_INST_FORMAT, startrel(this->pc_), - WasmOpcodes::OpcodeName(opcode)); - len += DecodeAtomicOpcode(opcode); - break; + } else { + CHECK_PROTOTYPE_OPCODE(bulk_memory); } + TRACE_PART(TRACE_INST_FORMAT, startrel(this->pc_), + WasmOpcodes::OpcodeName(opcode)); + len += DecodeNumericOpcode(opcode); + break; + } + case kSimdPrefix: { + CHECK_PROTOTYPE_OPCODE(simd); + len++; + byte simd_index = + this->template read_u8<validate>(this->pc_ + 1, "simd index"); + opcode = static_cast<WasmOpcode>(opcode << 8 | simd_index); + TRACE_PART(TRACE_INST_FORMAT, startrel(this->pc_), + WasmOpcodes::OpcodeName(opcode)); + len += DecodeSimdOpcode(opcode); + break; + } + case kAtomicPrefix: { + CHECK_PROTOTYPE_OPCODE(threads); + if (!CheckHasSharedMemory()) break; + len++; + byte atomic_index = + this->template read_u8<validate>(this->pc_ + 1, "atomic index"); + opcode = static_cast<WasmOpcode>(opcode << 8 | atomic_index); + TRACE_PART(TRACE_INST_FORMAT, startrel(this->pc_), + WasmOpcodes::OpcodeName(opcode)); + len += DecodeAtomicOpcode(opcode); + break; + } // Note that prototype opcodes are not handled in the fastpath // above this switch, to avoid checking a feature flag. #define SIMPLE_PROTOTYPE_CASE(name, opc, sig) \ case kExpr##name: /* fallthrough */ - FOREACH_SIMPLE_PROTOTYPE_OPCODE(SIMPLE_PROTOTYPE_CASE) + FOREACH_SIMPLE_PROTOTYPE_OPCODE(SIMPLE_PROTOTYPE_CASE) #undef SIMPLE_PROTOTYPE_CASE - BuildSimplePrototypeOperator(opcode); - break; - default: { - // Deal with special asmjs opcodes. - if (this->module_ != nullptr && - this->module_->origin == kAsmJsOrigin) { - sig = WasmOpcodes::AsmjsSignature(opcode); - if (sig) { - BuildSimpleOperator(opcode, sig); - } - } else { - this->error("Invalid opcode"); - return; + BuildSimplePrototypeOperator(opcode); + break; + default: { + // Deal with special asmjs opcodes. + if (this->module_ != nullptr && + this->module_->origin == kAsmJsOrigin) { + FunctionSig* sig = WasmOpcodes::AsmjsSignature(opcode); + if (sig) { + BuildSimpleOperator(opcode, sig); } + } else { + this->error("Invalid opcode"); + return; } } } @@ -2063,7 +2194,7 @@ class WasmFullDecoder : public WasmDecoder<validate> { void EndControl() { DCHECK(!control_.empty()); auto* current = &control_.back(); - stack_.resize(current->stack_depth); + stack_.erase(stack_.begin() + current->stack_depth, stack_.end()); CALL_INTERFACE_IF_REACHABLE(EndControl, current); current->reachability = kUnreachable; } @@ -2075,7 +2206,7 @@ class WasmFullDecoder : public WasmDecoder<validate> { merge->vals.first = get_val(0); } else if (arity > 1) { merge->vals.array = zone_->NewArray<Value>(arity); - for (unsigned i = 0; i < arity; i++) { + for (uint32_t i = 0; i < arity; i++) { merge->vals.array[i] = get_val(i); } } @@ -2086,7 +2217,7 @@ class WasmFullDecoder : public WasmDecoder<validate> { const byte* pc = this->pc_; Value* args = this->args_.data(); InitMerge(&c->end_merge, imm.out_arity(), [pc, &imm](uint32_t i) { - return Value::New(pc, imm.out_type(i)); + return Value{pc, imm.out_type(i)}; }); InitMerge(&c->start_merge, imm.in_arity(), [args](uint32_t i) { return args[i]; }); @@ -2095,7 +2226,7 @@ class WasmFullDecoder : public WasmDecoder<validate> { // Pops arguments as required by signature into {args_}. V8_INLINE void PopArgs(FunctionSig* sig) { int count = sig ? static_cast<int>(sig->parameter_count()) : 0; - args_.resize(count); + args_.resize(count, UnreachableValue(nullptr)); for (int i = count - 1; i >= 0; --i) { args_[i] = Pop(i, sig->GetParam(i)); } @@ -2106,38 +2237,26 @@ class WasmFullDecoder : public WasmDecoder<validate> { return sig->return_count() == 0 ? kWasmStmt : sig->GetReturn(); } - Control* PushControl(Control&& new_control) { + Control* PushControl(ControlKind kind) { Reachability reachability = control_.empty() ? kReachable : control_.back().innerReachability(); - control_.emplace_back(std::move(new_control)); - Control* c = &control_.back(); - c->reachability = reachability; - c->start_merge.reached = c->reachable(); - return c; - } - - Control* PushBlock() { - return PushControl(Control::Block(this->pc_, stack_size())); - } - Control* PushLoop() { - return PushControl(Control::Loop(this->pc_, stack_size())); - } - Control* PushIf() { - return PushControl(Control::If(this->pc_, stack_size())); - } - Control* PushTry() { - // current_catch_ = static_cast<int32_t>(control_.size() - 1); - return PushControl(Control::Try(this->pc_, stack_size())); + control_.emplace_back(kind, stack_size(), this->pc_, reachability); + return &control_.back(); } void PopControl(Control* c) { DCHECK_EQ(c, &control_.back()); CALL_INTERFACE_IF_PARENT_REACHABLE(PopControl, c); - bool reached = c->end_merge.reached; + + // A loop just leaves the values on the stack. + if (!c->is_loop()) PushMergeValues(c, &c->end_merge); + + bool parent_reached = + c->reachable() || c->end_merge.reached || c->is_onearmed_if(); control_.pop_back(); // If the parent block was reachable before, but the popped control does not - // return to here, this block becomes indirectly unreachable. - if (!control_.empty() && !reached && control_.back().reachable()) { + // return to here, this block becomes "spec only reachable". + if (!parent_reached && control_.back().reachable()) { control_.back().reachability = kSpecOnlyReachable; } } @@ -2162,7 +2281,7 @@ class WasmFullDecoder : public WasmDecoder<validate> { return imm.length; } - unsigned SimdExtractLane(WasmOpcode opcode, ValueType type) { + uint32_t SimdExtractLane(WasmOpcode opcode, ValueType type) { SimdLaneImmediate<validate> imm(this, this->pc_); if (this->Validate(this->pc_, opcode, imm)) { Value inputs[] = {Pop(0, kWasmS128)}; @@ -2173,10 +2292,11 @@ class WasmFullDecoder : public WasmDecoder<validate> { return imm.length; } - unsigned SimdReplaceLane(WasmOpcode opcode, ValueType type) { + uint32_t SimdReplaceLane(WasmOpcode opcode, ValueType type) { SimdLaneImmediate<validate> imm(this, this->pc_); if (this->Validate(this->pc_, opcode, imm)) { - Value inputs[2]; + Value inputs[2] = {UnreachableValue(this->pc_), + UnreachableValue(this->pc_)}; inputs[1] = Pop(1, type); inputs[0] = Pop(0, kWasmS128); auto* result = Push(kWasmS128); @@ -2186,7 +2306,7 @@ class WasmFullDecoder : public WasmDecoder<validate> { return imm.length; } - unsigned SimdShiftOp(WasmOpcode opcode) { + uint32_t SimdShiftOp(WasmOpcode opcode) { SimdShiftImmediate<validate> imm(this, this->pc_); if (this->Validate(this->pc_, opcode, imm)) { auto input = Pop(0, kWasmS128); @@ -2196,7 +2316,7 @@ class WasmFullDecoder : public WasmDecoder<validate> { return imm.length; } - unsigned Simd8x16ShuffleOp() { + uint32_t Simd8x16ShuffleOp() { Simd8x16ShuffleImmediate<validate> imm(this, this->pc_); if (this->Validate(this->pc_, imm)) { auto input1 = Pop(1, kWasmS128); @@ -2208,8 +2328,8 @@ class WasmFullDecoder : public WasmDecoder<validate> { return 16; } - unsigned DecodeSimdOpcode(WasmOpcode opcode) { - unsigned len = 0; + uint32_t DecodeSimdOpcode(WasmOpcode opcode) { + uint32_t len = 0; switch (opcode) { case kExprF32x4ExtractLane: { len = SimdExtractLane(opcode, kWasmF32); @@ -2262,14 +2382,14 @@ class WasmFullDecoder : public WasmDecoder<validate> { PopArgs(sig); auto* results = sig->return_count() == 0 ? nullptr : Push(GetReturnType(sig)); - CALL_INTERFACE_IF_REACHABLE(SimdOp, opcode, vec2vec(args_), results); + CALL_INTERFACE_IF_REACHABLE(SimdOp, opcode, VectorOf(args_), results); } } return len; } - unsigned DecodeAtomicOpcode(WasmOpcode opcode) { - unsigned len = 0; + uint32_t DecodeAtomicOpcode(WasmOpcode opcode) { + uint32_t len = 0; ValueType ret_type; FunctionSig* sig = WasmOpcodes::Signature(opcode); if (sig != nullptr) { @@ -2300,7 +2420,7 @@ class WasmFullDecoder : public WasmDecoder<validate> { len += imm.length; PopArgs(sig); auto result = ret_type == kWasmStmt ? nullptr : Push(GetReturnType(sig)); - CALL_INTERFACE_IF_REACHABLE(AtomicOp, opcode, vec2vec(args_), imm, + CALL_INTERFACE_IF_REACHABLE(AtomicOp, opcode, VectorOf(args_), imm, result); } else { this->error("invalid atomic opcode"); @@ -2308,36 +2428,116 @@ class WasmFullDecoder : public WasmDecoder<validate> { return len; } - void DoReturn(Control* c, bool implicit) { - int return_count = static_cast<int>(this->sig_->return_count()); - args_.resize(return_count); - - // Pop return values off the stack in reverse order. - for (int i = return_count - 1; i >= 0; --i) { - args_[i] = Pop(i, this->sig_->GetReturn(i)); + unsigned DecodeNumericOpcode(WasmOpcode opcode) { + unsigned len = 0; + FunctionSig* sig = WasmOpcodes::Signature(opcode); + if (sig != nullptr) { + switch (opcode) { + case kExprI32SConvertSatF32: + case kExprI32UConvertSatF32: + case kExprI32SConvertSatF64: + case kExprI32UConvertSatF64: + case kExprI64SConvertSatF32: + case kExprI64UConvertSatF32: + case kExprI64SConvertSatF64: + case kExprI64UConvertSatF64: + BuildSimpleOperator(opcode, sig); + break; + case kExprMemoryInit: { + MemoryInitImmediate<validate> imm(this, this->pc_); + if (!this->Validate(imm)) break; + len += imm.length; + auto size = Pop(2, sig->GetParam(2)); + auto src = Pop(1, sig->GetParam(1)); + auto dst = Pop(0, sig->GetParam(0)); + CALL_INTERFACE_IF_REACHABLE(MemoryInit, imm, dst, src, size); + break; + } + case kExprMemoryDrop: { + MemoryDropImmediate<validate> imm(this, this->pc_); + if (!this->Validate(imm)) break; + len += imm.length; + CALL_INTERFACE_IF_REACHABLE(MemoryDrop, imm); + break; + } + case kExprMemoryCopy: { + MemoryIndexImmediate<validate> imm(this, this->pc_ + 1); + if (!this->Validate(imm)) break; + len += imm.length; + auto size = Pop(2, sig->GetParam(2)); + auto src = Pop(1, sig->GetParam(1)); + auto dst = Pop(0, sig->GetParam(0)); + CALL_INTERFACE_IF_REACHABLE(MemoryCopy, imm, dst, src, size); + break; + } + case kExprMemoryFill: { + MemoryIndexImmediate<validate> imm(this, this->pc_ + 1); + if (!this->Validate(imm)) break; + len += imm.length; + auto size = Pop(2, sig->GetParam(2)); + auto value = Pop(1, sig->GetParam(1)); + auto dst = Pop(0, sig->GetParam(0)); + CALL_INTERFACE_IF_REACHABLE(MemoryFill, imm, dst, value, size); + break; + } + case kExprTableInit: { + TableInitImmediate<validate> imm(this, this->pc_); + if (!this->Validate(imm)) break; + len += imm.length; + PopArgs(sig); + CALL_INTERFACE_IF_REACHABLE(TableInit, imm, VectorOf(args_)); + break; + } + case kExprTableDrop: { + TableDropImmediate<validate> imm(this, this->pc_); + if (!this->Validate(imm)) break; + len += imm.length; + CALL_INTERFACE_IF_REACHABLE(TableDrop, imm); + break; + } + case kExprTableCopy: { + TableIndexImmediate<validate> imm(this, this->pc_ + 1); + if (!this->Validate(this->pc_ + 1, imm)) break; + len += imm.length; + PopArgs(sig); + CALL_INTERFACE_IF_REACHABLE(TableCopy, imm, VectorOf(args_)); + break; + } + default: + this->error("invalid numeric opcode"); + break; + } + } else { + this->error("invalid numeric opcode"); } + return len; + } - // Simulate that an implicit return morally comes after the current block. - if (implicit && c->end_merge.reached) c->reachability = kReachable; - CALL_INTERFACE_IF_REACHABLE(DoReturn, vec2vec(args_), implicit); + void DoReturn() { + size_t return_count = this->sig_->return_count(); + DCHECK_GE(stack_.size(), return_count); + Vector<Value> return_values = + return_count == 0 + ? Vector<Value>{} + : Vector<Value>{&*(stack_.end() - return_count), return_count}; - EndControl(); + CALL_INTERFACE_IF_REACHABLE(DoReturn, return_values); } inline Value* Push(ValueType type) { DCHECK_NE(kWasmStmt, type); - stack_.push_back(Value::New(this->pc_, type)); + stack_.emplace_back(this->pc_, type); return &stack_.back(); } void PushMergeValues(Control* c, Merge<Value>* merge) { DCHECK_EQ(c, &control_.back()); DCHECK(merge == &c->start_merge || merge == &c->end_merge); - stack_.resize(c->stack_depth); + stack_.erase(stack_.begin() + c->stack_depth, stack_.end()); if (merge->arity == 1) { stack_.push_back(merge->vals.first); } else { - for (unsigned i = 0; i < merge->arity; i++) { + for (uint32_t i = 0; i < merge->arity; i++) { stack_.push_back(merge->vals.array[i]); } } @@ -2354,7 +2554,7 @@ class WasmFullDecoder : public WasmDecoder<validate> { return stack_.data() + old_size; } - Value Pop(int index, ValueType expected) { + V8_INLINE Value Pop(int index, ValueType expected) { auto val = Pop(); if (!VALIDATE(val.type == expected || val.type == kWasmVar || expected == kWasmVar)) { @@ -2366,7 +2566,7 @@ class WasmFullDecoder : public WasmDecoder<validate> { return val; } - Value Pop() { + V8_INLINE Value Pop() { DCHECK(!control_.empty()); uint32_t limit = control_.back().stack_depth; if (stack_.size() <= limit) { @@ -2375,7 +2575,7 @@ class WasmFullDecoder : public WasmDecoder<validate> { this->errorf(this->pc_, "%s found empty stack", SafeOpcodeNameAt(this->pc_)); } - return Value::Unreachable(this->pc_); + return UnreachableValue(this->pc_); } auto val = stack_.back(); stack_.pop_back(); @@ -2396,22 +2596,24 @@ class WasmFullDecoder : public WasmDecoder<validate> { bool TypeCheckMergeValues(Control* c, Merge<Value>* merge) { DCHECK(merge == &c->start_merge || merge == &c->end_merge); DCHECK_GE(stack_.size(), c->stack_depth + merge->arity); + // The computation of {stack_values} is only valid if {merge->arity} is >0. + DCHECK_LT(0, merge->arity); + Value* stack_values = &*(stack_.end() - merge->arity); // Typecheck the topmost {merge->arity} values on the stack. for (uint32_t i = 0; i < merge->arity; ++i) { - auto& val = GetMergeValueFromStack(c, merge, i); - auto& old = (*merge)[i]; - if (val.type != old.type) { - // If {val.type} is polymorphic, which results from unreachable, make - // it more specific by using the merge value's expected type. - // If it is not polymorphic, this is a type error. - if (!VALIDATE(val.type == kWasmVar)) { - this->errorf( - this->pc_, "type error in merge[%u] (expected %s, got %s)", i, - ValueTypes::TypeName(old.type), ValueTypes::TypeName(val.type)); - return false; - } - val.type = old.type; + Value& val = stack_values[i]; + Value& old = (*merge)[i]; + if (val.type == old.type) continue; + // If {val.type} is polymorphic, which results from unreachable, make + // it more specific by using the merge value's expected type. + // If it is not polymorphic, this is a type error. + if (!VALIDATE(val.type == kWasmVar)) { + this->errorf(this->pc_, "type error in merge[%u] (expected %s, got %s)", + i, ValueTypes::TypeName(old.type), + ValueTypes::TypeName(val.type)); + return false; } + val.type = old.type; } return true; @@ -2431,13 +2633,15 @@ class WasmFullDecoder : public WasmDecoder<validate> { expected, startrel(c->pc), actual); return false; } + if (expected == 0) return true; // Fast path. return TypeCheckMergeValues(c, &c->end_merge); } - bool TypeCheckBreak(Control* c) { - // Breaks must have at least the number of values expected; can have more. + bool TypeCheckBranch(Control* c) { + // Branches must have at least the number of values expected; can have more. uint32_t expected = c->br_merge()->arity; + if (expected == 0) return true; // Fast path. DCHECK_GE(stack_.size(), control_.back().stack_depth); uint32_t actual = static_cast<uint32_t>(stack_.size()) - control_.back().stack_depth; @@ -2450,6 +2654,42 @@ class WasmFullDecoder : public WasmDecoder<validate> { return TypeCheckMergeValues(c, c->br_merge()); } + bool TypeCheckReturn() { + // Returns must have at least the number of values expected; can have more. + uint32_t num_returns = static_cast<uint32_t>(this->sig_->return_count()); + DCHECK_GE(stack_.size(), control_.back().stack_depth); + uint32_t actual = + static_cast<uint32_t>(stack_.size()) - control_.back().stack_depth; + if (!InsertUnreachablesIfNecessary(num_returns, actual)) { + this->errorf(this->pc_, + "expected %u elements on the stack for return, found %u", + num_returns, actual); + return false; + } + + // Typecheck the topmost {num_returns} values on the stack. + if (num_returns == 0) return true; + // This line requires num_returns > 0. + Value* stack_values = &*(stack_.end() - num_returns); + for (uint32_t i = 0; i < num_returns; ++i) { + auto& val = stack_values[i]; + ValueType expected_type = this->sig_->GetReturn(i); + if (val.type == expected_type) continue; + // If {val.type} is polymorphic, which results from unreachable, + // make it more specific by using the return's expected type. + // If it is not polymorphic, this is a type error. + if (!VALIDATE(val.type == kWasmVar)) { + this->errorf(this->pc_, + "type error in return[%u] (expected %s, got %s)", i, + ValueTypes::TypeName(expected_type), + ValueTypes::TypeName(val.type)); + return false; + } + val.type = expected_type; + } + return true; + } + inline bool InsertUnreachablesIfNecessary(uint32_t expected, uint32_t actual) { if (V8_LIKELY(actual >= expected)) { @@ -2464,13 +2704,13 @@ class WasmFullDecoder : public WasmDecoder<validate> { // unreachable, insert unreachable values below the actual values. // This simplifies {TypeCheckMergeValues}. auto pos = stack_.begin() + (stack_.size() - actual); - stack_.insert(pos, (expected - actual), Value::Unreachable(this->pc_)); + stack_.insert(pos, expected - actual, UnreachableValue(this->pc_)); return true; } void onFirstError() override { this->end_ = this->pc_; // Terminate decoding loop. - TRACE(" !%s\n", this->error_msg_.c_str()); + TRACE(" !%s\n", this->error_.message().c_str()); CALL_INTERFACE(OnFirstError); } @@ -2485,13 +2725,13 @@ class WasmFullDecoder : public WasmDecoder<validate> { BuildSimpleOperator(opcode, sig); } - inline void BuildSimpleOperator(WasmOpcode opcode, FunctionSig* sig) { + void BuildSimpleOperator(WasmOpcode opcode, FunctionSig* sig) { switch (sig->parameter_count()) { case 1: { auto val = Pop(0, sig->GetParam(0)); auto* ret = sig->return_count() == 0 ? nullptr : Push(sig->GetReturn(0)); - CALL_INTERFACE_IF_REACHABLE(UnOp, opcode, sig, val, ret); + CALL_INTERFACE_IF_REACHABLE(UnOp, opcode, val, ret); break; } case 2: { @@ -2499,13 +2739,35 @@ class WasmFullDecoder : public WasmDecoder<validate> { auto lval = Pop(0, sig->GetParam(0)); auto* ret = sig->return_count() == 0 ? nullptr : Push(sig->GetReturn(0)); - CALL_INTERFACE_IF_REACHABLE(BinOp, opcode, sig, lval, rval, ret); + CALL_INTERFACE_IF_REACHABLE(BinOp, opcode, lval, rval, ret); break; } default: UNREACHABLE(); } } + + void BuildSimpleOperator(WasmOpcode opcode, ValueType return_type, + ValueType arg_type) { + auto val = Pop(0, arg_type); + auto* ret = return_type == kWasmStmt ? nullptr : Push(return_type); + CALL_INTERFACE_IF_REACHABLE(UnOp, opcode, val, ret); + } + + void BuildSimpleOperator(WasmOpcode opcode, ValueType return_type, + ValueType lhs_type, ValueType rhs_type) { + auto rval = Pop(1, rhs_type); + auto lval = Pop(0, lhs_type); + auto* ret = return_type == kWasmStmt ? nullptr : Push(return_type); + CALL_INTERFACE_IF_REACHABLE(BinOp, opcode, lval, rval, ret); + } + +#define DEFINE_SIMPLE_SIG_OPERATOR(sig, ...) \ + void BuildSimpleOperator_##sig(WasmOpcode opcode) { \ + BuildSimpleOperator(opcode, __VA_ARGS__); \ + } + FOREACH_SIGNATURE(DEFINE_SIMPLE_SIG_OPERATOR) +#undef DEFINE_SIMPLE_SIG_OPERATOR }; #undef CALL_INTERFACE |