diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-10-12 14:27:29 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-10-13 09:35:20 +0000 |
commit | c30a6232df03e1efbd9f3b226777b07e087a1122 (patch) | |
tree | e992f45784689f373bcc38d1b79a239ebe17ee23 /chromium/v8/src/wasm/function-body-decoder-impl.h | |
parent | 7b5b123ac58f58ffde0f4f6e488bcd09aa4decd3 (diff) | |
download | qtwebengine-chromium-85-based.tar.gz |
BASELINE: Update Chromium to 85.0.4183.14085-based
Change-Id: Iaa42f4680837c57725b1344f108c0196741f6057
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/v8/src/wasm/function-body-decoder-impl.h')
-rw-r--r-- | chromium/v8/src/wasm/function-body-decoder-impl.h | 2667 |
1 files changed, 1481 insertions, 1186 deletions
diff --git a/chromium/v8/src/wasm/function-body-decoder-impl.h b/chromium/v8/src/wasm/function-body-decoder-impl.h index 48b804a3a92..d038a7c8d52 100644 --- a/chromium/v8/src/wasm/function-body-decoder-impl.h +++ b/chromium/v8/src/wasm/function-body-decoder-impl.h @@ -18,6 +18,7 @@ #include "src/wasm/wasm-limits.h" #include "src/wasm/wasm-module.h" #include "src/wasm/wasm-opcodes.h" +#include "src/wasm/wasm-subtyping.h" namespace v8 { namespace internal { @@ -42,7 +43,7 @@ struct WasmException; }()) #define CHECK_PROTOTYPE_OPCODE_GEN(feat, opt_break) \ - DCHECK(!this->module_ || this->module_->origin == kWasmOrigin); \ + DCHECK(this->module_->origin == kWasmOrigin); \ if (!this->enabled_.has_##feat()) { \ this->error("Invalid opcode (enable with --experimental-wasm-" #feat ")"); \ opt_break \ @@ -128,6 +129,138 @@ struct WasmException; V(I64AtomicStore16U, Uint16) \ V(I64AtomicStore32U, Uint32) +namespace value_type_reader { + +// Read a value type starting at address 'pc' in 'decoder'. +// No bytes are consumed. The result is written into the 'result' parameter. +// Returns the amount of bytes read, or 0 if decoding failed. +// Registers an error if the type opcode is invalid iff validate is set. +template <Decoder::ValidateFlag validate> +ValueType read_value_type(Decoder* decoder, const byte* pc, + uint32_t* const length, const WasmFeatures& enabled) { + *length = 1; + byte val = decoder->read_u8<validate>(pc, "value type opcode"); + if (decoder->failed()) { + return kWasmBottom; + } + + ValueTypeCode code = static_cast<ValueTypeCode>(val); + +#define REF_TYPE_CASE(heap_type, nullable, feature) \ + case kLocal##heap_type##Ref: { \ + ValueType result = ValueType::Ref(kHeap##heap_type, nullable); \ + if (enabled.has_##feature()) { \ + return result; \ + } \ + decoder->errorf( \ + pc, "invalid value type '%s', enable with --experimental-wasm-%s", \ + result.type_name().c_str(), #feature); \ + return kWasmBottom; \ + } + + switch (code) { + REF_TYPE_CASE(Func, kNullable, reftypes) + REF_TYPE_CASE(Extern, kNullable, reftypes) + REF_TYPE_CASE(Eq, kNullable, gc) + REF_TYPE_CASE(Exn, kNullable, eh) + case kLocalI32: + return kWasmI32; + case kLocalI64: + return kWasmI64; + case kLocalF32: + return kWasmF32; + case kLocalF64: + return kWasmF64; + case kLocalRef: + case kLocalOptRef: { + // Set length for the macro-defined cases: + *length += 1; + Nullability nullability = code == kLocalOptRef ? kNullable : kNonNullable; + uint8_t heap_index = decoder->read_u8<validate>(pc + 1, "heap type"); + switch (static_cast<ValueTypeCode>(heap_index)) { + REF_TYPE_CASE(Func, nullability, typed_funcref) + REF_TYPE_CASE(Extern, nullability, typed_funcref) + REF_TYPE_CASE(Eq, nullability, gc) + REF_TYPE_CASE(Exn, nullability, eh) + default: + uint32_t type_index = + decoder->read_u32v<validate>(pc + 1, length, "type index"); + *length += 1; + if (!enabled.has_gc()) { + decoder->error( + pc, + "invalid value type '(ref [null] (type $t))', enable with " + "--experimental-wasm-typed-gc"); + return kWasmBottom; + } + + if (!VALIDATE(type_index < kV8MaxWasmTypes)) { + decoder->errorf(pc + 1, + "Type index %u is greater than the maximum " + "number %zu of type definitions supported by V8", + type_index, kV8MaxWasmTypes); + return kWasmBottom; + } + return ValueType::Ref(static_cast<HeapType>(type_index), nullability); + } + decoder->errorf( + pc, + "invalid value type '(ref%s $t)', enable with --experimental-wasm-gc", + nullability ? " null" : ""); + return kWasmBottom; + } +#undef REF_TYPE_CASE + case kLocalRtt: + if (enabled.has_gc()) { + uint32_t depth_length; + uint32_t depth = + decoder->read_u32v<validate>(pc + 1, &depth_length, "depth"); + // TODO(7748): Introduce a proper limit. + const uint32_t kMaxRttSubtypingDepth = 7; + if (!VALIDATE(depth <= kMaxRttSubtypingDepth)) { + decoder->errorf(pc, + "subtyping depth %u is greater than the maximum " + "depth %u supported by V8", + depth, kMaxRttSubtypingDepth); + return kWasmBottom; + } + uint32_t type_index = decoder->read_u32v<validate>( + pc + 1 + depth_length, length, "type index"); + if (!VALIDATE(type_index < kV8MaxWasmTypes)) { + decoder->errorf(pc, + "Type index %u is greater than the maximum " + "number %zu of type definitions supported by V8", + type_index, kV8MaxWasmTypes); + return kWasmBottom; + } + *length += 1 + depth_length; + return ValueType::Rtt(static_cast<HeapType>(type_index), + static_cast<uint8_t>(depth)); + } + decoder->error( + pc, "invalid value type 'rtt', enable with --experimental-wasm-gc"); + return kWasmBottom; + case kLocalS128: + if (enabled.has_simd()) { + return kWasmS128; + } + decoder->error( + pc, + "invalid value type 'Simd128', enable with --experimental-wasm-simd"); + return kWasmBottom; + case kLocalVoid: + case kLocalI8: + case kLocalI16: + // Although these types are included in ValueType, they are technically + // not value types and are only used in specific contexts. The caller of + // this function is responsible to check for them separately. + break; + } + // Malformed modules specifying invalid types can get here. + return kWasmBottom; +} +} // namespace value_type_reader + // Helpers for decoding different kinds of immediates which follow bytecodes. template <Decoder::ValidateFlag validate> struct LocalIndexImmediate { @@ -174,7 +307,9 @@ struct ImmF32Immediate { float value; 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. + // We can't use bit_cast here because calling any helper function that + // returns a float would potentially flip NaN bits per C++ semantics, so we + // have to inline the memcpy call directly. uint32_t tmp = decoder->read_u32<validate>(pc + 1, "immf32"); memcpy(&value, &tmp, sizeof(value)); } @@ -192,6 +327,17 @@ struct ImmF64Immediate { }; template <Decoder::ValidateFlag validate> +struct RefNullImmediate { + ValueType type; + uint32_t length = 1; + inline RefNullImmediate(const WasmFeatures& enabled, Decoder* decoder, + const byte* pc) { + type = value_type_reader::read_value_type<validate>(decoder, pc + 1, + &length, enabled); + } +}; + +template <Decoder::ValidateFlag validate> struct GlobalIndexImmediate { uint32_t index; ValueType type = kWasmStmt; @@ -203,135 +349,6 @@ struct GlobalIndexImmediate { } }; -namespace value_type_reader { - -// Read a value type starting at address 'pc' in 'decoder'. -// No bytes are consumed. The result is written into the 'result' parameter. -// Returns the amount of bytes read, or 0 if decoding failed. -// Registers an error if the type opcode is invalid iff validate is set. -template <Decoder::ValidateFlag validate> -uint32_t read_value_type(Decoder* decoder, const byte* pc, ValueType* result, - const WasmFeatures& enabled) { - byte val = decoder->read_u8<validate>(pc, "value type opcode"); - if (decoder->failed()) return 0; - - ValueTypeCode code = static_cast<ValueTypeCode>(val); - switch (code) { - case kLocalI32: - *result = kWasmI32; - return 1; - case kLocalI64: - *result = kWasmI64; - return 1; - case kLocalF32: - *result = kWasmF32; - return 1; - case kLocalF64: - *result = kWasmF64; - return 1; - case kLocalAnyRef: - if (enabled.has_anyref()) { - *result = kWasmAnyRef; - return 1; - } - decoder->error(pc, - "invalid value type 'anyref', enable with " - "--experimental-wasm-anyref"); - return 0; - case kLocalFuncRef: - if (enabled.has_anyref()) { - *result = kWasmFuncRef; - return 1; - } - decoder->error(pc, - "invalid value type 'funcref', enable with " - "--experimental-wasm-anyref"); - return 0; - case kLocalNullRef: - if (enabled.has_anyref()) { - *result = kWasmNullRef; - return 1; - } - decoder->error(pc, - "invalid value type 'nullref', enable with " - "--experimental-wasm-anyref"); - return 0; - case kLocalExnRef: - if (enabled.has_eh()) { - *result = kWasmExnRef; - return 1; - } - decoder->error(pc, - "invalid value type 'exception ref', enable with " - "--experimental-wasm-eh"); - return 0; - case kLocalRef: - if (enabled.has_gc()) { - uint32_t length; - uint32_t type_index = - decoder->read_u32v<validate>(pc + 1, &length, "type index"); - *result = ValueType(ValueType::kRef, type_index); - return length + 1; - } - decoder->error(pc, - "invalid value type 'ref', enable with " - "--experimental-wasm-gc"); - return 0; - case kLocalOptRef: - if (enabled.has_gc()) { - uint32_t length; - uint32_t type_index = - decoder->read_u32v<validate>(pc + 1, &length, "type index"); - *result = ValueType(ValueType::kOptRef, type_index); - return length + 1; - } - decoder->error(pc, - "invalid value type 'optref', enable with " - "--experimental-wasm-gc"); - return 0; - case kLocalEqRef: - if (enabled.has_gc()) { - *result = kWasmEqRef; - return 1; - } - decoder->error(pc, - "invalid value type 'eqref', enable with " - "--experimental-wasm-simd"); - return 0; - case kLocalI31Ref: - if (enabled.has_gc()) { - // TODO(7748): Implement - decoder->error(pc, "'i31ref' is unimplemented"); - } - decoder->error(pc, - "invalid value type 'i31ref', enable with " - "--experimental-wasm-simd"); - return 0; - case kLocalRttRef: - if (enabled.has_gc()) { - // TODO(7748): Implement - decoder->error(pc, "'rttref' is unimplemented"); - } - decoder->error(pc, - "invalid value type 'rttref', enable with " - "--experimental-wasm-simd"); - return 0; - case kLocalS128: - if (enabled.has_simd()) { - *result = kWasmS128; - return 1; - } - decoder->error(pc, - "invalid value type 'Simd128', enable with " - "--experimental-wasm-simd"); - return 0; - default: - *result = kWasmBottom; - return 0; - } -} -} // namespace value_type_reader - template <Decoder::ValidateFlag validate> struct SelectTypeImmediate { uint32_t length; @@ -346,10 +363,11 @@ struct SelectTypeImmediate { pc + 1, "Invalid number of types. Select accepts exactly one type"); return; } - uint32_t type_length = value_type_reader::read_value_type<validate>( - decoder, pc + length + 1, &type, enabled); + uint32_t type_length; + type = value_type_reader::read_value_type<validate>( + decoder, pc + length + 1, &type_length, enabled); length += type_length; - if (type_length == 0) { + if (type == kWasmBottom) { decoder->error(pc + 1, "invalid select type"); } } @@ -368,9 +386,9 @@ struct BlockTypeImmediate { // 1st case: void block. Struct fields stay at default values. return; } - length = value_type_reader::read_value_type<validate>(decoder, pc + 1, - &type, enabled); - if (length > 0) { + type = value_type_reader::read_value_type<validate>(decoder, pc + 1, + &length, enabled); + if (type != kWasmBottom) { // 2nd case: block with val type immediate. return; } @@ -497,6 +515,17 @@ struct ArrayIndexImmediate { } }; +// TODO(jkummerow): Make this a superclass of StructIndexImmediate and +// ArrayIndexImmediate? Maybe even FunctionIndexImmediate too? +template <Decoder::ValidateFlag validate> +struct TypeIndexImmediate { + uint32_t index = 0; + uint32_t length = 0; + inline TypeIndexImmediate(Decoder* decoder, const byte* pc) { + index = decoder->read_u32v<validate>(pc, &length, "type index"); + } +}; + template <Decoder::ValidateFlag validate> struct CallIndirectImmediate { uint32_t table_index; @@ -509,7 +538,7 @@ struct CallIndirectImmediate { sig_index = decoder->read_u32v<validate>(pc + 1, &len, "signature index"); TableIndexImmediate<validate> table(decoder, pc + len); if (!VALIDATE((table.index == 0 && table.length == 1) || - enabled.has_anyref())) { + enabled.has_reftypes())) { decoder->errorf(pc + 1 + len, "expected table index 0, found %u", table.index); } @@ -733,7 +762,7 @@ struct Merge { // Reachability::kReachable. bool reached; - Merge(bool reached = false) : reached(reached) {} + explicit Merge(bool reached = false) : reached(reached) {} Value& operator[](uint32_t i) { DCHECK_GT(arity, i); @@ -746,6 +775,7 @@ enum ControlKind : uint8_t { kControlIfElse, kControlBlock, kControlLoop, + kControlLet, kControlTry, kControlTryCatch }; @@ -763,6 +793,7 @@ enum Reachability : uint8_t { template <typename Value> struct ControlBase { ControlKind kind = kControlBlock; + uint32_t locals_count = 0; uint32_t stack_depth = 0; // stack height at the beginning of the construct. const uint8_t* pc = nullptr; Reachability reachability = kReachable; @@ -773,13 +804,16 @@ struct ControlBase { MOVE_ONLY_NO_DEFAULT_CONSTRUCTOR(ControlBase); - ControlBase(ControlKind kind, uint32_t stack_depth, const uint8_t* pc, - Reachability reachability) + ControlBase(ControlKind kind, uint32_t locals_count, uint32_t stack_depth, + const uint8_t* pc, Reachability reachability) : kind(kind), + locals_count(locals_count), stack_depth(stack_depth), pc(pc), reachability(reachability), - start_merge(reachability == kReachable) {} + start_merge(reachability == kReachable) { + DCHECK(kind == kControlLet || locals_count == 0); + } // Check whether the current block is reachable. bool reachable() const { return reachability == kReachable; } @@ -799,6 +833,7 @@ struct ControlBase { bool is_onearmed_if() const { return kind == kControlIf; } bool is_if_else() const { return kind == kControlIfElse; } bool is_block() const { return kind == kControlBlock; } + bool is_let() const { return kind == kControlLet; } bool is_loop() const { return kind == kControlLoop; } bool is_incomplete_try() const { return kind == kControlTry; } bool is_try_catch() const { return kind == kControlTryCatch; } @@ -809,122 +844,120 @@ struct ControlBase { } }; -enum class LoadTransformationKind : uint8_t { - kSplat, - kExtend, -}; - // This is the list of callback functions that an interface for the // WasmFullDecoder should implement. // F(Name, args...) -#define INTERFACE_FUNCTIONS(F) \ - /* General: */ \ - F(StartFunction) \ - F(StartFunctionBody, Control* block) \ - F(FinishFunction) \ - F(OnFirstError) \ - F(NextInstruction, WasmOpcode) \ - /* Control: */ \ - 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, 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(RefFunc, uint32_t function_index, Value* result) \ - F(RefAsNonNull, const Value& arg, Value* result) \ - F(Drop, const Value& value) \ - F(DoReturn, Vector<Value> values) \ - F(LocalGet, Value* result, const LocalIndexImmediate<validate>& imm) \ - F(LocalSet, const Value& value, const LocalIndexImmediate<validate>& imm) \ - F(LocalTee, const Value& value, Value* result, \ - const LocalIndexImmediate<validate>& imm) \ - F(GlobalGet, Value* result, const GlobalIndexImmediate<validate>& imm) \ - F(GlobalSet, const Value& value, const GlobalIndexImmediate<validate>& imm) \ - F(TableGet, const Value& index, Value* result, \ - const TableIndexImmediate<validate>& imm) \ - F(TableSet, const Value& index, const Value& value, \ - const TableIndexImmediate<validate>& imm) \ - F(Unreachable) \ - F(Select, const Value& cond, const Value& fval, const Value& tval, \ - Value* result) \ - F(Br, 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, \ - const Value& index, Value* result) \ - F(LoadTransform, LoadType type, LoadTransformationKind transform, \ - MemoryAccessImmediate<validate>& imm, const Value& index, Value* result) \ - F(StoreMem, StoreType type, const MemoryAccessImmediate<validate>& imm, \ - const Value& index, const Value& value) \ - F(CurrentMemoryPages, 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, \ - const CallIndirectImmediate<validate>& imm, const Value args[], \ - Value returns[]) \ - F(ReturnCall, const CallFunctionImmediate<validate>& imm, \ - const Value args[]) \ - F(ReturnCallIndirect, const Value& index, \ - const CallIndirectImmediate<validate>& imm, const Value args[]) \ - F(BrOnNull, const Value& ref_object, uint32_t depth) \ - F(SimdOp, WasmOpcode opcode, Vector<Value> args, Value* result) \ - F(SimdLaneOp, WasmOpcode opcode, const SimdLaneImmediate<validate>& imm, \ - const Vector<Value> inputs, Value* result) \ - F(Simd8x16ShuffleOp, const Simd8x16ShuffleImmediate<validate>& imm, \ - const Value& input0, const Value& input1, Value* result) \ - F(Throw, const ExceptionIndexImmediate<validate>& imm, \ - const Vector<Value>& args) \ - 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) \ - F(AtomicFence) \ - F(MemoryInit, const MemoryInitImmediate<validate>& imm, const Value& dst, \ - const Value& src, const Value& size) \ - F(DataDrop, const DataDropImmediate<validate>& imm) \ - F(MemoryCopy, const MemoryCopyImmediate<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(ElemDrop, const ElemDropImmediate<validate>& imm) \ - F(TableCopy, const TableCopyImmediate<validate>& imm, Vector<Value> args) \ - F(TableGrow, const TableIndexImmediate<validate>& imm, const Value& value, \ - const Value& delta, Value* result) \ - F(TableSize, const TableIndexImmediate<validate>& imm, Value* result) \ - F(TableFill, const TableIndexImmediate<validate>& imm, const Value& start, \ - const Value& value, const Value& count) \ - F(StructNew, const StructIndexImmediate<validate>& imm, const Value args[], \ - Value* result) \ - F(StructGet, const Value& struct_object, \ - const FieldIndexImmediate<validate>& field, Value* result) \ - F(StructSet, const Value& struct_object, \ - const FieldIndexImmediate<validate>& field, const Value& field_value) \ - F(ArrayNew, const ArrayIndexImmediate<validate>& imm, const Value& length, \ - const Value& initial_value, Value* result) \ - F(ArrayGet, const Value& array_obj, \ - const ArrayIndexImmediate<validate>& imm, const Value& index, \ - Value* result) \ - F(ArraySet, const Value& array_obj, \ - const ArrayIndexImmediate<validate>& imm, const Value& index, \ - const Value& value) \ - F(ArrayLen, const Value& array_obj, Value* result) \ +#define INTERFACE_FUNCTIONS(F) \ + /* General: */ \ + F(StartFunction) \ + F(StartFunctionBody, Control* block) \ + F(FinishFunction) \ + F(OnFirstError) \ + F(NextInstruction, WasmOpcode) \ + /* Control: */ \ + 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, 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(RefFunc, uint32_t function_index, Value* result) \ + F(RefAsNonNull, const Value& arg, Value* result) \ + F(Drop, const Value& value) \ + F(DoReturn, Vector<Value> values) \ + F(LocalGet, Value* result, const LocalIndexImmediate<validate>& imm) \ + F(LocalSet, const Value& value, const LocalIndexImmediate<validate>& imm) \ + F(LocalTee, const Value& value, Value* result, \ + const LocalIndexImmediate<validate>& imm) \ + F(AllocateLocals, Vector<Value> local_values) \ + F(DeallocateLocals, uint32_t count) \ + F(GlobalGet, Value* result, const GlobalIndexImmediate<validate>& imm) \ + F(GlobalSet, const Value& value, const GlobalIndexImmediate<validate>& imm) \ + F(TableGet, const Value& index, Value* result, \ + const TableIndexImmediate<validate>& imm) \ + F(TableSet, const Value& index, const Value& value, \ + const TableIndexImmediate<validate>& imm) \ + F(Unreachable) \ + F(Select, const Value& cond, const Value& fval, const Value& tval, \ + Value* result) \ + F(Br, 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, \ + const Value& index, Value* result) \ + F(LoadTransform, LoadType type, LoadTransformationKind transform, \ + MemoryAccessImmediate<validate>& imm, const Value& index, Value* result) \ + F(StoreMem, StoreType type, const MemoryAccessImmediate<validate>& imm, \ + const Value& index, const Value& value) \ + F(CurrentMemoryPages, 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, \ + const CallIndirectImmediate<validate>& imm, const Value args[], \ + Value returns[]) \ + F(ReturnCall, const CallFunctionImmediate<validate>& imm, \ + const Value args[]) \ + F(ReturnCallIndirect, const Value& index, \ + const CallIndirectImmediate<validate>& imm, const Value args[]) \ + F(BrOnNull, const Value& ref_object, uint32_t depth) \ + F(SimdOp, WasmOpcode opcode, Vector<Value> args, Value* result) \ + F(SimdLaneOp, WasmOpcode opcode, const SimdLaneImmediate<validate>& imm, \ + const Vector<Value> inputs, Value* result) \ + F(Simd8x16ShuffleOp, const Simd8x16ShuffleImmediate<validate>& imm, \ + const Value& input0, const Value& input1, Value* result) \ + F(Throw, const ExceptionIndexImmediate<validate>& imm, \ + const Vector<Value>& args) \ + 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) \ + F(AtomicFence) \ + F(MemoryInit, const MemoryInitImmediate<validate>& imm, const Value& dst, \ + const Value& src, const Value& size) \ + F(DataDrop, const DataDropImmediate<validate>& imm) \ + F(MemoryCopy, const MemoryCopyImmediate<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(ElemDrop, const ElemDropImmediate<validate>& imm) \ + F(TableCopy, const TableCopyImmediate<validate>& imm, Vector<Value> args) \ + F(TableGrow, const TableIndexImmediate<validate>& imm, const Value& value, \ + const Value& delta, Value* result) \ + F(TableSize, const TableIndexImmediate<validate>& imm, Value* result) \ + F(TableFill, const TableIndexImmediate<validate>& imm, const Value& start, \ + const Value& value, const Value& count) \ + F(StructNew, const StructIndexImmediate<validate>& imm, const Value args[], \ + Value* result) \ + F(StructGet, const Value& struct_object, \ + const FieldIndexImmediate<validate>& field, bool is_signed, Value* result) \ + F(StructSet, const Value& struct_object, \ + const FieldIndexImmediate<validate>& field, const Value& field_value) \ + F(ArrayNew, const ArrayIndexImmediate<validate>& imm, const Value& length, \ + const Value& initial_value, Value* result) \ + F(ArrayGet, const Value& array_obj, \ + const ArrayIndexImmediate<validate>& imm, const Value& index, \ + bool is_signed, Value* result) \ + F(ArraySet, const Value& array_obj, \ + const ArrayIndexImmediate<validate>& imm, const Value& index, \ + const Value& value) \ + F(ArrayLen, const Value& array_obj, Value* result) \ + F(RttCanon, const TypeIndexImmediate<validate>& imm, Value* result) \ F(PassThrough, const Value& from, Value* to) // Generic Wasm bytecode decoder with utilities for decoding immediates, @@ -954,44 +987,81 @@ class WasmDecoder : public Decoder { : static_cast<uint32_t>(local_types_->size()); } - static bool DecodeLocals(const WasmFeatures& enabled, Decoder* decoder, - const FunctionSig* sig, - ZoneVector<ValueType>* type_list) { - DCHECK_NOT_NULL(type_list); - DCHECK_EQ(0, type_list->size()); - // Initialize from signature. - if (sig != nullptr) { - type_list->assign(sig->parameters().begin(), sig->parameters().end()); + void InitializeLocalsFromSig() { + if (sig_ != nullptr) { + local_types_->assign(sig_->parameters().begin(), + sig_->parameters().end()); } + } + + // Decodes local definitions in the current decoder. + // Returns true iff locals are found. + // Writes the total length of decoded locals in 'total_length'. + // If insert_postion is present, the decoded locals will be inserted into the + // 'local_types_' of this decoder. Otherwise, this function is used just to + // check validity and determine the encoding length of the locals in bytes. + // The decoder's pc is not advanced. If no locals are found (i.e., no + // compressed uint32 is found at pc), this will exit as 'false' and without an + // error. + bool DecodeLocals(const byte* pc, uint32_t* total_length, + const base::Optional<uint32_t> insert_position) { + DCHECK_NOT_NULL(local_types_); + + uint32_t length; + *total_length = 0; + + // The 'else' value is useless, we pass it for convenience. + ZoneVector<ValueType>::iterator insert_iterator = + insert_position.has_value() + ? local_types_->begin() + insert_position.value() + : local_types_->begin(); + // Decode local declarations, if any. - uint32_t entries = decoder->consume_u32v("local decls count"); - if (decoder->failed()) return false; + uint32_t entries = read_u32v<kValidate>(pc, &length, "local decls count"); + if (failed()) { + error(pc + *total_length, "invalid local decls count"); + return false; + } + *total_length += length; TRACE("local decls count: %u\n", entries); - while (entries-- > 0 && decoder->more()) { - uint32_t count = decoder->consume_u32v("local count"); - if (decoder->failed()) return false; - DCHECK_LE(type_list->size(), kV8MaxWasmFunctionLocals); - if (count > kV8MaxWasmFunctionLocals - type_list->size()) { - decoder->error(decoder->pc() - 1, "local count too large"); + while (entries-- > 0) { + if (!more()) { + error(end(), "expected more local decls but reached end of input"); + return false; + } + uint32_t count = + read_u32v<kValidate>(pc + *total_length, &length, "local count"); + if (failed()) { + error(pc + *total_length, "invalid local count"); return false; } - ValueType type; - uint32_t type_length = value_type_reader::read_value_type<validate>( - decoder, decoder->pc(), &type, enabled); - if (type_length == 0) { - decoder->error(decoder->pc(), "invalid local type"); + DCHECK_LE(local_types_->size(), kV8MaxWasmFunctionLocals); + if (count > kV8MaxWasmFunctionLocals - local_types_->size()) { + error(pc + *total_length, "local count too large"); return false; } - type_list->insert(type_list->end(), count, type); - decoder->consume_bytes(type_length); + *total_length += length; + + ValueType type = value_type_reader::read_value_type<kValidate>( + this, pc + *total_length, &length, enabled_); + if (type == kWasmBottom) { + error(pc + *total_length, "invalid local type"); + return false; + } + *total_length += length; + if (insert_position.has_value()) { + // Move the insertion iterator to the end of the newly inserted locals. + insert_iterator = + local_types_->insert(insert_iterator, count, type) + count; + } } - DCHECK(decoder->ok()); + DCHECK(ok()); return true; } - static BitVector* AnalyzeLoopAssignment(Decoder* decoder, const byte* pc, + static BitVector* AnalyzeLoopAssignment(WasmDecoder* decoder, const byte* pc, uint32_t locals_count, Zone* zone) { if (pc >= decoder->end()) return nullptr; if (*pc != kExprLoop) return nullptr; @@ -1055,11 +1125,17 @@ class WasmDecoder : public Decoder { return true; } - inline bool Complete(const byte* pc, ExceptionIndexImmediate<validate>& imm) { - if (!VALIDATE(module_ != nullptr && - imm.index < module_->exceptions.size())) { + inline bool Validate(const byte* pc, RefNullImmediate<validate>& imm) { + if (!VALIDATE(imm.type.is_nullable())) { + errorf(pc + 1, "ref.null does not exist for %s", + imm.type.type_name().c_str()); return false; } + return true; + } + + inline bool Complete(const byte* pc, ExceptionIndexImmediate<validate>& imm) { + if (!VALIDATE(imm.index < module_->exceptions.size())) return false; imm.exception = &module_->exceptions[imm.index]; return true; } @@ -1073,7 +1149,7 @@ class WasmDecoder : public Decoder { } inline bool Validate(const byte* pc, GlobalIndexImmediate<validate>& imm) { - if (!VALIDATE(module_ != nullptr && imm.index < module_->globals.size())) { + if (!VALIDATE(imm.index < module_->globals.size())) { errorf(pc + 1, "invalid global index: %u", imm.index); return false; } @@ -1083,9 +1159,7 @@ class WasmDecoder : public Decoder { } inline bool Complete(const byte* pc, StructIndexImmediate<validate>& imm) { - if (!VALIDATE(module_ != nullptr && module_->has_struct(imm.index))) { - return false; - } + if (!VALIDATE(module_->has_struct(imm.index))) return false; imm.struct_type = module_->struct_type(imm.index); return true; } @@ -1104,9 +1178,7 @@ class WasmDecoder : public Decoder { } inline bool Complete(const byte* pc, ArrayIndexImmediate<validate>& imm) { - if (!VALIDATE(module_ != nullptr && module_->has_array(imm.index))) { - return false; - } + if (!VALIDATE(module_->has_array(imm.index))) return false; imm.array_type = module_->array_type(imm.index); return true; } @@ -1117,6 +1189,15 @@ class WasmDecoder : public Decoder { return false; } + inline bool Validate(const byte* pc, TypeIndexImmediate<validate>& imm) { + if (!VALIDATE(module_ != nullptr && (module_->has_struct(imm.index) || + module_->has_array(imm.index)))) { + errorf(pc, "invalid type index: %u", imm.index); + return false; + } + return true; + } + inline bool CanReturnCall(const FunctionSig* target_sig) { if (target_sig == nullptr) return false; size_t num_returns = sig_->return_count(); @@ -1128,11 +1209,11 @@ class WasmDecoder : public Decoder { } inline bool Complete(const byte* pc, CallFunctionImmediate<validate>& imm) { - if (!VALIDATE(module_ != nullptr && - imm.index < module_->functions.size())) { - return false; - } + if (!VALIDATE(imm.index < module_->functions.size())) return false; imm.sig = module_->functions[imm.index].sig; + if (imm.sig->return_count() > 1) { + this->detected_->Add(kFeature_mv); + } return true; } @@ -1145,22 +1226,20 @@ class WasmDecoder : public Decoder { } inline bool Complete(const byte* pc, CallIndirectImmediate<validate>& imm) { - if (!VALIDATE(module_ != nullptr && - module_->has_signature(imm.sig_index))) { - return false; - } + if (!VALIDATE(module_->has_signature(imm.sig_index))) return false; imm.sig = module_->signature(imm.sig_index); + if (imm.sig->return_count() > 1) { + this->detected_->Add(kFeature_mv); + } return true; } inline bool Validate(const byte* pc, CallIndirectImmediate<validate>& imm) { - if (!VALIDATE(module_ != nullptr && - imm.table_index < module_->tables.size())) { + if (!VALIDATE(imm.table_index < module_->tables.size())) { error("function table has to exist to execute call_indirect"); return false; } - if (!VALIDATE(module_ != nullptr && - module_->tables[imm.table_index].type == kWasmFuncRef)) { + if (!VALIDATE(module_->tables[imm.table_index].type == kWasmFuncRef)) { error("table of call_indirect must be of type funcref"); return false; } @@ -1235,7 +1314,7 @@ class WasmDecoder : public Decoder { max_lane = std::max(max_lane, imm.shuffle[i]); } // Shuffle indices must be in [0..31] for a 16 lane shuffle. - if (!VALIDATE(max_lane <= 2 * kSimd128Size)) { + if (!VALIDATE(max_lane < 2 * kSimd128Size)) { error(pc_ + 2, "invalid shuffle mask"); return false; } @@ -1244,24 +1323,24 @@ class WasmDecoder : public Decoder { inline bool Complete(BlockTypeImmediate<validate>& imm) { if (imm.type != kWasmBottom) return true; - if (!VALIDATE(module_ && module_->has_signature(imm.sig_index))) { - return false; - } + if (!VALIDATE(module_->has_signature(imm.sig_index))) return false; imm.sig = module_->signature(imm.sig_index); + if (imm.sig->return_count() > 1) { + this->detected_->Add(kFeature_mv); + } return true; } inline bool Validate(BlockTypeImmediate<validate>& imm) { if (!Complete(imm)) { errorf(pc_, "block type index %u out of bounds (%zu types)", - imm.sig_index, module_ ? module_->types.size() : 0); + imm.sig_index, module_->types.size()); return false; } return true; } inline bool Validate(const byte* pc, FunctionIndexImmediate<validate>& imm) { - if (!module_) return true; if (!VALIDATE(imm.index < module_->functions.size())) { errorf(pc, "invalid function index: %u", imm.index); return false; @@ -1274,7 +1353,7 @@ class WasmDecoder : public Decoder { } inline bool Validate(const byte* pc, MemoryIndexImmediate<validate>& imm) { - if (!VALIDATE(module_ != nullptr && module_->has_memory)) { + if (!VALIDATE(module_->has_memory)) { errorf(pc + 1, "memory instruction with no memory"); return false; } @@ -1282,9 +1361,8 @@ class WasmDecoder : public Decoder { } inline bool Validate(MemoryInitImmediate<validate>& imm) { - if (!VALIDATE(module_ != nullptr && - imm.data_segment_index < - module_->num_declared_data_segments)) { + if (!VALIDATE(imm.data_segment_index < + module_->num_declared_data_segments)) { errorf(pc_ + 2, "invalid data segment index: %u", imm.data_segment_index); return false; } @@ -1294,8 +1372,7 @@ class WasmDecoder : public Decoder { } inline bool Validate(DataDropImmediate<validate>& imm) { - if (!VALIDATE(module_ != nullptr && - imm.index < module_->num_declared_data_segments)) { + if (!VALIDATE(imm.index < module_->num_declared_data_segments)) { errorf(pc_ + 2, "invalid data segment index: %u", imm.index); return false; } @@ -1309,7 +1386,7 @@ class WasmDecoder : public Decoder { } inline bool Validate(const byte* pc, TableIndexImmediate<validate>& imm) { - if (!VALIDATE(module_ != nullptr && imm.index < module_->tables.size())) { + if (!VALIDATE(imm.index < module_->tables.size())) { errorf(pc, "invalid table index: %u", imm.index); return false; } @@ -1317,8 +1394,7 @@ class WasmDecoder : public Decoder { } inline bool Validate(TableInitImmediate<validate>& imm) { - if (!VALIDATE(module_ != nullptr && - imm.elem_segment_index < module_->elem_segments.size())) { + if (!VALIDATE(imm.elem_segment_index < module_->elem_segments.size())) { errorf(pc_ + 2, "invalid element segment index: %u", imm.elem_segment_index); return false; @@ -1327,18 +1403,17 @@ class WasmDecoder : public Decoder { return false; } ValueType elem_type = module_->elem_segments[imm.elem_segment_index].type; - if (!VALIDATE( - elem_type.IsSubTypeOf(module_->tables[imm.table.index].type))) { + if (!VALIDATE(IsSubtypeOf(elem_type, module_->tables[imm.table.index].type, + module_))) { errorf(pc_ + 2, "table %u is not a super-type of %s", imm.table.index, - elem_type.type_name()); + elem_type.type_name().c_str()); return false; } return true; } inline bool Validate(ElemDropImmediate<validate>& imm) { - if (!VALIDATE(module_ != nullptr && - imm.index < module_->elem_segments.size())) { + if (!VALIDATE(imm.index < module_->elem_segments.size())) { errorf(pc_ + 2, "invalid element segment index: %u", imm.index); return false; } @@ -1349,16 +1424,16 @@ class WasmDecoder : public Decoder { if (!Validate(pc_ + 1, imm.table_src)) return false; if (!Validate(pc_ + 2, imm.table_dst)) return false; ValueType src_type = module_->tables[imm.table_src.index].type; - if (!VALIDATE( - src_type.IsSubTypeOf(module_->tables[imm.table_dst.index].type))) { + if (!VALIDATE(IsSubtypeOf( + src_type, module_->tables[imm.table_dst.index].type, module_))) { errorf(pc_ + 2, "table %u is not a super-type of %s", imm.table_dst.index, - src_type.type_name()); + src_type.type_name().c_str()); return false; } return true; } - static uint32_t OpcodeLength(Decoder* decoder, const byte* pc) { + static uint32_t OpcodeLength(WasmDecoder* decoder, const byte* pc) { WasmOpcode opcode = static_cast<WasmOpcode>(*pc); switch (opcode) { #define DECLARE_OPCODE_CASE(name, opcode, sig) case kExpr##name: @@ -1403,6 +1478,15 @@ class WasmDecoder : public Decoder { return 1 + imm.length; } + case kExprLet: { + BlockTypeImmediate<validate> imm(WasmFeatures::All(), decoder, pc); + uint32_t locals_length; + bool locals_result = + decoder->DecodeLocals(decoder->pc() + 1 + imm.length, + &locals_length, base::Optional<uint32_t>()); + return 1 + imm.length + (locals_result ? locals_length : 0); + } + case kExprThrow: { ExceptionIndexImmediate<validate> imm(decoder, pc); return 1 + imm.length; @@ -1442,6 +1526,10 @@ class WasmDecoder : public Decoder { return 1 + imm.length; } case kExprRefNull: { + RefNullImmediate<validate> imm(WasmFeatures::All(), decoder, pc); + return 1 + imm.length; + } + case kExprRefIsNull: { return 1; } case kExprRefFunc: { @@ -1594,13 +1682,27 @@ class WasmDecoder : public Decoder { BranchDepthImmediate<validate> imm(decoder, pc + 2); return 2 + imm.length; } - case kExprRttGet: + case kExprRttCanon: { + // TODO(7748): Introduce "HeapTypeImmediate" and use it here. + TypeIndexImmediate<validate> heaptype(decoder, pc + 2); + return 2 + heaptype.length; + } case kExprRttSub: { - // TODO(7748): Impelement. - UNIMPLEMENTED(); + // TODO(7748): Implement. + decoder->error(pc, "rtt.sub not implemented yet"); + return 2; } + case kExprI31New: + case kExprI31GetS: + case kExprI31GetU: + case kExprRefTest: + case kExprRefCast: + return 2; + default: + // This is unreachable except for malformed modules. + decoder->error(pc, "invalid gc opcode"); return 2; } } @@ -1609,7 +1711,8 @@ class WasmDecoder : public Decoder { } } - std::pair<uint32_t, uint32_t> StackEffect(const byte* pc) { + // TODO(clemensb): This is only used by the interpreter; move there. + V8_EXPORT_PRIVATE std::pair<uint32_t, uint32_t> StackEffect(const byte* pc) { WasmOpcode opcode = static_cast<WasmOpcode>(*pc); // Handle "simple" opcodes with a fixed signature first. const FunctionSig* sig = WasmOpcodes::Signature(opcode); @@ -1631,6 +1734,7 @@ class WasmDecoder : public Decoder { case kExprMemoryGrow: case kExprRefAsNonNull: case kExprBrOnNull: + case kExprRefIsNull: return {1, 1}; case kExprLocalSet: case kExprGlobalSet: @@ -1682,6 +1786,9 @@ class WasmDecoder : public Decoder { case kExprReturnCallIndirect: case kExprUnreachable: return {0, 0}; + case kExprLet: + // TODO(7748): Implement + return {0, 0}; case kNumericPrefix: case kAtomicPrefix: case kSimdPrefix: { @@ -1712,12 +1819,14 @@ class WasmDecoder : public Decoder { }; #define CALL_INTERFACE(name, ...) interface_.name(this, ##__VA_ARGS__) -#define CALL_INTERFACE_IF_REACHABLE(name, ...) \ - do { \ - DCHECK(!control_.empty()); \ - if (VALIDATE(this->ok()) && control_.back().reachable()) { \ - interface_.name(this, ##__VA_ARGS__); \ - } \ +#define CALL_INTERFACE_IF_REACHABLE(name, ...) \ + do { \ + DCHECK(!control_.empty()); \ + DCHECK_EQ(current_code_reachable_, \ + this->ok() && control_.back().reachable()); \ + if (current_code_reachable_) { \ + interface_.name(this, ##__VA_ARGS__); \ + } \ } while (false) #define CALL_INTERFACE_IF_PARENT_REACHABLE(name, ...) \ do { \ @@ -1765,8 +1874,12 @@ class WasmFullDecoder : public WasmDecoder<validate> { } DCHECK_EQ(0, this->local_types_->size()); - WasmDecoder<validate>::DecodeLocals(this->enabled_, this, this->sig_, - this->local_types_); + this->InitializeLocalsFromSig(); + uint32_t locals_length; + this->DecodeLocals(this->pc(), &locals_length, + static_cast<uint32_t>(this->local_types_->size())); + this->consume_bytes(locals_length); + CALL_INTERFACE(StartFunction); DecodeFunctionBody(); if (!this->failed()) CALL_INTERFACE(FinishFunction); @@ -1839,6 +1952,14 @@ class WasmFullDecoder : public WasmDecoder<validate> { return &*(stack_.end() - depth); } + void SetSucceedingCodeDynamicallyUnreachable() { + Control* current = &control_.back(); + if (current->reachable()) { + current->reachability = kSpecOnlyReachable; + current_code_reachable_ = false; + } + } + private: Zone* zone_; @@ -1847,6 +1968,9 @@ class WasmFullDecoder : public WasmDecoder<validate> { ZoneVector<ValueType> local_type_vec_; // types of local variables. ZoneVector<Value> stack_; // stack of values. ZoneVector<Control> control_; // stack of blocks, loops, and ifs. + // Controls whether code should be generated for the current block (basically + // a cache for {ok() && control_.back().reachable()}). + bool current_code_reachable_ = true; static Value UnreachableValue(const uint8_t* pc) { return Value{pc, kWasmBottom}; @@ -1895,832 +2019,905 @@ class WasmFullDecoder : public WasmDecoder<validate> { int len_ = 0; }; - // Decodes the body of a function. - void DecodeFunctionBody() { - TRACE("wasm-decode %p...%p (module+%u, %d bytes)\n", this->start(), - this->end(), this->pc_offset(), - static_cast<int>(this->end() - this->start())); - - // Set up initial function block. - { - 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{this->pc_, this->sig_->GetReturn(i)}; - }); - CALL_INTERFACE(StartFunctionBody, c); - } - - while (this->pc_ < this->end_) { // decoding loop. - uint32_t len = 1; - WasmOpcode opcode = static_cast<WasmOpcode>(*this->pc_); - - CALL_INTERFACE_IF_REACHABLE(NextInstruction, opcode); + // Helper to avoid calling member methods (which are more expensive to call + // indirectly). + template <WasmOpcode opcode> + static int DecodeOp(WasmFullDecoder* decoder) { + return decoder->DecodeOp<opcode>(); + } + template <WasmOpcode opcode> + int DecodeOp() { #if DEBUG - TraceLine trace_msg; + TraceLine trace_msg; #define TRACE_PART(...) trace_msg.Append(__VA_ARGS__) - if (!WasmOpcodes::IsPrefixOpcode(opcode)) { - TRACE_PART(TRACE_INST_FORMAT, startrel(this->pc_), - WasmOpcodes::OpcodeName(opcode)); - } + if (!WasmOpcodes::IsPrefixOpcode(opcode)) { + TRACE_PART(TRACE_INST_FORMAT, startrel(this->pc_), + WasmOpcodes::OpcodeName(opcode)); + } #else #define TRACE_PART(...) #endif - switch (opcode) { + int len = 1; + + // TODO(clemensb): Break this up into individual functions. + switch (opcode) { #define BUILD_SIMPLE_OPCODE(op, _, sig) \ case kExpr##op: \ BuildSimpleOperator_##sig(opcode); \ break; - FOREACH_SIMPLE_OPCODE(BUILD_SIMPLE_OPCODE) + 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; - auto args = PopArgs(imm.sig); - auto* block = PushControl(kControlBlock); - SetBlockType(block, imm, args.begin()); - 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, kWasmExnRef); - CALL_INTERFACE_IF_REACHABLE(Rethrow, exception); - EndControl(); + case kExprNop: + break; + case kExprBlock: { + BlockTypeImmediate<validate> imm(this->enabled_, this, this->pc_); + if (!this->Validate(imm)) break; + ArgVector args = PopArgs(imm.sig); + Control* block = PushControl(kControlBlock); + SetBlockType(block, imm, args.begin()); + CALL_INTERFACE_IF_REACHABLE(Block, block); + PushMergeValues(block, &block->start_merge); + len = 1 + imm.length; + break; + } + case kExprRethrow: { + CHECK_PROTOTYPE_OPCODE(eh); + Value exception = Pop(0, kWasmExnRef); + 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; + ArgVector args = 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; + ArgVector args = PopArgs(imm.sig); + Control* try_block = PushControl(kControlTry); + SetBlockType(try_block, imm, args.begin()); + 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 kExprThrow: { - CHECK_PROTOTYPE_OPCODE(eh); - ExceptionIndexImmediate<validate> imm(this, this->pc_); - len = 1 + imm.length; - if (!this->Validate(this->pc_, imm)) break; - auto args = PopArgs(imm.exception->ToFunctionSig()); - CALL_INTERFACE_IF_REACHABLE(Throw, imm, VectorOf(args)); - EndControl(); + Control* c = &control_.back(); + if (!VALIDATE(c->is_try())) { + this->error("catch does not match any try"); break; } - case kExprTry: { - CHECK_PROTOTYPE_OPCODE(eh); - BlockTypeImmediate<validate> imm(this->enabled_, this, this->pc_); - if (!this->Validate(imm)) break; - auto args = PopArgs(imm.sig); - auto* try_block = PushControl(kControlTry); - SetBlockType(try_block, imm, args.begin()); - len = 1 + imm.length; - CALL_INTERFACE_IF_REACHABLE(Try, try_block); - PushMergeValues(try_block, &try_block->start_merge); + if (!VALIDATE(c->is_incomplete_try())) { + this->error("catch already present for try"); break; } - case kExprCatch: { - CHECK_PROTOTYPE_OPCODE(eh); - 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())) { - this->error("catch already present for try"); - break; + c->kind = kControlTryCatch; + FallThruTo(c); + stack_.erase(stack_.begin() + c->stack_depth, stack_.end()); + c->reachability = control_at(1)->innerReachability(); + current_code_reachable_ = this->ok() && c->reachable(); + Value* exception = Push(kWasmExnRef); + CALL_INTERFACE_IF_PARENT_REACHABLE(Catch, c, exception); + break; + } + case kExprBrOnExn: { + CHECK_PROTOTYPE_OPCODE(eh); + BranchOnExceptionImmediate<validate> imm(this, this->pc_); + if (!this->Validate(this->pc_, imm.depth, control_.size())) break; + if (!this->Validate(this->pc_ + imm.depth.length, imm.index)) break; + Control* c = control_at(imm.depth.depth); + Value exception = Pop(0, kWasmExnRef); + const WasmExceptionSig* sig = imm.index.exception->sig; + size_t value_count = sig->parameter_count(); + // TODO(wasm): 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); + TypeCheckBranchResult check_result = TypeCheckBranch(c, true); + if (this->failed()) break; + if (V8_LIKELY(check_result == kReachableBranch)) { + CALL_INTERFACE(BrOnException, exception, imm.index, imm.depth.depth, + values); + c->br_merge()->reached = true; + } else if (check_result == kInvalidStack) { + break; + } + len = 1 + imm.length; + for (size_t i = 0; i < value_count; ++i) Pop(); + Value* pexception = Push(kWasmExnRef); + *pexception = exception; + break; + } + case kExprBrOnNull: { + CHECK_PROTOTYPE_OPCODE(typed_funcref); + BranchDepthImmediate<validate> imm(this, this->pc_); + if (!this->Validate(this->pc_, imm, control_.size())) break; + len = 1 + imm.length; + Value ref_object = Pop(); + if (this->failed()) break; + Control* c = control_at(imm.depth); + TypeCheckBranchResult check_result = TypeCheckBranch(c, true); + if (V8_LIKELY(check_result == kReachableBranch)) { + switch (ref_object.type.kind()) { + case ValueType::kRef: { + Value* result = Push(ref_object.type); + CALL_INTERFACE(PassThrough, ref_object, result); + break; + } + case ValueType::kOptRef: { + // We need to Push the result value after calling BrOnNull on + // the interface. Therefore we must sync the ref_object and + // result nodes afterwards (in PassThrough). + CALL_INTERFACE(BrOnNull, ref_object, imm.depth); + Value* result = Push( + ValueType::Ref(ref_object.type.heap_type(), kNonNullable)); + CALL_INTERFACE(PassThrough, ref_object, result); + c->br_merge()->reached = true; + break; + } + default: + this->error(this->pc_, + "invalid agrument type to ref.as_non_null"); + break; } - c->kind = kControlTryCatch; - FallThruTo(c); - stack_.erase(stack_.begin() + c->stack_depth, stack_.end()); - c->reachability = control_at(1)->innerReachability(); - auto* exception = Push(kWasmExnRef); - CALL_INTERFACE_IF_PARENT_REACHABLE(Catch, c, exception); - break; } - case kExprBrOnExn: { - CHECK_PROTOTYPE_OPCODE(eh); - BranchOnExceptionImmediate<validate> imm(this, this->pc_); - if (!this->Validate(this->pc_, imm.depth, control_.size())) break; - if (!this->Validate(this->pc_ + imm.depth.length, imm.index)) break; - Control* c = control_at(imm.depth.depth); - auto exception = Pop(0, kWasmExnRef); - const WasmExceptionSig* sig = imm.index.exception->sig; - size_t value_count = sig->parameter_count(); - // TODO(wasm): 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); - TypeCheckBranchResult check_result = TypeCheckBranch(c, true); - if (V8_LIKELY(check_result == kReachableBranch)) { - CALL_INTERFACE(BrOnException, exception, imm.index, imm.depth.depth, - values); - c->br_merge()->reached = true; - } else if (check_result == kInvalidStack) { - break; - } - len = 1 + imm.length; - for (size_t i = 0; i < value_count; ++i) Pop(); - auto* pexception = Push(kWasmExnRef); - *pexception = exception; + break; + } + case kExprLet: { + CHECK_PROTOTYPE_OPCODE(typed_funcref); + BlockTypeImmediate<validate> imm(this->enabled_, this, this->pc_); + if (!this->Validate(imm)) break; + uint32_t current_local_count = + static_cast<uint32_t>(local_type_vec_.size()); + // Temporarily add the let-defined values + // to the beginning of the function locals. + uint32_t locals_length; + if (!this->DecodeLocals(this->pc() + 1 + imm.length, &locals_length, + 0)) { + break; + } + len = 1 + imm.length + locals_length; + uint32_t locals_count = + static_cast<uint32_t>(local_type_vec_.size() - current_local_count); + ArgVector let_local_values = + PopArgs(static_cast<uint32_t>(imm.in_arity()), + VectorOf(local_type_vec_.data(), locals_count)); + ArgVector args = PopArgs(imm.sig); + Control* let_block = PushControl(kControlLet, locals_count); + SetBlockType(let_block, imm, args.begin()); + CALL_INTERFACE_IF_REACHABLE(Block, let_block); + PushMergeValues(let_block, &let_block->start_merge); + CALL_INTERFACE_IF_REACHABLE(AllocateLocals, VectorOf(let_local_values)); + break; + } + case kExprLoop: { + BlockTypeImmediate<validate> imm(this->enabled_, this, this->pc_); + if (!this->Validate(imm)) break; + ArgVector args = PopArgs(imm.sig); + Control* block = PushControl(kControlLoop); + SetBlockType(&control_.back(), imm, args.begin()); + 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; + Value cond = Pop(0, kWasmI32); + ArgVector args = PopArgs(imm.sig); + if (!VALIDATE(this->ok())) break; + Control* if_block = PushControl(kControlIf); + SetBlockType(if_block, imm, args.begin()); + 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 kExprBrOnNull: { - CHECK_PROTOTYPE_OPCODE(gc); - BranchDepthImmediate<validate> imm(this, this->pc_); - if (!this->Validate(this->pc_, imm, control_.size())) break; - len = 1 + imm.length; - Value ref_object = Pop(); - if (this->failed()) break; - Control* c = control_at(imm.depth); - TypeCheckBranchResult check_result = TypeCheckBranch(c, true); - if (V8_LIKELY(check_result == kReachableBranch)) { - switch (ref_object.type.kind()) { - case ValueType::kRef: { - auto* result = Push( - ValueType(ValueType::kRef, ref_object.type.ref_index())); - CALL_INTERFACE(PassThrough, ref_object, result); - break; - } - case ValueType::kOptRef: { - // We need to Push the result value after calling BrOnNull on - // the interface. Therefore we must sync the ref_object and - // result nodes afterwards (in PassThrough). - CALL_INTERFACE(BrOnNull, ref_object, imm.depth); - auto* result = Push( - ValueType(ValueType::kRef, ref_object.type.ref_index())); - CALL_INTERFACE(PassThrough, ref_object, result); - c->br_merge()->reached = true; - break; - } - case ValueType::kNullRef: - if (imm.depth == control_.size() - 1) { - DoReturn(); - } else { - CALL_INTERFACE(Br, c); - c->br_merge()->reached = true; - } - EndControl(); - break; - default: - this->error(this->pc_, - "invalid agrument type to ref.as_non_null"); - break; - } - } + 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; - auto args = PopArgs(imm.sig); - auto* block = PushControl(kControlLoop); - SetBlockType(&control_.back(), imm, args.begin()); - 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); - auto args = PopArgs(imm.sig); - if (!VALIDATE(this->ok())) break; - auto* if_block = PushControl(kControlIf); - SetBlockType(if_block, imm, args.begin()); - CALL_INTERFACE_IF_REACHABLE(If, cond, if_block); - len = 1 + imm.length; - PushMergeValues(if_block, &if_block->start_merge); + if (!TypeCheckFallThru()) 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(); + current_code_reachable_ = this->ok() && c->reachable(); + 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; - } - if (!TypeCheckFallThru()) 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(); + 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"); - break; - } - 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()) { - 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 (!TypeCheckOneArmedIf(c)) break; - } - if (!TypeCheckFallThru()) break; - - 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; - } - // The result of the block is the return value. - TRACE_PART("\n" TRACE_INST_FORMAT, startrel(this->pc_), - "(implicit) return"); - DoReturn(); - control_.clear(); - break; - } - PopControl(c); - break; + if (!TypeCheckOneArmedIf(c)) break; + } + if (c->is_let()) { + this->local_types_->erase( + this->local_types_->begin(), + this->local_types_->begin() + c->locals_count); + CALL_INTERFACE_IF_REACHABLE(DeallocateLocals, c->locals_count); } - case kExprSelect: { - auto cond = Pop(2, kWasmI32); - auto fval = Pop(); - auto tval = Pop(0, fval.type); - ValueType type = tval.type == kWasmBottom ? fval.type : tval.type; - if (type.IsSubTypeOf(kWasmAnyRef)) { - this->error( - "select without type is only valid for value type inputs"); + if (!TypeCheckFallThru()) break; + + 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; } - auto* result = Push(type); - CALL_INTERFACE_IF_REACHABLE(Select, cond, fval, tval, result); - break; - } - case kExprSelectWithType: { - CHECK_PROTOTYPE_OPCODE(anyref); - SelectTypeImmediate<validate> imm(this->enabled_, this, this->pc_); - if (this->failed()) break; - auto cond = Pop(2, kWasmI32); - auto fval = Pop(1, imm.type); - auto tval = Pop(0, imm.type); - auto* result = Push(imm.type); - CALL_INTERFACE_IF_REACHABLE(Select, cond, fval, tval, result); - 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 kExprBr: { - BranchDepthImmediate<validate> imm(this, this->pc_); - if (!this->Validate(this->pc_, imm, control_.size())) break; - Control* c = control_at(imm.depth); - TypeCheckBranchResult check_result = TypeCheckBranch(c, false); - if (V8_LIKELY(check_result == kReachableBranch)) { - if (imm.depth == control_.size() - 1) { - DoReturn(); - } else { - CALL_INTERFACE(Br, c); - c->br_merge()->reached = true; - } - } else if (check_result == kInvalidStack) { - break; - } - len = 1 + imm.length; - EndControl(); + PopControl(c); + break; + } + case kExprSelect: { + Value cond = Pop(2, kWasmI32); + Value fval = Pop(); + Value tval = Pop(0, fval.type); + ValueType type = tval.type == kWasmBottom ? fval.type : tval.type; + if (type.is_reference_type()) { + this->error( + "select without type is only valid for value type inputs"); 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); - TypeCheckBranchResult check_result = TypeCheckBranch(c, true); - if (V8_LIKELY(check_result == kReachableBranch)) { - CALL_INTERFACE(BrIf, cond, imm.depth); + Value* result = Push(type); + CALL_INTERFACE_IF_REACHABLE(Select, cond, fval, tval, result); + break; + } + case kExprSelectWithType: { + CHECK_PROTOTYPE_OPCODE(reftypes); + SelectTypeImmediate<validate> imm(this->enabled_, this, this->pc_); + if (this->failed()) break; + Value cond = Pop(2, kWasmI32); + Value fval = Pop(1, imm.type); + Value tval = Pop(0, imm.type); + Value* result = Push(imm.type); + CALL_INTERFACE_IF_REACHABLE(Select, cond, fval, tval, result); + len = 1 + imm.length; + break; + } + case kExprBr: { + BranchDepthImmediate<validate> imm(this, this->pc_); + if (!this->Validate(this->pc_, imm, control_.size())) break; + Control* c = control_at(imm.depth); + TypeCheckBranchResult check_result = TypeCheckBranch(c, false); + if (V8_LIKELY(check_result == kReachableBranch)) { + if (imm.depth == control_.size() - 1) { + DoReturn(); + } else { + CALL_INTERFACE(Br, c); c->br_merge()->reached = true; - } else if (check_result == kInvalidStack) { - break; } - len = 1 + imm.length; + } else if (check_result == kInvalidStack) { 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; - - // Cache the branch targets during the iteration, so that we can set - // all branch targets as reachable after the {CALL_INTERFACE} call. - std::vector<bool> br_targets(control_.size()); - - // The result types of the br_table instruction. We have to check the - // stack against these types. Only needed during validation. - std::vector<ValueType> result_types; - - while (iterator.has_next()) { - const uint32_t index = iterator.cur_index(); - const byte* pos = iterator.pc(); - uint32_t target = iterator.next(); - if (!VALIDATE(ValidateBrTableTarget(target, pos, index))) break; - // Avoid redundant branch target checks. - if (br_targets[target]) continue; - br_targets[target] = true; - - if (validate) { - if (index == 0) { - // With the first branch target, initialize the result types. - result_types = InitializeBrTableResultTypes(target); - } else if (!UpdateBrTableResultTypes(&result_types, target, pos, - index)) { - break; - } + len = 1 + imm.length; + EndControl(); + break; + } + case kExprBrIf: { + BranchDepthImmediate<validate> imm(this, this->pc_); + Value cond = Pop(0, kWasmI32); + if (this->failed()) break; + if (!this->Validate(this->pc_, imm, control_.size())) break; + Control* c = control_at(imm.depth); + TypeCheckBranchResult check_result = TypeCheckBranch(c, true); + if (V8_LIKELY(check_result == kReachableBranch)) { + CALL_INTERFACE(BrIf, cond, imm.depth); + c->br_merge()->reached = true; + } else if (check_result == kInvalidStack) { + break; + } + len = 1 + imm.length; + break; + } + case kExprBrTable: { + BranchTableImmediate<validate> imm(this, this->pc_); + BranchTableIterator<validate> iterator(this, imm); + Value key = Pop(0, kWasmI32); + if (this->failed()) break; + if (!this->Validate(this->pc_, imm, control_.size())) break; + + // Cache the branch targets during the iteration, so that we can set + // all branch targets as reachable after the {CALL_INTERFACE} call. + std::vector<bool> br_targets(control_.size()); + + // The result types of the br_table instruction. We have to check the + // stack against these types. Only needed during validation. + std::vector<ValueType> result_types; + + while (iterator.has_next()) { + const uint32_t index = iterator.cur_index(); + const byte* pos = iterator.pc(); + uint32_t target = iterator.next(); + if (!VALIDATE(ValidateBrTableTarget(target, pos, index))) break; + // Avoid redundant branch target checks. + if (br_targets[target]) continue; + br_targets[target] = true; + + if (validate) { + if (index == 0) { + // With the first branch target, initialize the result types. + result_types = InitializeBrTableResultTypes(target); + } else if (!UpdateBrTableResultTypes(&result_types, target, pos, + index)) { + break; } } + } - if (!VALIDATE(TypeCheckBrTable(result_types))) break; + if (!VALIDATE(TypeCheckBrTable(result_types))) break; - DCHECK(this->ok()); + DCHECK(this->ok()); - if (control_.back().reachable()) { - CALL_INTERFACE(BrTable, imm, key); + if (current_code_reachable_) { + CALL_INTERFACE(BrTable, imm, key); - for (int i = 0, e = control_depth(); i < e; ++i) { - if (!br_targets[i]) continue; - control_at(i)->br_merge()->reached = true; - } + for (int i = 0, e = control_depth(); i < e; ++i) { + if (!br_targets[i]) continue; + control_at(i)->br_merge()->reached = true; } - - len = 1 + iterator.length(); - EndControl(); - break; } - case kExprReturn: { - if (V8_LIKELY(control_.back().reachable())) { - if (!VALIDATE(TypeCheckReturn())) break; - DoReturn(); - } else { - // We pop all return values from the stack to check their type. - // Since we deal with unreachable code, we do not have to keep the - // values. - int num_returns = static_cast<int>(this->sig_->return_count()); - for (int i = num_returns - 1; i >= 0; --i) { - Pop(i, this->sig_->GetReturn(i)); - } - } - 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(kWasmNullRef); - CALL_INTERFACE_IF_REACHABLE(RefNull, value); - len = 1; - break; - } - case kExprRefFunc: { - CHECK_PROTOTYPE_OPCODE(anyref); - FunctionIndexImmediate<validate> imm(this, this->pc_); - if (!this->Validate(this->pc_, imm)) break; - auto* value = Push(kWasmFuncRef); - CALL_INTERFACE_IF_REACHABLE(RefFunc, imm.index, value); - len = 1 + imm.length; - break; - } - case kExprRefAsNonNull: { - CHECK_PROTOTYPE_OPCODE(gc); - auto value = Pop(); - switch (value.type.kind()) { - case ValueType::kRef: { - auto* result = - Push(ValueType(ValueType::kRef, value.type.ref_index())); - CALL_INTERFACE_IF_REACHABLE(PassThrough, value, result); - break; - } - case ValueType::kOptRef: { - auto* result = - Push(ValueType(ValueType::kRef, value.type.ref_index())); - CALL_INTERFACE_IF_REACHABLE(RefAsNonNull, value, result); - break; - } - case ValueType::kNullRef: - // TODO(7748): Fix this once the standard clears up (see - // https://github.com/WebAssembly/function-references/issues/21). - CALL_INTERFACE_IF_REACHABLE(Unreachable); - EndControl(); - break; - default: - this->error(this->pc_ + 1, - "invalid agrument type to ref.as_non_null"); - break; + len = 1 + iterator.length(); + EndControl(); + break; + } + case kExprReturn: { + if (V8_LIKELY(current_code_reachable_)) { + if (!VALIDATE(TypeCheckReturn())) break; + DoReturn(); + } else { + // We pop all return values from the stack to check their type. + // Since we deal with unreachable code, we do not have to keep the + // values. + int num_returns = static_cast<int>(this->sig_->return_count()); + for (int i = num_returns - 1; i >= 0; --i) { + Pop(i, this->sig_->GetReturn(i)); } - break; - } - case kExprLocalGet: { - LocalIndexImmediate<validate> imm(this, this->pc_); - if (!this->Validate(this->pc_, imm)) break; - auto* value = Push(imm.type); - CALL_INTERFACE_IF_REACHABLE(LocalGet, value, imm); - len = 1 + imm.length; - break; } - case kExprLocalSet: { - 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(LocalSet, value, imm); - len = 1 + imm.length; - break; - } - case kExprLocalTee: { - 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(LocalTee, value, result, imm); - len = 1 + imm.length; - break; - } - case kExprDrop: { - auto value = Pop(); - CALL_INTERFACE_IF_REACHABLE(Drop, value); + + EndControl(); + break; + } + case kExprUnreachable: { + CALL_INTERFACE_IF_REACHABLE(Unreachable); + EndControl(); + break; + } + case kExprI32Const: { + ImmI32Immediate<validate> imm(this, this->pc_); + Value* value = Push(kWasmI32); + CALL_INTERFACE_IF_REACHABLE(I32Const, value, imm.value); + len = 1 + imm.length; + break; + } + case kExprI64Const: { + ImmI64Immediate<validate> imm(this, this->pc_); + Value* value = Push(kWasmI64); + CALL_INTERFACE_IF_REACHABLE(I64Const, value, imm.value); + len = 1 + imm.length; + break; + } + case kExprF32Const: { + ImmF32Immediate<validate> imm(this, this->pc_); + Value* value = Push(kWasmF32); + CALL_INTERFACE_IF_REACHABLE(F32Const, value, imm.value); + len = 1 + imm.length; + break; + } + case kExprF64Const: { + ImmF64Immediate<validate> imm(this, this->pc_); + Value* value = Push(kWasmF64); + CALL_INTERFACE_IF_REACHABLE(F64Const, value, imm.value); + len = 1 + imm.length; + break; + } + case kExprRefNull: { + CHECK_PROTOTYPE_OPCODE(reftypes); + RefNullImmediate<validate> imm(this->enabled_, this, this->pc_); + if (!this->Validate(this->pc_, imm)) break; + Value* value = Push(imm.type); + CALL_INTERFACE_IF_REACHABLE(RefNull, value); + len = 1 + imm.length; + break; + } + case kExprRefIsNull: { + CHECK_PROTOTYPE_OPCODE(reftypes); + Value value = Pop(); + Value* result = Push(kWasmI32); + len = 1; + if (value.type.is_nullable()) { + CALL_INTERFACE_IF_REACHABLE(UnOp, opcode, value, result); break; } - case kExprGlobalGet: { - 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(GlobalGet, result, imm); + if (value.type.is_reference_type()) { + // Due to the check above, we know that the value is not null. + CALL_INTERFACE_IF_REACHABLE(I32Const, result, 0); break; } - case kExprGlobalSet: { - 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); + this->errorf(this->pc_, + "invalid argument type to ref.is_null. Expected " + "reference type, got %s", + value.type.type_name().c_str()); + break; + } + case kExprRefFunc: { + CHECK_PROTOTYPE_OPCODE(reftypes); + FunctionIndexImmediate<validate> imm(this, this->pc_); + if (!this->Validate(this->pc_, imm)) break; + Value* value = Push(ValueType::Ref(kHeapFunc, kNonNullable)); + CALL_INTERFACE_IF_REACHABLE(RefFunc, imm.index, value); + len = 1 + imm.length; + break; + } + case kExprRefAsNonNull: { + CHECK_PROTOTYPE_OPCODE(typed_funcref); + Value value = Pop(); + switch (value.type.kind()) { + case ValueType::kRef: { + Value* result = Push(value.type); + CALL_INTERFACE_IF_REACHABLE(PassThrough, value, result); break; } - auto value = Pop(0, imm.type); - CALL_INTERFACE_IF_REACHABLE(GlobalSet, value, imm); - break; - } - case kExprTableGet: { - CHECK_PROTOTYPE_OPCODE(anyref); - TableIndexImmediate<validate> imm(this, this->pc_); - len = 1 + imm.length; - if (!this->Validate(this->pc_, imm)) break; - DCHECK_NOT_NULL(this->module_); - auto index = Pop(0, kWasmI32); - auto* result = Push(this->module_->tables[imm.index].type); - CALL_INTERFACE_IF_REACHABLE(TableGet, index, result, imm); - break; - } - case kExprTableSet: { - CHECK_PROTOTYPE_OPCODE(anyref); - TableIndexImmediate<validate> imm(this, this->pc_); - len = 1 + imm.length; - if (!this->Validate(this->pc_, imm)) break; - auto value = Pop(1, this->module_->tables[imm.index].type); - auto index = Pop(0, kWasmI32); - CALL_INTERFACE_IF_REACHABLE(TableSet, index, 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"); + case ValueType::kOptRef: { + Value* result = + Push(ValueType::Ref(value.type.heap_type(), kNonNullable)); + CALL_INTERFACE_IF_REACHABLE(RefAsNonNull, value, result); break; } - auto value = Pop(0, kWasmI32); - auto* result = Push(kWasmI32); - CALL_INTERFACE_IF_REACHABLE(MemoryGrow, value, result); - break; + default: + this->error(this->pc_ + 1, + "invalid agrument type to ref.as_non_null"); + 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 kExprLocalGet: { + LocalIndexImmediate<validate> imm(this, this->pc_); + if (!this->Validate(this->pc_, imm)) break; + Value* value = Push(imm.type); + CALL_INTERFACE_IF_REACHABLE(LocalGet, value, imm); + len = 1 + imm.length; + break; + } + case kExprLocalSet: { + LocalIndexImmediate<validate> imm(this, this->pc_); + if (!this->Validate(this->pc_, imm)) break; + Value value = Pop(0, local_type_vec_[imm.index]); + CALL_INTERFACE_IF_REACHABLE(LocalSet, value, imm); + len = 1 + imm.length; + break; + } + case kExprLocalTee: { + LocalIndexImmediate<validate> imm(this, this->pc_); + if (!this->Validate(this->pc_, imm)) break; + Value value = Pop(0, local_type_vec_[imm.index]); + Value* result = Push(value.type); + CALL_INTERFACE_IF_REACHABLE(LocalTee, value, result, imm); + len = 1 + imm.length; + break; + } + case kExprDrop: { + Value value = Pop(); + CALL_INTERFACE_IF_REACHABLE(Drop, value); + break; + } + case kExprGlobalGet: { + GlobalIndexImmediate<validate> imm(this, this->pc_); + len = 1 + imm.length; + if (!this->Validate(this->pc_, imm)) break; + Value* result = Push(imm.type); + CALL_INTERFACE_IF_REACHABLE(GlobalGet, result, imm); + break; + } + case kExprGlobalSet: { + 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 kExprCallFunction: { - CallFunctionImmediate<validate> imm(this, this->pc_); - len = 1 + imm.length; - if (!this->Validate(this->pc_, imm)) break; - auto args = PopArgs(imm.sig); - auto* returns = PushReturns(imm.sig); - CALL_INTERFACE_IF_REACHABLE(CallDirect, imm, args.begin(), returns); + Value value = Pop(0, imm.type); + CALL_INTERFACE_IF_REACHABLE(GlobalSet, value, imm); + break; + } + case kExprTableGet: { + CHECK_PROTOTYPE_OPCODE(reftypes); + TableIndexImmediate<validate> imm(this, this->pc_); + len = 1 + imm.length; + if (!this->Validate(this->pc_, imm)) break; + Value index = Pop(0, kWasmI32); + Value* result = Push(this->module_->tables[imm.index].type); + CALL_INTERFACE_IF_REACHABLE(TableGet, index, result, imm); + break; + } + case kExprTableSet: { + CHECK_PROTOTYPE_OPCODE(reftypes); + TableIndexImmediate<validate> imm(this, this->pc_); + len = 1 + imm.length; + if (!this->Validate(this->pc_, imm)) break; + Value value = Pop(1, this->module_->tables[imm.index].type); + Value index = Pop(0, kWasmI32); + CALL_INTERFACE_IF_REACHABLE(TableSet, index, 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; + if (!VALIDATE(this->module_->origin == kWasmOrigin)) { + this->error("grow_memory is not supported for asmjs modules"); break; } - case kExprCallIndirect: { - CallIndirectImmediate<validate> imm(this->enabled_, this, this->pc_); - len = 1 + imm.length; - if (!this->Validate(this->pc_, imm)) break; - auto index = Pop(0, kWasmI32); - auto args = PopArgs(imm.sig); - auto* returns = PushReturns(imm.sig); - CALL_INTERFACE_IF_REACHABLE(CallIndirect, index, imm, args.begin(), - returns); + Value value = Pop(0, kWasmI32); + Value* result = Push(kWasmI32); + CALL_INTERFACE_IF_REACHABLE(MemoryGrow, value, result); + break; + } + case kExprMemorySize: { + if (!CheckHasMemory()) break; + MemoryIndexImmediate<validate> imm(this, this->pc_); + Value* 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; + ArgVector args = PopArgs(imm.sig); + Value* returns = PushReturns(imm.sig); + CALL_INTERFACE_IF_REACHABLE(CallDirect, imm, args.begin(), returns); + break; + } + case kExprCallIndirect: { + CallIndirectImmediate<validate> imm(this->enabled_, this, this->pc_); + len = 1 + imm.length; + if (!this->Validate(this->pc_, imm)) break; + Value index = Pop(0, kWasmI32); + ArgVector args = PopArgs(imm.sig); + Value* returns = PushReturns(imm.sig); + CALL_INTERFACE_IF_REACHABLE(CallIndirect, index, imm, args.begin(), + returns); + break; + } + case kExprReturnCall: { + CHECK_PROTOTYPE_OPCODE(return_call); + + CallFunctionImmediate<validate> imm(this, this->pc_); + len = 1 + imm.length; + if (!this->Validate(this->pc_, imm)) break; + if (!this->CanReturnCall(imm.sig)) { + OPCODE_ERROR(opcode, "tail call return types mismatch"); break; } - case kExprReturnCall: { - CHECK_PROTOTYPE_OPCODE(return_call); - CallFunctionImmediate<validate> imm(this, this->pc_); - len = 1 + imm.length; - if (!this->Validate(this->pc_, imm)) break; - if (!this->CanReturnCall(imm.sig)) { - OPCODE_ERROR(opcode, "tail call return types mismatch"); - break; - } + ArgVector args = PopArgs(imm.sig); - auto args = PopArgs(imm.sig); - - CALL_INTERFACE_IF_REACHABLE(ReturnCall, imm, args.begin()); - EndControl(); - break; - } - case kExprReturnCallIndirect: { - CHECK_PROTOTYPE_OPCODE(return_call); - CallIndirectImmediate<validate> imm(this->enabled_, this, this->pc_); - len = 1 + imm.length; - if (!this->Validate(this->pc_, imm)) break; - if (!this->CanReturnCall(imm.sig)) { - OPCODE_ERROR(opcode, "tail call return types mismatch"); - break; - } - auto index = Pop(0, kWasmI32); - auto args = PopArgs(imm.sig); - CALL_INTERFACE_IF_REACHABLE(ReturnCallIndirect, index, imm, - args.begin()); - EndControl(); - 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 == kExprTableGrow || opcode == kExprTableSize || - opcode == kExprTableFill) { - CHECK_PROTOTYPE_OPCODE(anyref); - } else if (opcode >= kExprMemoryInit) { - 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); - uint32_t length = 0; - opcode = - this->template read_prefixed_opcode<validate>(this->pc_, &length); - if (!VALIDATE(this->ok())) break; - len += length; - - TRACE_PART(TRACE_INST_FORMAT, startrel(this->pc_), - WasmOpcodes::OpcodeName(opcode)); - len += DecodeSimdOpcode(opcode, length); - break; - } - case kAtomicPrefix: { - CHECK_PROTOTYPE_OPCODE(threads); - 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); + CALL_INTERFACE_IF_REACHABLE(ReturnCall, imm, args.begin()); + EndControl(); + break; + } + case kExprReturnCallIndirect: { + CHECK_PROTOTYPE_OPCODE(return_call); + CallIndirectImmediate<validate> imm(this->enabled_, this, this->pc_); + len = 1 + imm.length; + if (!this->Validate(this->pc_, imm)) break; + if (!this->CanReturnCall(imm.sig)) { + OPCODE_ERROR(opcode, "tail call return types mismatch"); break; } - case kGCPrefix: { - CHECK_PROTOTYPE_OPCODE(gc); - byte gc_index = - this->template read_u8<validate>(this->pc_ + 1, "gc index"); - opcode = static_cast<WasmOpcode>(opcode << 8 | gc_index); - TRACE_PART(TRACE_INST_FORMAT, startrel(this->pc_), - WasmOpcodes::OpcodeName(opcode)); - len = DecodeGCOpcode(opcode); - break; + Value index = Pop(0, kWasmI32); + ArgVector args = PopArgs(imm.sig); + CALL_INTERFACE_IF_REACHABLE(ReturnCallIndirect, index, imm, + args.begin()); + EndControl(); + break; + } + case kNumericPrefix: { + ++len; + byte numeric_index = + this->template read_u8<validate>(this->pc_ + 1, "numeric index"); + WasmOpcode full_opcode = + static_cast<WasmOpcode>(opcode << 8 | numeric_index); + if (full_opcode == kExprTableGrow || full_opcode == kExprTableSize || + full_opcode == kExprTableFill) { + CHECK_PROTOTYPE_OPCODE(reftypes); + } else if (full_opcode >= kExprMemoryInit) { + CHECK_PROTOTYPE_OPCODE(bulk_memory); } + TRACE_PART(TRACE_INST_FORMAT, startrel(this->pc_), + WasmOpcodes::OpcodeName(full_opcode)); + len += DecodeNumericOpcode(full_opcode); + break; + } + case kSimdPrefix: { + CHECK_PROTOTYPE_OPCODE(simd); + uint32_t length = 0; + WasmOpcode full_opcode = + this->template read_prefixed_opcode<validate>(this->pc_, &length); + if (!VALIDATE(this->ok())) break; + len += length; + + TRACE_PART(TRACE_INST_FORMAT, startrel(this->pc_), + WasmOpcodes::OpcodeName(full_opcode)); + len += DecodeSimdOpcode(full_opcode, length); + break; + } + case kAtomicPrefix: { + CHECK_PROTOTYPE_OPCODE(threads); + len++; + byte atomic_index = + this->template read_u8<validate>(this->pc_ + 1, "atomic index"); + WasmOpcode full_opcode = + static_cast<WasmOpcode>(opcode << 8 | atomic_index); + TRACE_PART(TRACE_INST_FORMAT, startrel(this->pc_), + WasmOpcodes::OpcodeName(full_opcode)); + len += DecodeAtomicOpcode(full_opcode); + break; + } + case kGCPrefix: { + CHECK_PROTOTYPE_OPCODE(gc); + byte gc_index = + this->template read_u8<validate>(this->pc_ + 1, "gc index"); + WasmOpcode full_opcode = + static_cast<WasmOpcode>(opcode << 8 | gc_index); + TRACE_PART(TRACE_INST_FORMAT, startrel(this->pc_), + WasmOpcodes::OpcodeName(full_opcode)); + len = DecodeGCOpcode(full_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 && is_asmjs_module(this->module_)) { - const FunctionSig* 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 (is_asmjs_module(this->module_)) { + const FunctionSig* sig = WasmOpcodes::AsmjsSignature(opcode); + if (sig) { + BuildSimpleOperator(opcode, sig); } + } else { + this->error("Invalid opcode"); + return 0; } } + } #if DEBUG - if (FLAG_trace_wasm_decoder) { - TRACE_PART(" "); - for (Control& c : control_) { - switch (c.kind) { - case kControlIf: - TRACE_PART("I"); - break; - case kControlBlock: - TRACE_PART("B"); - break; - case kControlLoop: - TRACE_PART("L"); - break; - case kControlTry: - TRACE_PART("T"); - break; - default: - break; - } - if (c.start_merge.arity) TRACE_PART("%u-", c.start_merge.arity); - TRACE_PART("%u", c.end_merge.arity); - if (!c.reachable()) TRACE_PART("%c", c.unreachable() ? '*' : '#'); + if (FLAG_trace_wasm_decoder) { + TRACE_PART(" "); + for (Control& c : control_) { + switch (c.kind) { + case kControlIf: + TRACE_PART("I"); + break; + case kControlBlock: + TRACE_PART("B"); + break; + case kControlLoop: + TRACE_PART("L"); + break; + case kControlTry: + TRACE_PART("T"); + break; + case kControlIfElse: + case kControlTryCatch: + case kControlLet: // TODO(7748): Implement + break; } - TRACE_PART(" | "); - for (size_t i = 0; i < stack_.size(); ++i) { - auto& val = stack_[i]; - WasmOpcode opcode = static_cast<WasmOpcode>(*val.pc); - if (WasmOpcodes::IsPrefixOpcode(opcode)) { - opcode = this->template read_prefixed_opcode<Decoder::kNoValidate>( - val.pc); + if (c.start_merge.arity) TRACE_PART("%u-", c.start_merge.arity); + TRACE_PART("%u", c.end_merge.arity); + if (!c.reachable()) TRACE_PART("%c", c.unreachable() ? '*' : '#'); + } + TRACE_PART(" | "); + for (size_t i = 0; i < stack_.size(); ++i) { + Value& val = stack_[i]; + WasmOpcode val_opcode = static_cast<WasmOpcode>(*val.pc); + if (WasmOpcodes::IsPrefixOpcode(val_opcode)) { + val_opcode = + this->template read_prefixed_opcode<Decoder::kNoValidate>(val.pc); + } + TRACE_PART(" %c@%d:%s", val.type.short_name(), + static_cast<int>(val.pc - this->start_), + WasmOpcodes::OpcodeName(val_opcode)); + // If the decoder failed, don't try to decode the immediates, as this + // can trigger a DCHECK failure. + if (this->failed()) continue; + switch (val_opcode) { + case kExprI32Const: { + ImmI32Immediate<Decoder::kNoValidate> imm(this, val.pc); + TRACE_PART("[%d]", imm.value); + break; } - TRACE_PART(" %c@%d:%s", val.type.short_name(), - static_cast<int>(val.pc - this->start_), - WasmOpcodes::OpcodeName(opcode)); - // If the decoder failed, don't try to decode the immediates, as this - // can trigger a DCHECK failure. - if (this->failed()) continue; - switch (opcode) { - case kExprI32Const: { - ImmI32Immediate<Decoder::kNoValidate> imm(this, val.pc); - TRACE_PART("[%d]", imm.value); - break; - } - case kExprLocalGet: - case kExprLocalSet: - case kExprLocalTee: { - LocalIndexImmediate<Decoder::kNoValidate> imm(this, val.pc); - TRACE_PART("[%u]", imm.index); - break; - } - case kExprGlobalGet: - case kExprGlobalSet: { - GlobalIndexImmediate<Decoder::kNoValidate> imm(this, val.pc); - TRACE_PART("[%u]", imm.index); - break; - } - default: - break; + case kExprLocalGet: + case kExprLocalSet: + case kExprLocalTee: { + LocalIndexImmediate<Decoder::kNoValidate> imm(this, val.pc); + TRACE_PART("[%u]", imm.index); + break; + } + case kExprGlobalGet: + case kExprGlobalSet: { + GlobalIndexImmediate<Decoder::kNoValidate> imm(this, val.pc); + TRACE_PART("[%u]", imm.index); + break; } + default: + break; } } + } #endif + return len; + } + + using OpcodeHandler = int (*)(WasmFullDecoder*); + + template <size_t idx> + struct GetOpcodeHandlerTableEntry + : public std::integral_constant< + OpcodeHandler, + &WasmFullDecoder::DecodeOp<static_cast<WasmOpcode>(idx)>> {}; + + OpcodeHandler GetOpcodeHandler(uint8_t opcode) { + static constexpr std::array<OpcodeHandler, 256> kOpcodeHandlers = + base::make_array<256, GetOpcodeHandlerTableEntry>(); + return kOpcodeHandlers[opcode]; + } + + void DecodeFunctionBody() { + TRACE("wasm-decode %p...%p (module+%u, %d bytes)\n", this->start(), + this->end(), this->pc_offset(), + static_cast<int>(this->end() - this->start())); + + // Set up initial function block. + { + Control* 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{this->pc_, this->sig_->GetReturn(i)}; + }); + CALL_INTERFACE(StartFunctionBody, c); + } + + // Decode the function body. + while (this->pc_ < this->end_) { + uint8_t first_byte = *this->pc_; + CALL_INTERFACE_IF_REACHABLE(NextInstruction, + static_cast<WasmOpcode>(first_byte)); + OpcodeHandler handler = GetOpcodeHandler(first_byte); + int len = (*handler)(this); this->pc_ += len; - } // end decode loop + } + if (!VALIDATE(this->pc_ == this->end_) && this->ok()) { this->error("Beyond end of code"); } @@ -2728,13 +2925,14 @@ class WasmFullDecoder : public WasmDecoder<validate> { void EndControl() { DCHECK(!control_.empty()); - auto* current = &control_.back(); + Control* current = &control_.back(); stack_.erase(stack_.begin() + current->stack_depth, stack_.end()); CALL_INTERFACE_IF_REACHABLE(EndControl, current); current->reachability = kUnreachable; + current_code_reachable_ = false; } - template<typename func> + template <typename func> void InitMerge(Merge<Value>* merge, uint32_t arity, func get_val) { merge->arity = arity; if (arity == 1) { @@ -2771,7 +2969,16 @@ class WasmFullDecoder : public WasmDecoder<validate> { int count = static_cast<int>(type->field_count()); ArgVector args(count); for (int i = count - 1; i >= 0; i--) { - args[i] = Pop(i, type->field(i)); + args[i] = Pop(i, type->field(i).Unpacked()); + } + return args; + } + + V8_INLINE ArgVector PopArgs(uint32_t base_index, + Vector<ValueType> arg_types) { + ArgVector args(arg_types.size()); + for (int i = static_cast<int>(arg_types.size()) - 1; i >= 0; i--) { + args[i] = Pop(base_index + i, arg_types[i]); } return args; } @@ -2781,10 +2988,12 @@ class WasmFullDecoder : public WasmDecoder<validate> { return sig->return_count() == 0 ? kWasmStmt : sig->GetReturn(); } - Control* PushControl(ControlKind kind) { + Control* PushControl(ControlKind kind, uint32_t locals_count = 0) { Reachability reachability = control_.empty() ? kReachable : control_.back().innerReachability(); - control_.emplace_back(kind, stack_size(), this->pc_, reachability); + control_.emplace_back(kind, locals_count, stack_size(), this->pc_, + reachability); + current_code_reachable_ = this->ok() && reachability == kReachable; return &control_.back(); } @@ -2800,17 +3009,16 @@ class WasmFullDecoder : public WasmDecoder<validate> { control_.pop_back(); // If the parent block was reachable before, but the popped control does not // return to here, this block becomes "spec only reachable". - if (!parent_reached && control_.back().reachable()) { - control_.back().reachability = kSpecOnlyReachable; - } + if (!parent_reached) SetSucceedingCodeDynamicallyUnreachable(); + current_code_reachable_ = control_.back().reachable(); } int DecodeLoadMem(LoadType type, int prefix_len = 0) { if (!CheckHasMemory()) return 0; MemoryAccessImmediate<validate> imm(this, this->pc_ + prefix_len, type.size_log_2()); - auto index = Pop(0, kWasmI32); - auto* result = Push(type.value_type()); + Value index = Pop(0, kWasmI32); + Value* result = Push(type.value_type()); CALL_INTERFACE_IF_REACHABLE(LoadMem, type, imm, index, result); return imm.length; } @@ -2823,8 +3031,8 @@ class WasmFullDecoder : public WasmDecoder<validate> { transform == LoadTransformationKind::kExtend ? 3 : type.size_log_2(); MemoryAccessImmediate<validate> imm(this, this->pc_ + opcode_length, max_alignment); - auto index = Pop(0, kWasmI32); - auto* result = Push(kWasmS128); + Value index = Pop(0, kWasmI32); + Value* result = Push(kWasmS128); CALL_INTERFACE_IF_REACHABLE(LoadTransform, type, transform, imm, index, result); return imm.length; @@ -2834,8 +3042,8 @@ class WasmFullDecoder : public WasmDecoder<validate> { if (!CheckHasMemory()) return 0; MemoryAccessImmediate<validate> imm(this, this->pc_ + prefix_len, store.size_log_2()); - auto value = Pop(1, store.value_type()); - auto index = Pop(0, kWasmI32); + Value value = Pop(1, store.value_type()); + Value index = Pop(0, kWasmI32); CALL_INTERFACE_IF_REACHABLE(StoreMem, store, imm, index, value); return imm.length; } @@ -2850,7 +3058,7 @@ class WasmFullDecoder : public WasmDecoder<validate> { } std::vector<ValueType> InitializeBrTableResultTypes(uint32_t target) { - auto* merge = control_at(target)->br_merge(); + Merge<Value>* merge = control_at(target)->br_merge(); int br_arity = merge->arity; std::vector<ValueType> result(br_arity); for (int i = 0; i < br_arity; ++i) { @@ -2861,7 +3069,7 @@ class WasmFullDecoder : public WasmDecoder<validate> { bool UpdateBrTableResultTypes(std::vector<ValueType>* result_types, uint32_t target, const byte* pos, int index) { - auto* merge = control_at(target)->br_merge(); + Merge<Value>* merge = control_at(target)->br_merge(); int br_arity = merge->arity; // First we check if the arities match. if (br_arity != static_cast<int>(result_types->size())) { @@ -2873,18 +3081,27 @@ class WasmFullDecoder : public WasmDecoder<validate> { } for (int i = 0; i < br_arity; ++i) { - if (this->enabled_.has_anyref()) { + if (this->enabled_.has_reftypes()) { // The expected type is the biggest common sub type of all targets. + ValueType type = (*result_types)[i]; (*result_types)[i] = - ValueType::CommonSubType((*result_types)[i], (*merge)[i].type); + CommonSubtype((*result_types)[i], (*merge)[i].type, this->module_); + if ((*result_types)[i] == kWasmBottom) { + this->errorf(pos, + "inconsistent type in br_table target %u (previous " + "was %s, this one is %s)", + index, type.type_name().c_str(), + (*merge)[i].type.type_name().c_str()); + return false; + } } else { // All target must have the same signature. if ((*result_types)[i] != (*merge)[i].type) { this->errorf(pos, "inconsistent type in br_table target %u (previous " "was %s, this one is %s)", - index, (*result_types)[i].type_name(), - (*merge)[i].type.type_name()); + index, (*result_types)[i].type_name().c_str(), + (*merge)[i].type.type_name().c_str()); return false; } } @@ -2909,10 +3126,11 @@ class WasmFullDecoder : public WasmDecoder<validate> { // Type-check the topmost br_arity values on the stack. for (int i = 0; i < br_arity; ++i) { Value& val = stack_values[i]; - if (!val.type.IsSubTypeOf(result_types[i])) { + if (!IsSubtypeOf(val.type, result_types[i], this->module_)) { this->errorf(this->pc_, "type error in merge[%u] (expected %s, got %s)", i, - result_types[i].type_name(), val.type.type_name()); + result_types[i].type_name().c_str(), + val.type.type_name().c_str()); return false; } } @@ -2928,7 +3146,7 @@ class WasmFullDecoder : public WasmDecoder<validate> { SimdLaneImmediate<validate> imm(this, this->pc_, opcode_length); if (this->Validate(this->pc_, opcode, imm)) { Value inputs[] = {Pop(0, kWasmS128)}; - auto* result = Push(type); + Value* result = Push(type); CALL_INTERFACE_IF_REACHABLE(SimdLaneOp, opcode, imm, ArrayVector(inputs), result); } @@ -2943,7 +3161,7 @@ class WasmFullDecoder : public WasmDecoder<validate> { UnreachableValue(this->pc_)}; inputs[1] = Pop(1, type); inputs[0] = Pop(0, kWasmS128); - auto* result = Push(kWasmS128); + Value* result = Push(kWasmS128); CALL_INTERFACE_IF_REACHABLE(SimdLaneOp, opcode, imm, ArrayVector(inputs), result); } @@ -2953,9 +3171,9 @@ class WasmFullDecoder : public WasmDecoder<validate> { uint32_t Simd8x16ShuffleOp(uint32_t opcode_length) { Simd8x16ShuffleImmediate<validate> imm(this, this->pc_, opcode_length); if (this->Validate(this->pc_, imm)) { - auto input1 = Pop(1, kWasmS128); - auto input0 = Pop(0, kWasmS128); - auto* result = Push(kWasmS128); + Value input1 = Pop(1, kWasmS128); + Value input0 = Pop(0, kWasmS128); + Value* result = Push(kWasmS128); CALL_INTERFACE_IF_REACHABLE(Simd8x16ShuffleOp, imm, input0, input1, result); } @@ -3075,8 +3293,8 @@ class WasmFullDecoder : public WasmDecoder<validate> { this->error("invalid simd opcode"); break; } - auto args = PopArgs(sig); - auto* results = + ArgVector args = PopArgs(sig); + Value* results = sig->return_count() == 0 ? nullptr : Push(GetReturnType(sig)); CALL_INTERFACE_IF_REACHABLE(SimdOp, opcode, VectorOf(args), results); } @@ -3091,29 +3309,66 @@ class WasmFullDecoder : public WasmDecoder<validate> { StructIndexImmediate<validate> imm(this, this->pc_ + len); len += imm.length; if (!this->Validate(this->pc_, imm)) break; - auto args = PopArgs(imm.struct_type); - auto* value = Push(ValueType(ValueType::kRef, imm.index)); + ArgVector args = PopArgs(imm.struct_type); + Value* value = Push( + ValueType::Ref(static_cast<HeapType>(imm.index), kNonNullable)); CALL_INTERFACE_IF_REACHABLE(StructNew, imm, args.begin(), value); break; } case kExprStructGet: { FieldIndexImmediate<validate> field(this, this->pc_ + len); if (!this->Validate(this->pc_ + len, field)) break; + ValueType field_type = + field.struct_index.struct_type->field(field.index); + if (field_type.is_packed()) { + this->error(this->pc_, + "struct.get used with a field of packed type. " + "Use struct.get_s or struct.get_u instead."); + break; + } len += field.length; - auto struct_obj = - Pop(0, ValueType(ValueType::kOptRef, field.struct_index.index)); - auto* value = Push(field.struct_index.struct_type->field(field.index)); - CALL_INTERFACE_IF_REACHABLE(StructGet, struct_obj, field, value); + Value struct_obj = Pop( + 0, ValueType::Ref(static_cast<HeapType>(field.struct_index.index), + kNullable)); + Value* value = Push(field_type); + CALL_INTERFACE_IF_REACHABLE(StructGet, struct_obj, field, true, value); + break; + } + case kExprStructGetU: + case kExprStructGetS: { + FieldIndexImmediate<validate> field(this, this->pc_ + len); + if (!this->Validate(this->pc_ + len, field)) break; + len += field.length; + ValueType field_type = + field.struct_index.struct_type->field(field.index); + if (!field_type.is_packed()) { + this->errorf(this->pc_, + "%s is only valid for packed struct fields. " + "Use struct.get instead.", + WasmOpcodes::OpcodeName(opcode)); + break; + } + Value struct_obj = Pop( + 0, ValueType::Ref(static_cast<HeapType>(field.struct_index.index), + kNullable)); + Value* value = Push(field_type.Unpacked()); + CALL_INTERFACE_IF_REACHABLE(StructGet, struct_obj, field, + opcode == kExprStructGetS, value); break; } case kExprStructSet: { FieldIndexImmediate<validate> field(this, this->pc_ + len); if (!this->Validate(this->pc_ + len, field)) break; len += field.length; - auto field_value = Pop( - 0, ValueType(field.struct_index.struct_type->field(field.index))); - auto struct_obj = - Pop(0, ValueType(ValueType::kOptRef, field.struct_index.index)); + const StructType* struct_type = field.struct_index.struct_type; + if (!struct_type->mutability(field.index)) { + this->error(this->pc_, "setting immutable struct field"); + break; + } + Value field_value = Pop(1, struct_type->field(field.index).Unpacked()); + Value struct_obj = Pop( + 0, ValueType::Ref(static_cast<HeapType>(field.struct_index.index), + kNullable)); CALL_INTERFACE_IF_REACHABLE(StructSet, struct_obj, field, field_value); break; } @@ -3121,31 +3376,66 @@ class WasmFullDecoder : public WasmDecoder<validate> { ArrayIndexImmediate<validate> imm(this, this->pc_ + len); len += imm.length; if (!this->Validate(this->pc_, imm)) break; - auto length = Pop(0, kWasmI32); - auto initial_value = Pop(0, imm.array_type->element_type()); - auto* value = Push(ValueType(ValueType::kRef, imm.index)); + Value length = Pop(1, kWasmI32); + Value initial_value = Pop(0, imm.array_type->element_type().Unpacked()); + Value* value = Push( + ValueType::Ref(static_cast<HeapType>(imm.index), kNonNullable)); CALL_INTERFACE_IF_REACHABLE(ArrayNew, imm, length, initial_value, value); break; } + case kExprArrayGetS: + case kExprArrayGetU: { + ArrayIndexImmediate<validate> imm(this, this->pc_ + len); + len += imm.length; + if (!this->Validate(this->pc_ + len, imm)) break; + if (!imm.array_type->element_type().is_packed()) { + this->errorf(this->pc_, + "%s is only valid for packed arrays. " + "Use or array.get instead.", + WasmOpcodes::OpcodeName(opcode)); + break; + } + Value index = Pop(1, kWasmI32); + Value array_obj = + Pop(0, ValueType::Ref(static_cast<HeapType>(imm.index), kNullable)); + Value* value = Push(imm.array_type->element_type().Unpacked()); + // TODO(7748): Optimize this when array_obj is non-nullable ref. + CALL_INTERFACE_IF_REACHABLE(ArrayGet, array_obj, imm, index, + opcode == kExprArrayGetS, value); + break; + } case kExprArrayGet: { ArrayIndexImmediate<validate> imm(this, this->pc_ + len); len += imm.length; if (!this->Validate(this->pc_ + len, imm)) break; - auto index = Pop(0, kWasmI32); - auto array_obj = Pop(0, ValueType(ValueType::kOptRef, imm.index)); - auto* value = Push(imm.array_type->element_type()); + if (imm.array_type->element_type().is_packed()) { + this->error(this->pc_, + "array.get used with a field of packed type. " + "Use array.get_s or array.get_u instead."); + break; + } + Value index = Pop(1, kWasmI32); + Value array_obj = + Pop(0, ValueType::Ref(static_cast<HeapType>(imm.index), kNullable)); + Value* value = Push(imm.array_type->element_type()); // TODO(7748): Optimize this when array_obj is non-nullable ref. - CALL_INTERFACE_IF_REACHABLE(ArrayGet, array_obj, imm, index, value); + CALL_INTERFACE_IF_REACHABLE(ArrayGet, array_obj, imm, index, true, + value); break; } case kExprArraySet: { ArrayIndexImmediate<validate> imm(this, this->pc_ + len); len += imm.length; if (!this->Validate(this->pc_ + len, imm)) break; - auto value = Pop(0, imm.array_type->element_type()); - auto index = Pop(0, kWasmI32); - auto array_obj = Pop(0, ValueType(ValueType::kOptRef, imm.index)); + if (!imm.array_type->mutability()) { + this->error(this->pc_, "setting element of immutable array"); + break; + } + Value value = Pop(2, imm.array_type->element_type().Unpacked()); + Value index = Pop(1, kWasmI32); + Value array_obj = + Pop(0, ValueType::Ref(static_cast<HeapType>(imm.index), kNullable)); // TODO(7748): Optimize this when array_obj is non-nullable ref. CALL_INTERFACE_IF_REACHABLE(ArraySet, array_obj, imm, index, value); break; @@ -3154,11 +3444,22 @@ class WasmFullDecoder : public WasmDecoder<validate> { ArrayIndexImmediate<validate> imm(this, this->pc_ + len); len += imm.length; if (!this->Validate(this->pc_ + len, imm)) break; - auto array_obj = Pop(0, ValueType(ValueType::kOptRef, imm.index)); - auto* value = Push(kWasmI32); + Value array_obj = + Pop(0, ValueType::Ref(static_cast<HeapType>(imm.index), kNullable)); + Value* value = Push(kWasmI32); CALL_INTERFACE_IF_REACHABLE(ArrayLen, array_obj, value); break; } + case kExprRttCanon: { + // TODO(7748): Introduce HeapTypeImmediate and use that here. + TypeIndexImmediate<validate> imm(this, this->pc_ + len); + len += imm.length; + if (!this->Validate(this->pc_ + len, imm)) break; + Value* value = + Push(ValueType::Rtt(static_cast<HeapType>(imm.index), 1)); + CALL_INTERFACE_IF_REACHABLE(RttCanon, imm, value); + break; + } default: this->error("invalid gc opcode"); return 0; @@ -3209,8 +3510,8 @@ class WasmFullDecoder : public WasmDecoder<validate> { MemoryAccessImmediate<validate> imm( this, this->pc_ + 1, ElementSizeLog2Of(memtype.representation())); len += imm.length; - auto args = PopArgs(sig); - auto result = ret_type == kWasmStmt ? nullptr : Push(GetReturnType(sig)); + ArgVector args = PopArgs(sig); + Value* result = ret_type == kWasmStmt ? nullptr : Push(GetReturnType(sig)); CALL_INTERFACE_IF_REACHABLE(AtomicOp, opcode, VectorOf(args), imm, result); return len; } @@ -3234,9 +3535,9 @@ class WasmFullDecoder : public WasmDecoder<validate> { 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)); + Value size = Pop(2, sig->GetParam(2)); + Value src = Pop(1, sig->GetParam(1)); + Value dst = Pop(0, sig->GetParam(0)); CALL_INTERFACE_IF_REACHABLE(MemoryInit, imm, dst, src, size); break; } @@ -3251,9 +3552,9 @@ class WasmFullDecoder : public WasmDecoder<validate> { MemoryCopyImmediate<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)); + Value size = Pop(2, sig->GetParam(2)); + Value src = Pop(1, sig->GetParam(1)); + Value dst = Pop(0, sig->GetParam(0)); CALL_INTERFACE_IF_REACHABLE(MemoryCopy, imm, dst, src, size); break; } @@ -3261,9 +3562,9 @@ class WasmFullDecoder : public WasmDecoder<validate> { MemoryIndexImmediate<validate> imm(this, this->pc_ + 1); if (!this->Validate(this->pc_ + 1, 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)); + Value size = Pop(2, sig->GetParam(2)); + Value value = Pop(1, sig->GetParam(1)); + Value dst = Pop(0, sig->GetParam(0)); CALL_INTERFACE_IF_REACHABLE(MemoryFill, imm, dst, value, size); break; } @@ -3271,7 +3572,7 @@ class WasmFullDecoder : public WasmDecoder<validate> { TableInitImmediate<validate> imm(this, this->pc_); if (!this->Validate(imm)) break; len += imm.length; - auto args = PopArgs(sig); + ArgVector args = PopArgs(sig); CALL_INTERFACE_IF_REACHABLE(TableInit, imm, VectorOf(args)); break; } @@ -3286,7 +3587,7 @@ class WasmFullDecoder : public WasmDecoder<validate> { TableCopyImmediate<validate> imm(this, this->pc_); if (!this->Validate(imm)) break; len += imm.length; - auto args = PopArgs(sig); + ArgVector args = PopArgs(sig); CALL_INTERFACE_IF_REACHABLE(TableCopy, imm, VectorOf(args)); break; } @@ -3294,9 +3595,9 @@ class WasmFullDecoder : public WasmDecoder<validate> { TableIndexImmediate<validate> imm(this, this->pc_ + 1); if (!this->Validate(this->pc_, imm)) break; len += imm.length; - auto delta = Pop(1, sig->GetParam(1)); - auto value = Pop(0, this->module_->tables[imm.index].type); - auto* result = Push(kWasmI32); + Value delta = Pop(1, sig->GetParam(1)); + Value value = Pop(0, this->module_->tables[imm.index].type); + Value* result = Push(kWasmI32); CALL_INTERFACE_IF_REACHABLE(TableGrow, imm, value, delta, result); break; } @@ -3304,7 +3605,7 @@ class WasmFullDecoder : public WasmDecoder<validate> { TableIndexImmediate<validate> imm(this, this->pc_ + 1); if (!this->Validate(this->pc_, imm)) break; len += imm.length; - auto* result = Push(kWasmI32); + Value* result = Push(kWasmI32); CALL_INTERFACE_IF_REACHABLE(TableSize, imm, result); break; } @@ -3312,9 +3613,9 @@ class WasmFullDecoder : public WasmDecoder<validate> { TableIndexImmediate<validate> imm(this, this->pc_ + 1); if (!this->Validate(this->pc_, imm)) break; len += imm.length; - auto count = Pop(2, sig->GetParam(2)); - auto value = Pop(1, this->module_->tables[imm.index].type); - auto start = Pop(0, sig->GetParam(0)); + Value count = Pop(2, sig->GetParam(2)); + Value value = Pop(1, this->module_->tables[imm.index].type); + Value start = Pop(0, sig->GetParam(0)); CALL_INTERFACE_IF_REACHABLE(TableFill, imm, start, value, count); break; } @@ -3330,6 +3631,9 @@ class WasmFullDecoder : public WasmDecoder<validate> { void DoReturn() { size_t return_count = this->sig_->return_count(); + if (return_count > 1) { + this->detected_->Add(kFeature_mv); + } DCHECK_GE(stack_.size(), return_count); Vector<Value> return_values = return_count == 0 @@ -3370,12 +3674,13 @@ class WasmFullDecoder : public WasmDecoder<validate> { } V8_INLINE Value Pop(int index, ValueType expected) { - auto val = Pop(); - if (!VALIDATE(val.type.IsSubTypeOf(expected) || val.type == kWasmBottom || - expected == kWasmBottom)) { + Value val = Pop(); + if (!VALIDATE(IsSubtypeOf(val.type, expected, this->module_) || + val.type == kWasmBottom || expected == kWasmBottom)) { this->errorf(val.pc, "%s[%d] expected type %s, found %s of type %s", - SafeOpcodeNameAt(this->pc_), index, expected.type_name(), - SafeOpcodeNameAt(val.pc), val.type.type_name()); + SafeOpcodeNameAt(this->pc_), index, + expected.type_name().c_str(), SafeOpcodeNameAt(val.pc), + val.type.type_name().c_str()); } return val; } @@ -3391,7 +3696,7 @@ class WasmFullDecoder : public WasmDecoder<validate> { } return UnreachableValue(this->pc_); } - auto val = stack_.back(); + Value val = stack_.back(); stack_.pop_back(); return val; } @@ -3435,9 +3740,10 @@ class WasmFullDecoder : public WasmDecoder<validate> { for (uint32_t i = 0; i < merge->arity; ++i) { Value& val = stack_values[i]; Value& old = (*merge)[i]; - if (!val.type.IsSubTypeOf(old.type)) { + if (!IsSubtypeOf(val.type, old.type, this->module_)) { this->errorf(this->pc_, "type error in merge[%u] (expected %s, got %s)", - i, old.type.type_name(), val.type.type_name()); + i, old.type.type_name().c_str(), + val.type.type_name().c_str()); return false; } } @@ -3452,9 +3758,10 @@ class WasmFullDecoder : public WasmDecoder<validate> { for (uint32_t i = 0; i < c->start_merge.arity; ++i) { Value& start = c->start_merge[i]; Value& end = c->end_merge[i]; - if (!start.type.IsSubTypeOf(end.type)) { + if (!IsSubtypeOf(start.type, end.type, this->module_)) { this->errorf(this->pc_, "type error in merge[%u] (expected %s, got %s)", - i, end.type.type_name(), start.type.type_name()); + i, end.type.type_name().c_str(), + start.type.type_name().c_str()); return false; } } @@ -3463,7 +3770,7 @@ class WasmFullDecoder : public WasmDecoder<validate> { } bool TypeCheckFallThru() { - static_assert(validate, "Call this function only whithin VALIDATE"); + static_assert(validate, "Call this function only within VALIDATE"); Control& c = control_.back(); if (V8_LIKELY(c.reachable())) { uint32_t expected = c.end_merge.arity; @@ -3554,12 +3861,12 @@ class WasmFullDecoder : public WasmDecoder<validate> { // This line requires num_returns > 0. Value* stack_values = &*(stack_.end() - num_returns); for (int i = 0; i < num_returns; ++i) { - auto& val = stack_values[i]; + Value& val = stack_values[i]; ValueType expected_type = this->sig_->GetReturn(i); - if (!val.type.IsSubTypeOf(expected_type)) { - this->errorf(this->pc_, - "type error in return[%u] (expected %s, got %s)", i, - expected_type.type_name(), val.type.type_name()); + if (!IsSubtypeOf(val.type, expected_type, this->module_)) { + this->errorf( + this->pc_, "type error in return[%u] (expected %s, got %s)", i, + expected_type.type_name().c_str(), val.type.type_name().c_str()); return false; } } @@ -3568,14 +3875,13 @@ class WasmFullDecoder : public WasmDecoder<validate> { void onFirstError() override { this->end_ = this->pc_; // Terminate decoding loop. + this->current_code_reachable_ = false; TRACE(" !%s\n", this->error_.message().c_str()); CALL_INTERFACE(OnFirstError); } void BuildSimplePrototypeOperator(WasmOpcode opcode) { - if (opcode == kExprRefIsNull) { - RET_ON_PROTOTYPE_OPCODE(anyref); - } else if (opcode == kExprRefEq) { + if (opcode == kExprRefEq) { RET_ON_PROTOTYPE_OPCODE(gc); } const FunctionSig* sig = WasmOpcodes::Signature(opcode); @@ -3583,39 +3889,28 @@ class WasmFullDecoder : public WasmDecoder<validate> { } void BuildSimpleOperator(WasmOpcode opcode, const 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, val, ret); - break; - } - case 2: { - auto rval = Pop(1, sig->GetParam(1)); - auto lval = Pop(0, sig->GetParam(0)); - auto* ret = - sig->return_count() == 0 ? nullptr : Push(sig->GetReturn(0)); - CALL_INTERFACE_IF_REACHABLE(BinOp, opcode, lval, rval, ret); - break; - } - default: - UNREACHABLE(); + DCHECK_GE(1, sig->return_count()); + ValueType ret = sig->return_count() == 0 ? kWasmStmt : sig->GetReturn(0); + if (sig->parameter_count() == 1) { + BuildSimpleOperator(opcode, ret, sig->GetParam(0)); + } else { + DCHECK_EQ(2, sig->parameter_count()); + BuildSimpleOperator(opcode, ret, sig->GetParam(0), sig->GetParam(1)); } } 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); + Value val = Pop(0, arg_type); + Value* 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); + Value rval = Pop(1, rhs_type); + Value lval = Pop(0, lhs_type); + Value* ret = return_type == kWasmStmt ? nullptr : Push(return_type); CALL_INTERFACE_IF_REACHABLE(BinOp, opcode, lval, rval, ret); } |