| // Copyright 2017 the V8 project authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef V8_WASM_FUNCTION_BODY_DECODER_IMPL_H_ |
| #define V8_WASM_FUNCTION_BODY_DECODER_IMPL_H_ |
| |
| // Do only include this header for implementing new Interface of the |
| // WasmFullDecoder. |
| |
| #include "src/bit-vector.h" |
| #include "src/wasm/decoder.h" |
| #include "src/wasm/function-body-decoder.h" |
| #include "src/wasm/wasm-limits.h" |
| #include "src/wasm/wasm-module.h" |
| #include "src/wasm/wasm-opcodes.h" |
| |
| namespace v8 { |
| namespace internal { |
| namespace wasm { |
| |
| struct WasmGlobal; |
| struct WasmException; |
| |
| #if DEBUG |
| #define TRACE(...) \ |
| do { \ |
| if (FLAG_trace_wasm_decoder) PrintF(__VA_ARGS__); \ |
| } while (false) |
| #else |
| #define TRACE(...) |
| #endif |
| |
| // Return the evaluation of `condition` if validate==true, DCHECK that it's |
| // true and always return true otherwise. |
| #define VALIDATE(condition) \ |
| (validate ? (condition) : [&] { \ |
| DCHECK(condition); \ |
| return true; \ |
| }()) |
| |
| #define CHECK_PROTOTYPE_OPCODE(flag) \ |
| DCHECK(!this->module_ || !this->module_->is_asm_js()); \ |
| if (!FLAG_experimental_wasm_##flag) { \ |
| this->error("Invalid opcode (enable with --experimental-wasm-" #flag ")"); \ |
| break; \ |
| } |
| |
| #define OPCODE_ERROR(opcode, message) \ |
| (this->errorf(this->pc_, "%s: %s", WasmOpcodes::OpcodeName(opcode), \ |
| (message))) |
| |
| #define ATOMIC_OP_LIST(V) \ |
| V(I32AtomicLoad, Uint32) \ |
| V(I32AtomicAdd, Uint32) \ |
| V(I32AtomicSub, Uint32) \ |
| V(I32AtomicAnd, Uint32) \ |
| V(I32AtomicOr, Uint32) \ |
| V(I32AtomicXor, Uint32) \ |
| V(I32AtomicExchange, Uint32) \ |
| V(I32AtomicLoad8U, Uint8) \ |
| V(I32AtomicAdd8U, Uint8) \ |
| V(I32AtomicSub8U, Uint8) \ |
| V(I32AtomicAnd8U, Uint8) \ |
| V(I32AtomicOr8U, Uint8) \ |
| V(I32AtomicXor8U, Uint8) \ |
| V(I32AtomicExchange8U, Uint8) \ |
| V(I32AtomicLoad16U, Uint16) \ |
| V(I32AtomicAdd16U, Uint16) \ |
| V(I32AtomicSub16U, Uint16) \ |
| V(I32AtomicAnd16U, Uint16) \ |
| V(I32AtomicOr16U, Uint16) \ |
| V(I32AtomicXor16U, Uint16) \ |
| V(I32AtomicExchange16U, Uint16) \ |
| V(I32AtomicCompareExchange, Uint32) \ |
| V(I32AtomicCompareExchange8U, Uint8) \ |
| V(I32AtomicCompareExchange16U, Uint16) |
| |
| #define ATOMIC_STORE_OP_LIST(V) \ |
| V(I32AtomicStore, Uint32) \ |
| V(I32AtomicStore8U, Uint8) \ |
| V(I32AtomicStore16U, Uint16) |
| |
| template <typename T> |
| Vector<T> vec2vec(std::vector<T>& vec) { |
| return Vector<T>(vec.data(), vec.size()); |
| } |
| |
| // Helpers for decoding different kinds of operands which follow bytecodes. |
| template <bool validate> |
| struct LocalIndexOperand { |
| uint32_t index; |
| ValueType type = kWasmStmt; |
| unsigned length; |
| |
| inline LocalIndexOperand(Decoder* decoder, const byte* pc) { |
| index = decoder->read_u32v<validate>(pc + 1, &length, "local index"); |
| } |
| }; |
| |
| template <bool validate> |
| struct ExceptionIndexOperand { |
| uint32_t index; |
| const WasmException* exception = nullptr; |
| unsigned length; |
| |
| inline ExceptionIndexOperand(Decoder* decoder, const byte* pc) { |
| index = decoder->read_u32v<validate>(pc + 1, &length, "exception index"); |
| } |
| }; |
| |
| template <bool validate> |
| struct ImmI32Operand { |
| int32_t value; |
| unsigned length; |
| inline ImmI32Operand(Decoder* decoder, const byte* pc) { |
| value = decoder->read_i32v<validate>(pc + 1, &length, "immi32"); |
| } |
| }; |
| |
| template <bool validate> |
| struct ImmI64Operand { |
| int64_t value; |
| unsigned length; |
| inline ImmI64Operand(Decoder* decoder, const byte* pc) { |
| value = decoder->read_i64v<validate>(pc + 1, &length, "immi64"); |
| } |
| }; |
| |
| template <bool validate> |
| struct ImmF32Operand { |
| float value; |
| unsigned length = 4; |
| inline ImmF32Operand(Decoder* decoder, const byte* pc) { |
| // Avoid bit_cast because it might not preserve the signalling bit of a NaN. |
| uint32_t tmp = decoder->read_u32<validate>(pc + 1, "immf32"); |
| memcpy(&value, &tmp, sizeof(value)); |
| } |
| }; |
| |
| template <bool validate> |
| struct ImmF64Operand { |
| double value; |
| unsigned length = 8; |
| inline ImmF64Operand(Decoder* decoder, const byte* pc) { |
| // Avoid bit_cast because it might not preserve the signalling bit of a NaN. |
| uint64_t tmp = decoder->read_u64<validate>(pc + 1, "immf64"); |
| memcpy(&value, &tmp, sizeof(value)); |
| } |
| }; |
| |
| template <bool validate> |
| struct GlobalIndexOperand { |
| uint32_t index; |
| ValueType type = kWasmStmt; |
| const WasmGlobal* global = nullptr; |
| unsigned length; |
| |
| inline GlobalIndexOperand(Decoder* decoder, const byte* pc) { |
| index = decoder->read_u32v<validate>(pc + 1, &length, "global index"); |
| } |
| }; |
| |
| template <bool validate> |
| struct BlockTypeOperand { |
| uint32_t arity = 0; |
| const byte* types = nullptr; // pointer to encoded types for the block. |
| unsigned length = 1; |
| |
| inline BlockTypeOperand(Decoder* decoder, const byte* pc) { |
| uint8_t val = decoder->read_u8<validate>(pc + 1, "block type"); |
| ValueType type = kWasmStmt; |
| if (decode_local_type(val, &type)) { |
| arity = type == kWasmStmt ? 0 : 1; |
| types = pc + 1; |
| } else { |
| // Handle multi-value blocks. |
| if (!VALIDATE(FLAG_experimental_wasm_mv)) { |
| decoder->error(pc + 1, "invalid block arity > 1"); |
| return; |
| } |
| if (!VALIDATE(val == kMultivalBlock)) { |
| decoder->error(pc + 1, "invalid block type"); |
| return; |
| } |
| // Decode and check the types vector of the block. |
| unsigned len = 0; |
| uint32_t count = |
| decoder->read_u32v<validate>(pc + 2, &len, "block arity"); |
| // {count} is encoded as {arity-2}, so that a {0} count here corresponds |
| // to a block with 2 values. This makes invalid/redundant encodings |
| // impossible. |
| arity = count + 2; |
| length = 1 + len + arity; |
| types = pc + 1 + 1 + len; |
| |
| for (uint32_t i = 0; i < arity; i++) { |
| uint32_t offset = 1 + 1 + len + i; |
| val = decoder->read_u8<validate>(pc + offset, "block type"); |
| decode_local_type(val, &type); |
| if (!VALIDATE(type != kWasmStmt)) { |
| decoder->error(pc + offset, "invalid block type"); |
| return; |
| } |
| } |
| } |
| } |
| |
| // Decode a byte representing a local type. Return {false} if the encoded |
| // byte was invalid or {kMultivalBlock}. |
| inline bool decode_local_type(uint8_t val, ValueType* result) { |
| switch (static_cast<ValueTypeCode>(val)) { |
| case kLocalVoid: |
| *result = kWasmStmt; |
| return true; |
| case kLocalI32: |
| *result = kWasmI32; |
| return true; |
| case kLocalI64: |
| *result = kWasmI64; |
| return true; |
| case kLocalF32: |
| *result = kWasmF32; |
| return true; |
| case kLocalF64: |
| *result = kWasmF64; |
| return true; |
| case kLocalS128: |
| *result = kWasmS128; |
| return true; |
| default: |
| *result = kWasmStmt; |
| return false; |
| } |
| } |
| |
| ValueType read_entry(unsigned index) { |
| DCHECK_LT(index, arity); |
| ValueType result; |
| bool success = decode_local_type(types[index], &result); |
| DCHECK(success); |
| USE(success); |
| return result; |
| } |
| }; |
| |
| template <bool validate> |
| struct BreakDepthOperand { |
| uint32_t depth; |
| unsigned length; |
| inline BreakDepthOperand(Decoder* decoder, const byte* pc) { |
| depth = decoder->read_u32v<validate>(pc + 1, &length, "break depth"); |
| } |
| }; |
| |
| template <bool validate> |
| struct CallIndirectOperand { |
| uint32_t table_index; |
| uint32_t index; |
| FunctionSig* sig = nullptr; |
| unsigned length; |
| inline CallIndirectOperand(Decoder* decoder, const byte* pc) { |
| unsigned len = 0; |
| index = decoder->read_u32v<validate>(pc + 1, &len, "signature index"); |
| table_index = decoder->read_u8<validate>(pc + 1 + len, "table index"); |
| if (!VALIDATE(table_index == 0)) { |
| decoder->errorf(pc + 1 + len, "expected table index 0, found %u", |
| table_index); |
| } |
| length = 1 + len; |
| } |
| }; |
| |
| template <bool validate> |
| struct CallFunctionOperand { |
| uint32_t index; |
| FunctionSig* sig = nullptr; |
| unsigned length; |
| inline CallFunctionOperand(Decoder* decoder, const byte* pc) { |
| index = decoder->read_u32v<validate>(pc + 1, &length, "function index"); |
| } |
| }; |
| |
| template <bool validate> |
| struct MemoryIndexOperand { |
| uint32_t index; |
| unsigned length = 1; |
| inline MemoryIndexOperand(Decoder* decoder, const byte* pc) { |
| index = decoder->read_u8<validate>(pc + 1, "memory index"); |
| if (!VALIDATE(index == 0)) { |
| decoder->errorf(pc + 1, "expected memory index 0, found %u", index); |
| } |
| } |
| }; |
| |
| template <bool validate> |
| struct BranchTableOperand { |
| uint32_t table_count; |
| const byte* start; |
| const byte* table; |
| inline BranchTableOperand(Decoder* decoder, const byte* pc) { |
| DCHECK_EQ(kExprBrTable, decoder->read_u8<validate>(pc, "opcode")); |
| start = pc + 1; |
| unsigned len = 0; |
| table_count = decoder->read_u32v<validate>(pc + 1, &len, "table count"); |
| table = pc + 1 + len; |
| } |
| }; |
| |
| // A helper to iterate over a branch table. |
| template <bool validate> |
| class BranchTableIterator { |
| public: |
| unsigned cur_index() { return index_; } |
| bool has_next() { return decoder_->ok() && index_ <= table_count_; } |
| uint32_t next() { |
| DCHECK(has_next()); |
| index_++; |
| unsigned length; |
| uint32_t result = |
| decoder_->read_u32v<validate>(pc_, &length, "branch table entry"); |
| pc_ += length; |
| return result; |
| } |
| // length, including the length of the {BranchTableOperand}, but not the |
| // opcode. |
| unsigned length() { |
| while (has_next()) next(); |
| return static_cast<unsigned>(pc_ - start_); |
| } |
| const byte* pc() { return pc_; } |
| |
| BranchTableIterator(Decoder* decoder, |
| const BranchTableOperand<validate>& operand) |
| : decoder_(decoder), |
| start_(operand.start), |
| pc_(operand.table), |
| index_(0), |
| table_count_(operand.table_count) {} |
| |
| private: |
| Decoder* decoder_; |
| const byte* start_; |
| const byte* pc_; |
| uint32_t index_; // the current index. |
| uint32_t table_count_; // the count of entries, not including default. |
| }; |
| |
| template <bool validate> |
| struct MemoryAccessOperand { |
| uint32_t alignment; |
| uint32_t offset; |
| unsigned length; |
| inline MemoryAccessOperand(Decoder* decoder, const byte* pc, |
| uint32_t max_alignment) { |
| unsigned alignment_length; |
| alignment = |
| decoder->read_u32v<validate>(pc + 1, &alignment_length, "alignment"); |
| if (!VALIDATE(alignment <= max_alignment)) { |
| decoder->errorf(pc + 1, |
| "invalid alignment; expected maximum alignment is %u, " |
| "actual alignment is %u", |
| max_alignment, alignment); |
| } |
| unsigned offset_length; |
| offset = decoder->read_u32v<validate>(pc + 1 + alignment_length, |
| &offset_length, "offset"); |
| length = alignment_length + offset_length; |
| } |
| }; |
| |
| // Operand for SIMD lane operations. |
| template <bool validate> |
| struct SimdLaneOperand { |
| uint8_t lane; |
| unsigned length = 1; |
| |
| inline SimdLaneOperand(Decoder* decoder, const byte* pc) { |
| lane = decoder->read_u8<validate>(pc + 2, "lane"); |
| } |
| }; |
| |
| // Operand for SIMD shift operations. |
| template <bool validate> |
| struct SimdShiftOperand { |
| uint8_t shift; |
| unsigned length = 1; |
| |
| inline SimdShiftOperand(Decoder* decoder, const byte* pc) { |
| shift = decoder->read_u8<validate>(pc + 2, "shift"); |
| } |
| }; |
| |
| // Operand for SIMD S8x16 shuffle operations. |
| template <bool validate> |
| struct Simd8x16ShuffleOperand { |
| uint8_t shuffle[kSimd128Size]; |
| |
| inline Simd8x16ShuffleOperand(Decoder* decoder, const byte* pc) { |
| for (uint32_t i = 0; i < kSimd128Size; ++i) { |
| shuffle[i] = decoder->read_u8<validate>(pc + 2 + i, "shuffle"); |
| } |
| } |
| }; |
| |
| // An entry on the value stack. |
| struct ValueBase { |
| const byte* pc; |
| ValueType type; |
| |
| // Named constructors. |
| static ValueBase Unreachable(const byte* pc) { return {pc, kWasmVar}; } |
| |
| static ValueBase New(const byte* pc, ValueType type) { return {pc, type}; } |
| }; |
| |
| template <typename Value> |
| struct Merge { |
| uint32_t arity; |
| union { |
| Value* array; |
| Value first; |
| } vals; // Either multiple values or a single value. |
| |
| Value& operator[](uint32_t i) { |
| DCHECK_GT(arity, i); |
| return arity == 1 ? vals.first : vals.array[i]; |
| } |
| }; |
| |
| enum ControlKind { |
| kControlIf, |
| kControlIfElse, |
| kControlBlock, |
| kControlLoop, |
| kControlTry, |
| kControlTryCatch |
| }; |
| |
| // An entry on the control stack (i.e. if, block, loop, or try). |
| template <typename Value> |
| struct ControlBase { |
| const byte* pc; |
| ControlKind kind; |
| uint32_t stack_depth; // stack height at the beginning of the construct. |
| bool unreachable; // The current block has been ended. |
| |
| // Values merged into the end of this control construct. |
| Merge<Value> merge; |
| |
| inline bool is_if() const { return is_onearmed_if() || is_if_else(); } |
| inline bool is_onearmed_if() const { return kind == kControlIf; } |
| inline bool is_if_else() const { return kind == kControlIfElse; } |
| inline bool is_block() const { return kind == kControlBlock; } |
| inline bool is_loop() const { return kind == kControlLoop; } |
| inline bool is_try() const { return is_incomplete_try() || is_try_catch(); } |
| inline bool is_incomplete_try() const { return kind == kControlTry; } |
| inline bool is_try_catch() const { return kind == kControlTryCatch; } |
| |
| // Named constructors. |
| static ControlBase Block(const byte* pc, size_t stack_depth) { |
| return {pc, kControlBlock, static_cast<uint32_t>(stack_depth), false, {}}; |
| } |
| |
| static ControlBase If(const byte* pc, size_t stack_depth) { |
| return {pc, kControlIf, static_cast<uint32_t>(stack_depth), false, {}}; |
| } |
| |
| static ControlBase Loop(const byte* pc, size_t stack_depth) { |
| return {pc, kControlLoop, static_cast<uint32_t>(stack_depth), false, {}}; |
| } |
| |
| static ControlBase Try(const byte* pc, size_t stack_depth) { |
| return {pc, kControlTry, static_cast<uint32_t>(stack_depth), false, {}}; |
| } |
| }; |
| |
| #define CONCRETE_NAMED_CONSTRUCTOR(concrete_type, abstract_type, name) \ |
| template <typename... Args> \ |
| static concrete_type name(Args&&... args) { \ |
| concrete_type val; \ |
| static_cast<abstract_type&>(val) = \ |
| abstract_type::name(std::forward<Args>(args)...); \ |
| return val; \ |
| } |
| |
| // Provide the default named constructors, which default-initialize the |
| // ConcreteType and the initialize the fields of ValueBase correctly. |
| // Use like this: |
| // struct Value : public ValueWithNamedConstructors<Value> { int new_field; }; |
| template <typename ConcreteType> |
| struct ValueWithNamedConstructors : public ValueBase { |
| // Named constructors. |
| CONCRETE_NAMED_CONSTRUCTOR(ConcreteType, ValueBase, Unreachable) |
| CONCRETE_NAMED_CONSTRUCTOR(ConcreteType, ValueBase, New) |
| }; |
| |
| // Provide the default named constructors, which default-initialize the |
| // ConcreteType and the initialize the fields of ControlBase correctly. |
| // Use like this: |
| // struct Control : public ControlWithNamedConstructors<Control, Value> { |
| // int my_uninitialized_field; |
| // char* other_field = nullptr; |
| // }; |
| template <typename ConcreteType, typename Value> |
| struct ControlWithNamedConstructors : public ControlBase<Value> { |
| // Named constructors. |
| CONCRETE_NAMED_CONSTRUCTOR(ConcreteType, ControlBase<Value>, Block) |
| CONCRETE_NAMED_CONSTRUCTOR(ConcreteType, ControlBase<Value>, If) |
| CONCRETE_NAMED_CONSTRUCTOR(ConcreteType, ControlBase<Value>, Loop) |
| CONCRETE_NAMED_CONSTRUCTOR(ConcreteType, ControlBase<Value>, Try) |
| }; |
| |
| // This is the list of callback functions that an interface for the |
| // WasmFullDecoder should implement. |
| // F(Name, args...) |
| #define INTERFACE_FUNCTIONS(F) \ |
| /* General: */ \ |
| F(StartFunction) \ |
| F(StartFunctionBody, Control* block) \ |
| F(FinishFunction) \ |
| /* Control: */ \ |
| F(Block, Control* block) \ |
| F(Loop, Control* block) \ |
| F(Try, Control* block) \ |
| F(If, const Value& cond, Control* if_block) \ |
| F(FallThruTo, Control* c) \ |
| F(PopControl, const Control& block) \ |
| F(EndControl, Control* block) \ |
| /* Instructions: */ \ |
| F(UnOp, WasmOpcode opcode, FunctionSig*, const Value& value, Value* result) \ |
| F(BinOp, WasmOpcode opcode, FunctionSig*, const Value& lhs, \ |
| const Value& rhs, Value* result) \ |
| F(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(DoReturn, Vector<Value> values) \ |
| F(GetLocal, Value* result, const LocalIndexOperand<validate>& operand) \ |
| F(SetLocal, const Value& value, const LocalIndexOperand<validate>& operand) \ |
| F(TeeLocal, const Value& value, Value* result, \ |
| const LocalIndexOperand<validate>& operand) \ |
| F(GetGlobal, Value* result, const GlobalIndexOperand<validate>& operand) \ |
| F(SetGlobal, const Value& value, \ |
| const GlobalIndexOperand<validate>& operand) \ |
| F(Unreachable) \ |
| F(Select, const Value& cond, const Value& fval, const Value& tval, \ |
| Value* result) \ |
| F(BreakTo, uint32_t depth) \ |
| F(BrIf, const Value& cond, uint32_t depth) \ |
| F(BrTable, const BranchTableOperand<validate>& operand, const Value& key) \ |
| F(Else, Control* if_block) \ |
| F(LoadMem, ValueType type, MachineType mem_type, \ |
| const MemoryAccessOperand<validate>& operand, const Value& index, \ |
| Value* result) \ |
| F(StoreMem, ValueType type, MachineType mem_type, \ |
| const MemoryAccessOperand<validate>& operand, const Value& index, \ |
| const Value& value) \ |
| F(CurrentMemoryPages, Value* result) \ |
| F(GrowMemory, const Value& value, Value* result) \ |
| F(CallDirect, const CallFunctionOperand<validate>& operand, \ |
| const Value args[], Value returns[]) \ |
| F(CallIndirect, const Value& index, \ |
| const CallIndirectOperand<validate>& operand, const Value args[], \ |
| Value returns[]) \ |
| F(SimdOp, WasmOpcode opcode, Vector<Value> args, Value* result) \ |
| F(SimdLaneOp, WasmOpcode opcode, const SimdLaneOperand<validate>& operand, \ |
| const Vector<Value> inputs, Value* result) \ |
| F(SimdShiftOp, WasmOpcode opcode, const SimdShiftOperand<validate>& operand, \ |
| const Value& input, Value* result) \ |
| F(Simd8x16ShuffleOp, const Simd8x16ShuffleOperand<validate>& operand, \ |
| const Value& input0, const Value& input1, Value* result) \ |
| F(Throw, const ExceptionIndexOperand<validate>&, Control* block, \ |
| const Vector<Value>& args) \ |
| F(CatchException, const ExceptionIndexOperand<validate>& operand, \ |
| Control* block, void** caught_values) \ |
| F(SetCaughtValue, void* caught_values, Value* value, size_t index) \ |
| F(AtomicOp, WasmOpcode opcode, Vector<Value> args, \ |
| const MemoryAccessOperand<validate>& operand, Value* result) |
| |
| // Generic Wasm bytecode decoder with utilities for decoding operands, |
| // lengths, etc. |
| template <bool validate> |
| class WasmDecoder : public Decoder { |
| public: |
| WasmDecoder(const WasmModule* module, FunctionSig* sig, const byte* start, |
| const byte* end, uint32_t buffer_offset = 0) |
| : Decoder(start, end, buffer_offset), |
| module_(module), |
| sig_(sig), |
| local_types_(nullptr) {} |
| const WasmModule* module_; |
| FunctionSig* sig_; |
| |
| ZoneVector<ValueType>* local_types_; |
| |
| uint32_t total_locals() const { |
| return local_types_ == nullptr |
| ? 0 |
| : static_cast<uint32_t>(local_types_->size()); |
| } |
| |
| static bool DecodeLocals(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()); |
| } |
| // Decode local declarations, if any. |
| uint32_t entries = decoder->consume_u32v("local decls count"); |
| if (decoder->failed()) return false; |
| |
| TRACE("local decls count: %u\n", entries); |
| while (entries-- > 0 && decoder->ok() && decoder->more()) { |
| uint32_t count = decoder->consume_u32v("local count"); |
| if (decoder->failed()) return false; |
| |
| if ((count + type_list->size()) > kV8MaxWasmFunctionLocals) { |
| decoder->error(decoder->pc() - 1, "local count too large"); |
| return false; |
| } |
| byte code = decoder->consume_u8("local type"); |
| if (decoder->failed()) return false; |
| |
| ValueType type; |
| switch (code) { |
| case kLocalI32: |
| type = kWasmI32; |
| break; |
| case kLocalI64: |
| type = kWasmI64; |
| break; |
| case kLocalF32: |
| type = kWasmF32; |
| break; |
| case kLocalF64: |
| type = kWasmF64; |
| break; |
| case kLocalS128: |
| if (FLAG_experimental_wasm_simd) { |
| type = kWasmS128; |
| break; |
| } |
| // else fall through to default. |
| default: |
| decoder->error(decoder->pc() - 1, "invalid local type"); |
| return false; |
| } |
| type_list->insert(type_list->end(), count, type); |
| } |
| DCHECK(decoder->ok()); |
| return true; |
| } |
| |
| static BitVector* AnalyzeLoopAssignment(Decoder* decoder, const byte* pc, |
| uint32_t locals_count, Zone* zone) { |
| if (pc >= decoder->end()) return nullptr; |
| if (*pc != kExprLoop) return nullptr; |
| |
| // The number of locals_count is augmented by 2 so that 'locals_count - 2' |
| // can be used to track mem_size, and 'locals_count - 1' to track mem_start. |
| BitVector* assigned = new (zone) BitVector(locals_count, zone); |
| int depth = 0; |
| // Iteratively process all AST nodes nested inside the loop. |
| while (pc < decoder->end() && decoder->ok()) { |
| WasmOpcode opcode = static_cast<WasmOpcode>(*pc); |
| unsigned length = 1; |
| switch (opcode) { |
| case kExprLoop: |
| case kExprIf: |
| case kExprBlock: |
| case kExprTry: |
| length = OpcodeLength(decoder, pc); |
| depth++; |
| break; |
| case kExprSetLocal: // fallthru |
| case kExprTeeLocal: { |
| LocalIndexOperand<validate> operand(decoder, pc); |
| if (assigned->length() > 0 && |
| operand.index < static_cast<uint32_t>(assigned->length())) { |
| // Unverified code might have an out-of-bounds index. |
| assigned->Add(operand.index); |
| } |
| length = 1 + operand.length; |
| break; |
| } |
| case kExprGrowMemory: |
| case kExprCallFunction: |
| case kExprCallIndirect: |
| // Add mem_size and mem_start to the assigned set. |
| assigned->Add(locals_count - 2); // mem_size |
| assigned->Add(locals_count - 1); // mem_start |
| length = OpcodeLength(decoder, pc); |
| break; |
| case kExprEnd: |
| depth--; |
| break; |
| default: |
| length = OpcodeLength(decoder, pc); |
| break; |
| } |
| if (depth <= 0) break; |
| pc += length; |
| } |
| return decoder->ok() ? assigned : nullptr; |
| } |
| |
| inline bool Validate(const byte* pc, LocalIndexOperand<validate>& operand) { |
| if (!VALIDATE(operand.index < total_locals())) { |
| errorf(pc + 1, "invalid local index: %u", operand.index); |
| return false; |
| } |
| operand.type = local_types_ ? local_types_->at(operand.index) : kWasmStmt; |
| return true; |
| } |
| |
| inline bool Validate(const byte* pc, |
| ExceptionIndexOperand<validate>& operand) { |
| if (!VALIDATE(module_ != nullptr && |
| operand.index < module_->exceptions.size())) { |
| errorf(pc + 1, "Invalid exception index: %u", operand.index); |
| return false; |
| } |
| operand.exception = &module_->exceptions[operand.index]; |
| return true; |
| } |
| |
| inline bool Validate(const byte* pc, GlobalIndexOperand<validate>& operand) { |
| if (!VALIDATE(module_ != nullptr && |
| operand.index < module_->globals.size())) { |
| errorf(pc + 1, "invalid global index: %u", operand.index); |
| return false; |
| } |
| operand.global = &module_->globals[operand.index]; |
| operand.type = operand.global->type; |
| return true; |
| } |
| |
| inline bool Complete(const byte* pc, CallFunctionOperand<validate>& operand) { |
| if (!VALIDATE(module_ != nullptr && |
| operand.index < module_->functions.size())) { |
| return false; |
| } |
| operand.sig = module_->functions[operand.index].sig; |
| return true; |
| } |
| |
| inline bool Validate(const byte* pc, CallFunctionOperand<validate>& operand) { |
| if (Complete(pc, operand)) { |
| return true; |
| } |
| errorf(pc + 1, "invalid function index: %u", operand.index); |
| return false; |
| } |
| |
| inline bool Complete(const byte* pc, CallIndirectOperand<validate>& operand) { |
| if (!VALIDATE(module_ != nullptr && |
| operand.index < module_->signatures.size())) { |
| return false; |
| } |
| operand.sig = module_->signatures[operand.index]; |
| return true; |
| } |
| |
| inline bool Validate(const byte* pc, CallIndirectOperand<validate>& operand) { |
| if (!VALIDATE(module_ != nullptr && !module_->function_tables.empty())) { |
| error("function table has to exist to execute call_indirect"); |
| return false; |
| } |
| if (!Complete(pc, operand)) { |
| errorf(pc + 1, "invalid signature index: #%u", operand.index); |
| return false; |
| } |
| return true; |
| } |
| |
| inline bool Validate(const byte* pc, BreakDepthOperand<validate>& operand, |
| size_t control_depth) { |
| if (!VALIDATE(operand.depth < control_depth)) { |
| errorf(pc + 1, "invalid break depth: %u", operand.depth); |
| return false; |
| } |
| return true; |
| } |
| |
| bool Validate(const byte* pc, BranchTableOperand<validate>& operand, |
| size_t block_depth) { |
| if (!VALIDATE(operand.table_count < kV8MaxWasmFunctionSize)) { |
| errorf(pc + 1, "invalid table count (> max function size): %u", |
| operand.table_count); |
| return false; |
| } |
| return checkAvailable(operand.table_count); |
| } |
| |
| inline bool Validate(const byte* pc, WasmOpcode opcode, |
| SimdLaneOperand<validate>& operand) { |
| uint8_t num_lanes = 0; |
| switch (opcode) { |
| case kExprF32x4ExtractLane: |
| case kExprF32x4ReplaceLane: |
| case kExprI32x4ExtractLane: |
| case kExprI32x4ReplaceLane: |
| num_lanes = 4; |
| break; |
| case kExprI16x8ExtractLane: |
| case kExprI16x8ReplaceLane: |
| num_lanes = 8; |
| break; |
| case kExprI8x16ExtractLane: |
| case kExprI8x16ReplaceLane: |
| num_lanes = 16; |
| break; |
| default: |
| UNREACHABLE(); |
| break; |
| } |
| if (!VALIDATE(operand.lane >= 0 && operand.lane < num_lanes)) { |
| error(pc_ + 2, "invalid lane index"); |
| return false; |
| } else { |
| return true; |
| } |
| } |
| |
| inline bool Validate(const byte* pc, WasmOpcode opcode, |
| SimdShiftOperand<validate>& operand) { |
| uint8_t max_shift = 0; |
| switch (opcode) { |
| case kExprI32x4Shl: |
| case kExprI32x4ShrS: |
| case kExprI32x4ShrU: |
| max_shift = 32; |
| break; |
| case kExprI16x8Shl: |
| case kExprI16x8ShrS: |
| case kExprI16x8ShrU: |
| max_shift = 16; |
| break; |
| case kExprI8x16Shl: |
| case kExprI8x16ShrS: |
| case kExprI8x16ShrU: |
| max_shift = 8; |
| break; |
| default: |
| UNREACHABLE(); |
| break; |
| } |
| if (!VALIDATE(operand.shift >= 0 && operand.shift < max_shift)) { |
| error(pc_ + 2, "invalid shift amount"); |
| return false; |
| } else { |
| return true; |
| } |
| } |
| |
| inline bool Validate(const byte* pc, |
| Simd8x16ShuffleOperand<validate>& operand) { |
| uint8_t max_lane = 0; |
| for (uint32_t i = 0; i < kSimd128Size; ++i) |
| max_lane = std::max(max_lane, operand.shuffle[i]); |
| // Shuffle indices must be in [0..31] for a 16 lane shuffle. |
| if (!VALIDATE(max_lane <= 2 * kSimd128Size)) { |
| error(pc_ + 2, "invalid shuffle mask"); |
| return false; |
| } |
| return true; |
| } |
| |
| static unsigned OpcodeLength(Decoder* decoder, const byte* pc) { |
| WasmOpcode opcode = static_cast<WasmOpcode>(*pc); |
| switch (opcode) { |
| #define DECLARE_OPCODE_CASE(name, opcode, sig) case kExpr##name: |
| FOREACH_LOAD_MEM_OPCODE(DECLARE_OPCODE_CASE) |
| FOREACH_STORE_MEM_OPCODE(DECLARE_OPCODE_CASE) |
| #undef DECLARE_OPCODE_CASE |
| { |
| MemoryAccessOperand<validate> operand(decoder, pc, UINT32_MAX); |
| return 1 + operand.length; |
| } |
| case kExprBr: |
| case kExprBrIf: { |
| BreakDepthOperand<validate> operand(decoder, pc); |
| return 1 + operand.length; |
| } |
| case kExprSetGlobal: |
| case kExprGetGlobal: { |
| GlobalIndexOperand<validate> operand(decoder, pc); |
| return 1 + operand.length; |
| } |
| |
| case kExprCallFunction: { |
| CallFunctionOperand<validate> operand(decoder, pc); |
| return 1 + operand.length; |
| } |
| case kExprCallIndirect: { |
| CallIndirectOperand<validate> operand(decoder, pc); |
| return 1 + operand.length; |
| } |
| |
| case kExprTry: |
| case kExprIf: // fall through |
| case kExprLoop: |
| case kExprBlock: { |
| BlockTypeOperand<validate> operand(decoder, pc); |
| return 1 + operand.length; |
| } |
| |
| case kExprThrow: |
| case kExprCatch: { |
| ExceptionIndexOperand<validate> operand(decoder, pc); |
| return 1 + operand.length; |
| } |
| |
| case kExprSetLocal: |
| case kExprTeeLocal: |
| case kExprGetLocal: { |
| LocalIndexOperand<validate> operand(decoder, pc); |
| return 1 + operand.length; |
| } |
| case kExprBrTable: { |
| BranchTableOperand<validate> operand(decoder, pc); |
| BranchTableIterator<validate> iterator(decoder, operand); |
| return 1 + iterator.length(); |
| } |
| case kExprI32Const: { |
| ImmI32Operand<validate> operand(decoder, pc); |
| return 1 + operand.length; |
| } |
| case kExprI64Const: { |
| ImmI64Operand<validate> operand(decoder, pc); |
| return 1 + operand.length; |
| } |
| case kExprGrowMemory: |
| case kExprMemorySize: { |
| MemoryIndexOperand<validate> operand(decoder, pc); |
| return 1 + operand.length; |
| } |
| case kExprF32Const: |
| return 5; |
| case kExprF64Const: |
| return 9; |
| case kSimdPrefix: { |
| byte simd_index = decoder->read_u8<validate>(pc + 1, "simd_index"); |
| WasmOpcode opcode = |
| static_cast<WasmOpcode>(kSimdPrefix << 8 | simd_index); |
| switch (opcode) { |
| #define DECLARE_OPCODE_CASE(name, opcode, sig) case kExpr##name: |
| FOREACH_SIMD_0_OPERAND_OPCODE(DECLARE_OPCODE_CASE) |
| #undef DECLARE_OPCODE_CASE |
| return 2; |
| #define DECLARE_OPCODE_CASE(name, opcode, sig) case kExpr##name: |
| FOREACH_SIMD_1_OPERAND_OPCODE(DECLARE_OPCODE_CASE) |
| #undef DECLARE_OPCODE_CASE |
| return 3; |
| #define DECLARE_OPCODE_CASE(name, opcode, sig) case kExpr##name: |
| FOREACH_SIMD_MEM_OPCODE(DECLARE_OPCODE_CASE) |
| #undef DECLARE_OPCODE_CASE |
| { |
| MemoryAccessOperand<validate> operand(decoder, pc + 1, UINT32_MAX); |
| return 2 + operand.length; |
| } |
| // Shuffles require a byte per lane, or 16 immediate bytes. |
| case kExprS8x16Shuffle: |
| return 2 + kSimd128Size; |
| default: |
| decoder->error(pc, "invalid SIMD opcode"); |
| return 2; |
| } |
| } |
| default: |
| return 1; |
| } |
| } |
| |
| std::pair<uint32_t, uint32_t> StackEffect(const byte* pc) { |
| WasmOpcode opcode = static_cast<WasmOpcode>(*pc); |
| // Handle "simple" opcodes with a fixed signature first. |
| FunctionSig* sig = WasmOpcodes::Signature(opcode); |
| if (!sig) sig = WasmOpcodes::AsmjsSignature(opcode); |
| if (sig) return {sig->parameter_count(), sig->return_count()}; |
| if (WasmOpcodes::IsPrefixOpcode(opcode)) { |
| opcode = static_cast<WasmOpcode>(opcode << 8 | *(pc + 1)); |
| } |
| |
| #define DECLARE_OPCODE_CASE(name, opcode, sig) case kExpr##name: |
| // clang-format off |
| switch (opcode) { |
| case kExprSelect: |
| return {3, 1}; |
| case kExprS128StoreMem: |
| FOREACH_STORE_MEM_OPCODE(DECLARE_OPCODE_CASE) |
| return {2, 0}; |
| case kExprS128LoadMem: |
| FOREACH_LOAD_MEM_OPCODE(DECLARE_OPCODE_CASE) |
| case kExprTeeLocal: |
| case kExprGrowMemory: |
| return {1, 1}; |
| case kExprSetLocal: |
| case kExprSetGlobal: |
| case kExprDrop: |
| case kExprBrIf: |
| case kExprBrTable: |
| case kExprIf: |
| return {1, 0}; |
| case kExprGetLocal: |
| case kExprGetGlobal: |
| case kExprI32Const: |
| case kExprI64Const: |
| case kExprF32Const: |
| case kExprF64Const: |
| case kExprMemorySize: |
| return {0, 1}; |
| case kExprCallFunction: { |
| CallFunctionOperand<validate> operand(this, pc); |
| CHECK(Complete(pc, operand)); |
| return {operand.sig->parameter_count(), operand.sig->return_count()}; |
| } |
| case kExprCallIndirect: { |
| CallIndirectOperand<validate> operand(this, pc); |
| CHECK(Complete(pc, operand)); |
| // Indirect calls pop an additional argument for the table index. |
| return {operand.sig->parameter_count() + 1, |
| operand.sig->return_count()}; |
| } |
| case kExprBr: |
| case kExprBlock: |
| case kExprLoop: |
| case kExprEnd: |
| case kExprElse: |
| case kExprNop: |
| case kExprReturn: |
| case kExprUnreachable: |
| return {0, 0}; |
| default: |
| V8_Fatal(__FILE__, __LINE__, "unimplemented opcode: %x (%s)", opcode, |
| WasmOpcodes::OpcodeName(opcode)); |
| return {0, 0}; |
| } |
| #undef DECLARE_OPCODE_CASE |
| // clang-format on |
| } |
| }; |
| |
| template <bool validate, typename Interface> |
| class WasmFullDecoder : public WasmDecoder<validate> { |
| using Value = typename Interface::Value; |
| using Control = typename Interface::Control; |
| using MergeValues = Merge<Value>; |
| |
| // All Value and Control types should be trivially copyable for |
| // performance. We push and pop them, and store them in local variables. |
| static_assert(IS_TRIVIALLY_COPYABLE(Value), |
| "all Value<...> types should be trivially copyable"); |
| static_assert(IS_TRIVIALLY_COPYABLE(Control), |
| "all Control<...> types should be trivially copyable"); |
| |
| public: |
| template <typename... InterfaceArgs> |
| WasmFullDecoder(Zone* zone, const wasm::WasmModule* module, |
| const FunctionBody& body, InterfaceArgs&&... interface_args) |
| : WasmDecoder<validate>(module, body.sig, body.start, body.end, |
| body.offset), |
| zone_(zone), |
| interface_(std::forward<InterfaceArgs>(interface_args)...), |
| local_type_vec_(zone), |
| stack_(zone), |
| control_(zone), |
| last_end_found_(false) { |
| this->local_types_ = &local_type_vec_; |
| } |
| |
| Interface& interface() { return interface_; } |
| |
| bool Decode() { |
| DCHECK(stack_.empty()); |
| DCHECK(control_.empty()); |
| |
| if (FLAG_wasm_code_fuzzer_gen_test) { |
| PrintRawWasmCode(this->start_, this->end_); |
| } |
| base::ElapsedTimer decode_timer; |
| if (FLAG_trace_wasm_decode_time) { |
| decode_timer.Start(); |
| } |
| |
| if (this->end_ < this->pc_) { |
| this->error("function body end < start"); |
| return false; |
| } |
| |
| DCHECK_EQ(0, this->local_types_->size()); |
| WasmDecoder<validate>::DecodeLocals(this, this->sig_, this->local_types_); |
| interface_.StartFunction(this); |
| DecodeFunctionBody(); |
| if (!this->failed()) interface_.FinishFunction(this); |
| |
| if (this->failed()) return this->TraceFailed(); |
| |
| if (!control_.empty()) { |
| // Generate a better error message whether the unterminated control |
| // structure is the function body block or an innner structure. |
| if (control_.size() > 1) { |
| this->error(control_.back().pc, "unterminated control structure"); |
| } else { |
| this->error("function body must end with \"end\" opcode"); |
| } |
| return TraceFailed(); |
| } |
| |
| if (!last_end_found_) { |
| this->error("function body must end with \"end\" opcode"); |
| return false; |
| } |
| |
| if (FLAG_trace_wasm_decode_time) { |
| double ms = decode_timer.Elapsed().InMillisecondsF(); |
| PrintF("wasm-decode %s (%0.3f ms)\n\n", this->ok() ? "ok" : "failed", ms); |
| } else { |
| TRACE("wasm-decode %s\n\n", this->ok() ? "ok" : "failed"); |
| } |
| |
| return true; |
| } |
| |
| bool TraceFailed() { |
| TRACE("wasm-error module+%-6d func+%d: %s\n\n", this->error_offset_, |
| this->GetBufferRelativeOffset(this->error_offset_), |
| this->error_msg_.c_str()); |
| return false; |
| } |
| |
| const char* SafeOpcodeNameAt(const byte* pc) { |
| if (pc >= this->end_) return "<end>"; |
| return WasmOpcodes::OpcodeName(static_cast<WasmOpcode>(*pc)); |
| } |
| |
| inline Zone* zone() const { return zone_; } |
| |
| inline uint32_t NumLocals() { |
| return static_cast<uint32_t>(local_type_vec_.size()); |
| } |
| |
| inline ValueType GetLocalType(uint32_t index) { |
| return local_type_vec_[index]; |
| } |
| |
| inline wasm::WasmCodePosition position() { |
| int offset = static_cast<int>(this->pc_ - this->start_); |
| DCHECK_EQ(this->pc_ - this->start_, offset); // overflows cannot happen |
| return offset; |
| } |
| |
| inline uint32_t control_depth() const { |
| return static_cast<uint32_t>(control_.size()); |
| } |
| |
| inline Control* control_at(uint32_t depth) { |
| DCHECK_GT(control_.size(), depth); |
| return &control_[control_.size() - depth - 1]; |
| } |
| |
| inline uint32_t stack_size() const { |
| return static_cast<uint32_t>(stack_.size()); |
| } |
| |
| inline Value* stack_value(uint32_t depth) { |
| DCHECK_GT(stack_.size(), depth); |
| return &stack_[stack_.size() - depth - 1]; |
| } |
| |
| inline Value& GetMergeValueFromStack(Control* c, uint32_t i) { |
| DCHECK_GT(c->merge.arity, i); |
| DCHECK_GE(stack_.size(), c->stack_depth + c->merge.arity); |
| return stack_[stack_.size() - c->merge.arity + i]; |
| } |
| |
| private: |
| static constexpr size_t kErrorMsgSize = 128; |
| |
| Zone* zone_; |
| |
| Interface interface_; |
| |
| ZoneVector<ValueType> local_type_vec_; // types of local variables. |
| ZoneVector<Value> stack_; // stack of values. |
| ZoneVector<Control> control_; // stack of blocks, loops, and ifs. |
| bool last_end_found_; |
| |
| bool CheckHasMemory() { |
| if (!VALIDATE(this->module_->has_memory)) { |
| this->error(this->pc_ - 1, "memory instruction with no memory"); |
| return false; |
| } |
| return true; |
| } |
| |
| bool CheckHasSharedMemory() { |
| if (!VALIDATE(this->module_->has_shared_memory)) { |
| this->error(this->pc_ - 1, "Atomic opcodes used without shared memory"); |
| return false; |
| } |
| return true; |
| } |
| |
| // Decodes the body of a function. |
| void DecodeFunctionBody() { |
| TRACE("wasm-decode %p...%p (module+%u, %d bytes)\n", |
| reinterpret_cast<const void*>(this->start()), |
| reinterpret_cast<const void*>(this->end()), this->pc_offset(), |
| static_cast<int>(this->end() - this->start())); |
| |
| // Set up initial function block. |
| { |
| auto* c = PushBlock(); |
| c->merge.arity = static_cast<uint32_t>(this->sig_->return_count()); |
| |
| if (c->merge.arity == 1) { |
| c->merge.vals.first = Value::New(this->pc_, this->sig_->GetReturn(0)); |
| } else if (c->merge.arity > 1) { |
| c->merge.vals.array = zone_->NewArray<Value>(c->merge.arity); |
| for (unsigned i = 0; i < c->merge.arity; i++) { |
| c->merge.vals.array[i] = |
| Value::New(this->pc_, this->sig_->GetReturn(i)); |
| } |
| } |
| interface_.StartFunctionBody(this, c); |
| } |
| |
| while (this->pc_ < this->end_) { // decoding loop. |
| unsigned len = 1; |
| WasmOpcode opcode = static_cast<WasmOpcode>(*this->pc_); |
| #if DEBUG |
| if (FLAG_trace_wasm_decoder && !WasmOpcodes::IsPrefixOpcode(opcode)) { |
| TRACE(" @%-8d #%-20s|", startrel(this->pc_), |
| WasmOpcodes::OpcodeName(opcode)); |
| } |
| #endif |
| |
| FunctionSig* sig = WasmOpcodes::Signature(opcode); |
| if (sig) { |
| BuildSimpleOperator(opcode, sig); |
| } else { |
| // Complex bytecode. |
| switch (opcode) { |
| case kExprNop: |
| break; |
| case kExprBlock: { |
| BlockTypeOperand<validate> operand(this, this->pc_); |
| auto* block = PushBlock(); |
| SetBlockType(block, operand); |
| len = 1 + operand.length; |
| interface_.Block(this, block); |
| break; |
| } |
| case kExprRethrow: { |
| // TODO(kschimpf): Implement. |
| CHECK_PROTOTYPE_OPCODE(eh); |
| OPCODE_ERROR(opcode, "not implemented yet"); |
| break; |
| } |
| case kExprThrow: { |
| CHECK_PROTOTYPE_OPCODE(eh); |
| ExceptionIndexOperand<true> operand(this, this->pc_); |
| len = 1 + operand.length; |
| if (!this->Validate(this->pc_, operand)) break; |
| std::vector<Value> args; |
| PopArgs(operand.exception->ToFunctionSig(), &args); |
| interface_.Throw(this, operand, &control_.back(), vec2vec(args)); |
| break; |
| } |
| case kExprTry: { |
| CHECK_PROTOTYPE_OPCODE(eh); |
| BlockTypeOperand<validate> operand(this, this->pc_); |
| auto* try_block = PushTry(); |
| SetBlockType(try_block, operand); |
| len = 1 + operand.length; |
| interface_.Try(this, try_block); |
| break; |
| } |
| case kExprCatch: { |
| // TODO(kschimpf): Fix to use type signature of exception. |
| CHECK_PROTOTYPE_OPCODE(eh); |
| ExceptionIndexOperand<true> operand(this, this->pc_); |
| len = 1 + operand.length; |
| |
| if (!this->Validate(this->pc_, operand)) break; |
| |
| if (!VALIDATE(!control_.empty())) { |
| this->error("catch does not match any try"); |
| break; |
| } |
| |
| Control* c = &control_.back(); |
| if (!VALIDATE(c->is_try())) { |
| this->error("catch does not match any try"); |
| break; |
| } |
| |
| if (!VALIDATE(c->is_incomplete_try())) { |
| OPCODE_ERROR(opcode, "multiple catch blocks not implemented"); |
| break; |
| } |
| c->kind = kControlTryCatch; |
| FallThruTo(c); |
| stack_.resize(c->stack_depth); |
| void* caught_values = nullptr; |
| interface_.CatchException(this, operand, c, &caught_values); |
| const WasmExceptionSig* sig = operand.exception->sig; |
| for (size_t i = 0, e = sig->parameter_count(); i < e; ++i) { |
| auto* value = Push(sig->GetParam(i)); |
| interface_.SetCaughtValue(this, caught_values, value, i); |
| } |
| break; |
| } |
| case kExprCatchAll: { |
| // TODO(kschimpf): Implement. |
| CHECK_PROTOTYPE_OPCODE(eh); |
| OPCODE_ERROR(opcode, "not implemented yet"); |
| break; |
| } |
| case kExprLoop: { |
| BlockTypeOperand<validate> operand(this, this->pc_); |
| // The continue environment is the inner environment. |
| auto* block = PushLoop(); |
| SetBlockType(&control_.back(), operand); |
| len = 1 + operand.length; |
| interface_.Loop(this, block); |
| break; |
| } |
| case kExprIf: { |
| // Condition on top of stack. Split environments for branches. |
| BlockTypeOperand<validate> operand(this, this->pc_); |
| auto cond = Pop(0, kWasmI32); |
| auto* if_block = PushIf(); |
| SetBlockType(if_block, operand); |
| interface_.If(this, cond, if_block); |
| len = 1 + operand.length; |
| 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; |
| } |
| c->kind = kControlIfElse; |
| FallThruTo(c); |
| stack_.resize(c->stack_depth); |
| interface_.Else(this, c); |
| break; |
| } |
| case kExprEnd: { |
| if (!VALIDATE(!control_.empty())) { |
| this->error("end does not match any if, try, or block"); |
| return; |
| } |
| Control* c = &control_.back(); |
| if (c->is_loop()) { |
| // A loop just leaves the values on the stack. |
| TypeCheckFallThru(c); |
| PopControl(c); |
| break; |
| } |
| if (c->is_onearmed_if()) { |
| // End the true branch of a one-armed if. |
| if (!VALIDATE(c->unreachable || |
| stack_.size() == c->stack_depth)) { |
| this->error("end of if expected empty stack"); |
| stack_.resize(c->stack_depth); |
| } |
| if (!VALIDATE(c->merge.arity == 0)) { |
| this->error("non-void one-armed if"); |
| } |
| } else if (!VALIDATE(!c->is_incomplete_try())) { |
| this->error(this->pc_, "missing catch in try"); |
| break; |
| } |
| FallThruTo(c); |
| PushEndValues(c); |
| |
| if (control_.size() == 1) { |
| // If at the last (implicit) control, check we are at end. |
| if (!VALIDATE(this->pc_ + 1 == this->end_)) { |
| this->error(this->pc_ + 1, "trailing code after function end"); |
| break; |
| } |
| last_end_found_ = true; |
| // The result of the block is the return value. |
| TRACE(" @%-8d #xx:%-20s|", startrel(this->pc_), |
| "(implicit) return"); |
| DoReturn(); |
| TRACE("\n"); |
| } |
| |
| PopControl(c); |
| break; |
| } |
| case kExprSelect: { |
| auto cond = Pop(2, kWasmI32); |
| auto fval = Pop(); |
| auto tval = Pop(0, fval.type); |
| auto* result = Push(tval.type == kWasmVar ? fval.type : tval.type); |
| interface_.Select(this, cond, fval, tval, result); |
| break; |
| } |
| case kExprBr: { |
| BreakDepthOperand<validate> operand(this, this->pc_); |
| if (this->Validate(this->pc_, operand, control_.size()) && |
| TypeCheckBreak(operand.depth)) { |
| interface_.BreakTo(this, operand.depth); |
| } |
| len = 1 + operand.length; |
| EndControl(); |
| break; |
| } |
| case kExprBrIf: { |
| BreakDepthOperand<validate> operand(this, this->pc_); |
| auto cond = Pop(0, kWasmI32); |
| if (this->Validate(this->pc_, operand, control_.size()) && |
| TypeCheckBreak(operand.depth)) { |
| interface_.BrIf(this, cond, operand.depth); |
| } |
| len = 1 + operand.length; |
| break; |
| } |
| case kExprBrTable: { |
| BranchTableOperand<validate> operand(this, this->pc_); |
| BranchTableIterator<validate> iterator(this, operand); |
| if (!this->Validate(this->pc_, operand, control_.size())) break; |
| auto key = Pop(0, kWasmI32); |
| uint32_t br_arity = 0; |
| while (iterator.has_next()) { |
| const uint32_t i = iterator.cur_index(); |
| const byte* pos = iterator.pc(); |
| uint32_t target = iterator.next(); |
| if (!VALIDATE(target < control_.size())) { |
| this->error(pos, "improper branch in br_table"); |
| break; |
| } |
| // Check that label types match up. |
| Control* c = control_at(target); |
| uint32_t arity = c->is_loop() ? 0 : c->merge.arity; |
| if (i == 0) { |
| br_arity = arity; |
| } else if (!VALIDATE(br_arity == arity)) { |
| this->errorf(pos, |
| "inconsistent arity in br_table target %d" |
| " (previous was %u, this one %u)", |
| i, br_arity, arity); |
| } |
| if (!VALIDATE(TypeCheckBreak(target))) break; |
| } |
| if (!VALIDATE(this->ok())) break; |
| |
| if (operand.table_count > 0) { |
| interface_.BrTable(this, operand, key); |
| } else { |
| // Only a default target. Do the equivalent of br. |
| BranchTableIterator<validate> iterator(this, operand); |
| const byte* pos = iterator.pc(); |
| uint32_t target = iterator.next(); |
| if (!VALIDATE(target < control_.size())) { |
| this->error(pos, "improper branch in br_table"); |
| break; |
| } |
| interface_.BreakTo(this, target); |
| } |
| len = 1 + iterator.length(); |
| EndControl(); |
| break; |
| } |
| case kExprReturn: { |
| DoReturn(); |
| break; |
| } |
| case kExprUnreachable: { |
| interface_.Unreachable(this); |
| EndControl(); |
| break; |
| } |
| case kExprI32Const: { |
| ImmI32Operand<validate> operand(this, this->pc_); |
| auto* value = Push(kWasmI32); |
| interface_.I32Const(this, value, operand.value); |
| len = 1 + operand.length; |
| break; |
| } |
| case kExprI64Const: { |
| ImmI64Operand<validate> operand(this, this->pc_); |
| auto* value = Push(kWasmI64); |
| interface_.I64Const(this, value, operand.value); |
| len = 1 + operand.length; |
| break; |
| } |
| case kExprF32Const: { |
| ImmF32Operand<validate> operand(this, this->pc_); |
| auto* value = Push(kWasmF32); |
| interface_.F32Const(this, value, operand.value); |
| len = 1 + operand.length; |
| break; |
| } |
| case kExprF64Const: { |
| ImmF64Operand<validate> operand(this, this->pc_); |
| auto* value = Push(kWasmF64); |
| interface_.F64Const(this, value, operand.value); |
| len = 1 + operand.length; |
| break; |
| } |
| case kExprGetLocal: { |
| LocalIndexOperand<validate> operand(this, this->pc_); |
| if (!this->Validate(this->pc_, operand)) break; |
| auto* value = Push(operand.type); |
| interface_.GetLocal(this, value, operand); |
| len = 1 + operand.length; |
| break; |
| } |
| case kExprSetLocal: { |
| LocalIndexOperand<validate> operand(this, this->pc_); |
| if (!this->Validate(this->pc_, operand)) break; |
| auto value = Pop(0, local_type_vec_[operand.index]); |
| interface_.SetLocal(this, value, operand); |
| len = 1 + operand.length; |
| break; |
| } |
| case kExprTeeLocal: { |
| LocalIndexOperand<validate> operand(this, this->pc_); |
| if (!this->Validate(this->pc_, operand)) break; |
| auto value = Pop(0, local_type_vec_[operand.index]); |
| auto* result = Push(value.type); |
| interface_.TeeLocal(this, value, result, operand); |
| len = 1 + operand.length; |
| break; |
| } |
| case kExprDrop: { |
| Pop(); |
| break; |
| } |
| case kExprGetGlobal: { |
| GlobalIndexOperand<validate> operand(this, this->pc_); |
| len = 1 + operand.length; |
| if (!this->Validate(this->pc_, operand)) break; |
| auto* result = Push(operand.type); |
| interface_.GetGlobal(this, result, operand); |
| break; |
| } |
| case kExprSetGlobal: { |
| GlobalIndexOperand<validate> operand(this, this->pc_); |
| len = 1 + operand.length; |
| if (!this->Validate(this->pc_, operand)) break; |
| if (!VALIDATE(operand.global->mutability)) { |
| this->errorf(this->pc_, "immutable global #%u cannot be assigned", |
| operand.index); |
| break; |
| } |
| auto value = Pop(0, operand.type); |
| interface_.SetGlobal(this, value, operand); |
| break; |
| } |
| case kExprI32LoadMem8S: |
| len = DecodeLoadMem(kWasmI32, MachineType::Int8()); |
| break; |
| case kExprI32LoadMem8U: |
| len = DecodeLoadMem(kWasmI32, MachineType::Uint8()); |
| break; |
| case kExprI32LoadMem16S: |
| len = DecodeLoadMem(kWasmI32, MachineType::Int16()); |
| break; |
| case kExprI32LoadMem16U: |
| len = DecodeLoadMem(kWasmI32, MachineType::Uint16()); |
| break; |
| case kExprI32LoadMem: |
| len = DecodeLoadMem(kWasmI32, MachineType::Int32()); |
| break; |
| case kExprI64LoadMem8S: |
| len = DecodeLoadMem(kWasmI64, MachineType::Int8()); |
| break; |
| case kExprI64LoadMem8U: |
| len = DecodeLoadMem(kWasmI64, MachineType::Uint8()); |
| break; |
| case kExprI64LoadMem16S: |
| len = DecodeLoadMem(kWasmI64, MachineType::Int16()); |
| break; |
| case kExprI64LoadMem16U: |
| len = DecodeLoadMem(kWasmI64, MachineType::Uint16()); |
| break; |
| case kExprI64LoadMem32S: |
| len = DecodeLoadMem(kWasmI64, MachineType::Int32()); |
| break; |
| case kExprI64LoadMem32U: |
| len = DecodeLoadMem(kWasmI64, MachineType::Uint32()); |
| break; |
| case kExprI64LoadMem: |
| len = DecodeLoadMem(kWasmI64, MachineType::Int64()); |
| break; |
| case kExprF32LoadMem: |
| len = DecodeLoadMem(kWasmF32, MachineType::Float32()); |
| break; |
| case kExprF64LoadMem: |
| len = DecodeLoadMem(kWasmF64, MachineType::Float64()); |
| break; |
| case kExprI32StoreMem8: |
| len = DecodeStoreMem(kWasmI32, MachineType::Int8()); |
| break; |
| case kExprI32StoreMem16: |
| len = DecodeStoreMem(kWasmI32, MachineType::Int16()); |
| break; |
| case kExprI32StoreMem: |
| len = DecodeStoreMem(kWasmI32, MachineType::Int32()); |
| break; |
| case kExprI64StoreMem8: |
| len = DecodeStoreMem(kWasmI64, MachineType::Int8()); |
| break; |
| case kExprI64StoreMem16: |
| len = DecodeStoreMem(kWasmI64, MachineType::Int16()); |
| break; |
| case kExprI64StoreMem32: |
| len = DecodeStoreMem(kWasmI64, MachineType::Int32()); |
| break; |
| case kExprI64StoreMem: |
| len = DecodeStoreMem(kWasmI64, MachineType::Int64()); |
| break; |
| case kExprF32StoreMem: |
| len = DecodeStoreMem(kWasmF32, MachineType::Float32()); |
| break; |
| case kExprF64StoreMem: |
| len = DecodeStoreMem(kWasmF64, MachineType::Float64()); |
| break; |
| case kExprGrowMemory: { |
| if (!CheckHasMemory()) break; |
| MemoryIndexOperand<validate> operand(this, this->pc_); |
| len = 1 + operand.length; |
| DCHECK_NOT_NULL(this->module_); |
| if (!VALIDATE(this->module_->is_wasm())) { |
| this->error("grow_memory is not supported for asmjs modules"); |
| break; |
| } |
| auto value = Pop(0, kWasmI32); |
| auto* result = Push(kWasmI32); |
| interface_.GrowMemory(this, value, result); |
| break; |
| } |
| case kExprMemorySize: { |
| if (!CheckHasMemory()) break; |
| MemoryIndexOperand<validate> operand(this, this->pc_); |
| auto* result = Push(kWasmI32); |
| len = 1 + operand.length; |
| interface_.CurrentMemoryPages(this, result); |
| break; |
| } |
| case kExprCallFunction: { |
| CallFunctionOperand<validate> operand(this, this->pc_); |
| len = 1 + operand.length; |
| if (!this->Validate(this->pc_, operand)) break; |
| // TODO(clemensh): Better memory management. |
| std::vector<Value> args; |
| PopArgs(operand.sig, &args); |
| auto* returns = PushReturns(operand.sig); |
| interface_.CallDirect(this, operand, args.data(), returns); |
| break; |
| } |
| case kExprCallIndirect: { |
| CallIndirectOperand<validate> operand(this, this->pc_); |
| len = 1 + operand.length; |
| if (!this->Validate(this->pc_, operand)) break; |
| auto index = Pop(0, kWasmI32); |
| // TODO(clemensh): Better memory management. |
| std::vector<Value> args; |
| PopArgs(operand.sig, &args); |
| auto* returns = PushReturns(operand.sig); |
| interface_.CallIndirect(this, index, operand, args.data(), returns); |
| break; |
| } |
| case kSimdPrefix: { |
| CHECK_PROTOTYPE_OPCODE(simd); |
| len++; |
| byte simd_index = |
| this->template read_u8<validate>(this->pc_ + 1, "simd index"); |
| opcode = static_cast<WasmOpcode>(opcode << 8 | simd_index); |
| TRACE(" @%-4d #%-20s|", startrel(this->pc_), |
| WasmOpcodes::OpcodeName(opcode)); |
| len += DecodeSimdOpcode(opcode); |
| break; |
| } |
| case kAtomicPrefix: { |
| CHECK_PROTOTYPE_OPCODE(threads); |
| if (!CheckHasSharedMemory()) break; |
| len++; |
| byte atomic_index = |
| this->template read_u8<validate>(this->pc_ + 1, "atomic index"); |
| opcode = static_cast<WasmOpcode>(opcode << 8 | atomic_index); |
| TRACE(" @%-4d #%-20s|", startrel(this->pc_), |
| WasmOpcodes::OpcodeName(opcode)); |
| len += DecodeAtomicOpcode(opcode); |
| break; |
| } |
| default: { |
| // Deal with special asmjs opcodes. |
| if (this->module_ != nullptr && this->module_->is_asm_js()) { |
| sig = WasmOpcodes::AsmjsSignature(opcode); |
| if (sig) { |
| BuildSimpleOperator(opcode, sig); |
| } |
| } else { |
| this->error("Invalid opcode"); |
| return; |
| } |
| } |
| } |
| } |
| |
| #if DEBUG |
| if (FLAG_trace_wasm_decoder) { |
| PrintF(" "); |
| for (size_t i = 0; i < control_.size(); ++i) { |
| Control* c = &control_[i]; |
| switch (c->kind) { |
| case kControlIf: |
| PrintF("I"); |
| break; |
| case kControlBlock: |
| PrintF("B"); |
| break; |
| case kControlLoop: |
| PrintF("L"); |
| break; |
| case kControlTry: |
| PrintF("T"); |
| break; |
| default: |
| break; |
| } |
| PrintF("%u", c->merge.arity); |
| if (c->unreachable) PrintF("*"); |
| } |
| PrintF(" | "); |
| 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 = static_cast<WasmOpcode>(opcode << 8 | *(val.pc + 1)); |
| } |
| PrintF(" %c@%d:%s", WasmOpcodes::ShortNameOf(val.type), |
| static_cast<int>(val.pc - this->start_), |
| WasmOpcodes::OpcodeName(opcode)); |
| switch (opcode) { |
| case kExprI32Const: { |
| ImmI32Operand<validate> operand(this, val.pc); |
| PrintF("[%d]", operand.value); |
| break; |
| } |
| case kExprGetLocal: |
| case kExprSetLocal: |
| case kExprTeeLocal: { |
| LocalIndexOperand<validate> operand(this, val.pc); |
| PrintF("[%u]", operand.index); |
| break; |
| } |
| case kExprGetGlobal: |
| case kExprSetGlobal: { |
| GlobalIndexOperand<validate> operand(this, val.pc); |
| PrintF("[%u]", operand.index); |
| break; |
| } |
| default: |
| break; |
| } |
| } |
| PrintF("\n"); |
| } |
| #endif |
| this->pc_ += len; |
| } // end decode loop |
| if (this->pc_ > this->end_ && this->ok()) this->error("Beyond end of code"); |
| } |
| |
| void EndControl() { |
| DCHECK(!control_.empty()); |
| auto* current = &control_.back(); |
| stack_.resize(current->stack_depth); |
| current->unreachable = true; |
| interface_.EndControl(this, current); |
| } |
| |
| void SetBlockType(Control* c, BlockTypeOperand<validate>& operand) { |
| c->merge.arity = operand.arity; |
| if (c->merge.arity == 1) { |
| c->merge.vals.first = Value::New(this->pc_, operand.read_entry(0)); |
| } else if (c->merge.arity > 1) { |
| c->merge.vals.array = zone_->NewArray<Value>(c->merge.arity); |
| for (unsigned i = 0; i < c->merge.arity; i++) { |
| c->merge.vals.array[i] = Value::New(this->pc_, operand.read_entry(i)); |
| } |
| } |
| } |
| |
| // TODO(clemensh): Better memory management. |
| void PopArgs(FunctionSig* sig, std::vector<Value>* result) { |
| DCHECK(result->empty()); |
| int count = static_cast<int>(sig->parameter_count()); |
| result->resize(count); |
| for (int i = count - 1; i >= 0; --i) { |
| (*result)[i] = Pop(i, sig->GetParam(i)); |
| } |
| } |
| |
| ValueType GetReturnType(FunctionSig* sig) { |
| DCHECK_GE(1, sig->return_count()); |
| return sig->return_count() == 0 ? kWasmStmt : sig->GetReturn(); |
| } |
| |
| Control* PushBlock() { |
| control_.emplace_back(Control::Block(this->pc_, stack_.size())); |
| return &control_.back(); |
| } |
| |
| Control* PushLoop() { |
| control_.emplace_back(Control::Loop(this->pc_, stack_.size())); |
| return &control_.back(); |
| } |
| |
| Control* PushIf() { |
| control_.emplace_back(Control::If(this->pc_, stack_.size())); |
| return &control_.back(); |
| } |
| |
| Control* PushTry() { |
| control_.emplace_back(Control::Try(this->pc_, stack_.size())); |
| // current_catch_ = static_cast<int32_t>(control_.size() - 1); |
| return &control_.back(); |
| } |
| |
| void PopControl(Control* c) { |
| DCHECK_EQ(c, &control_.back()); |
| interface_.PopControl(this, *c); |
| control_.pop_back(); |
| } |
| |
| int DecodeLoadMem(ValueType type, MachineType mem_type) { |
| if (!CheckHasMemory()) return 0; |
| MemoryAccessOperand<validate> operand( |
| this, this->pc_, ElementSizeLog2Of(mem_type.representation())); |
| |
| auto index = Pop(0, kWasmI32); |
| auto* result = Push(type); |
| interface_.LoadMem(this, type, mem_type, operand, index, result); |
| return 1 + operand.length; |
| } |
| |
| int DecodeStoreMem(ValueType type, MachineType mem_type) { |
| if (!CheckHasMemory()) return 0; |
| MemoryAccessOperand<validate> operand( |
| this, this->pc_, ElementSizeLog2Of(mem_type.representation())); |
| auto value = Pop(1, type); |
| auto index = Pop(0, kWasmI32); |
| interface_.StoreMem(this, type, mem_type, operand, index, value); |
| return 1 + operand.length; |
| } |
| |
| int DecodePrefixedLoadMem(ValueType type, MachineType mem_type) { |
| if (!CheckHasMemory()) return 0; |
| MemoryAccessOperand<validate> operand( |
| this, this->pc_ + 1, ElementSizeLog2Of(mem_type.representation())); |
| |
| auto index = Pop(0, kWasmI32); |
| auto* result = Push(type); |
| interface_.LoadMem(this, type, mem_type, operand, index, result); |
| return operand.length; |
| } |
| |
| int DecodePrefixedStoreMem(ValueType type, MachineType mem_type) { |
| if (!CheckHasMemory()) return 0; |
| MemoryAccessOperand<validate> operand( |
| this, this->pc_ + 1, ElementSizeLog2Of(mem_type.representation())); |
| auto value = Pop(1, type); |
| auto index = Pop(0, kWasmI32); |
| interface_.StoreMem(this, type, mem_type, operand, index, value); |
| return operand.length; |
| } |
| |
| unsigned SimdExtractLane(WasmOpcode opcode, ValueType type) { |
| SimdLaneOperand<validate> operand(this, this->pc_); |
| if (this->Validate(this->pc_, opcode, operand)) { |
| Value inputs[] = {Pop(0, ValueType::kSimd128)}; |
| auto* result = Push(type); |
| interface_.SimdLaneOp(this, opcode, operand, ArrayVector(inputs), result); |
| } |
| return operand.length; |
| } |
| |
| unsigned SimdReplaceLane(WasmOpcode opcode, ValueType type) { |
| SimdLaneOperand<validate> operand(this, this->pc_); |
| if (this->Validate(this->pc_, opcode, operand)) { |
| Value inputs[2]; |
| inputs[1] = Pop(1, type); |
| inputs[0] = Pop(0, ValueType::kSimd128); |
| auto* result = Push(ValueType::kSimd128); |
| interface_.SimdLaneOp(this, opcode, operand, ArrayVector(inputs), result); |
| } |
| return operand.length; |
| } |
| |
| unsigned SimdShiftOp(WasmOpcode opcode) { |
| SimdShiftOperand<validate> operand(this, this->pc_); |
| if (this->Validate(this->pc_, opcode, operand)) { |
| auto input = Pop(0, ValueType::kSimd128); |
| auto* result = Push(ValueType::kSimd128); |
| interface_.SimdShiftOp(this, opcode, operand, input, result); |
| } |
| return operand.length; |
| } |
| |
| unsigned Simd8x16ShuffleOp() { |
| Simd8x16ShuffleOperand<validate> operand(this, this->pc_); |
| if (this->Validate(this->pc_, operand)) { |
| auto input1 = Pop(1, ValueType::kSimd128); |
| auto input0 = Pop(0, ValueType::kSimd128); |
| auto* result = Push(ValueType::kSimd128); |
| interface_.Simd8x16ShuffleOp(this, operand, input0, input1, result); |
| } |
| return 16; |
| } |
| |
| unsigned DecodeSimdOpcode(WasmOpcode opcode) { |
| unsigned len = 0; |
| switch (opcode) { |
| case kExprF32x4ExtractLane: { |
| len = SimdExtractLane(opcode, ValueType::kFloat32); |
| break; |
| } |
| case kExprI32x4ExtractLane: |
| case kExprI16x8ExtractLane: |
| case kExprI8x16ExtractLane: { |
| len = SimdExtractLane(opcode, ValueType::kWord32); |
| break; |
| } |
| case kExprF32x4ReplaceLane: { |
| len = SimdReplaceLane(opcode, ValueType::kFloat32); |
| break; |
| } |
| case kExprI32x4ReplaceLane: |
| case kExprI16x8ReplaceLane: |
| case kExprI8x16ReplaceLane: { |
| len = SimdReplaceLane(opcode, ValueType::kWord32); |
| break; |
| } |
| case kExprI32x4Shl: |
| case kExprI32x4ShrS: |
| case kExprI32x4ShrU: |
| case kExprI16x8Shl: |
| case kExprI16x8ShrS: |
| case kExprI16x8ShrU: |
| case kExprI8x16Shl: |
| case kExprI8x16ShrS: |
| case kExprI8x16ShrU: { |
| len = SimdShiftOp(opcode); |
| break; |
| } |
| case kExprS8x16Shuffle: { |
| len = Simd8x16ShuffleOp(); |
| break; |
| } |
| case kExprS128LoadMem: |
| len = DecodePrefixedLoadMem(kWasmS128, MachineType::Simd128()); |
| break; |
| case kExprS128StoreMem: |
| len = DecodePrefixedStoreMem(kWasmS128, MachineType::Simd128()); |
| break; |
| default: { |
| FunctionSig* sig = WasmOpcodes::Signature(opcode); |
| if (!VALIDATE(sig != nullptr)) { |
| this->error("invalid simd opcode"); |
| break; |
| } |
| std::vector<Value> args; |
| PopArgs(sig, &args); |
| auto* result = |
| sig->return_count() == 0 ? nullptr : Push(GetReturnType(sig)); |
| interface_.SimdOp(this, opcode, vec2vec(args), result); |
| } |
| } |
| return len; |
| } |
| |
| unsigned DecodeAtomicOpcode(WasmOpcode opcode) { |
| unsigned len = 0; |
| ValueType ret_type; |
| FunctionSig* sig = WasmOpcodes::AtomicSignature(opcode); |
| if (sig != nullptr) { |
| MachineType memtype; |
| switch (opcode) { |
| #define CASE_ATOMIC_STORE_OP(Name, Type) \ |
| case kExpr##Name: { \ |
| memtype = MachineType::Type(); \ |
| ret_type = MachineRepresentation::kNone; \ |
| break; \ |
| } |
| ATOMIC_STORE_OP_LIST(CASE_ATOMIC_STORE_OP) |
| #undef CASE_ATOMIC_OP |
| #define CASE_ATOMIC_OP(Name, Type) \ |
| case kExpr##Name: { \ |
| memtype = MachineType::Type(); \ |
| ret_type = GetReturnType(sig); \ |
| break; \ |
| } |
| ATOMIC_OP_LIST(CASE_ATOMIC_OP) |
| #undef CASE_ATOMIC_OP |
| default: |
| this->error("invalid atomic opcode"); |
| break; |
| } |
| // TODO(clemensh): Better memory management here. |
| std::vector<Value> args(sig->parameter_count()); |
| MemoryAccessOperand<validate> operand( |
| this, this->pc_ + 1, ElementSizeLog2Of(memtype.representation())); |
| len += operand.length; |
| for (int i = static_cast<int>(sig->parameter_count() - 1); i >= 0; --i) { |
| args[i] = Pop(i, sig->GetParam(i)); |
| } |
| auto result = ret_type == MachineRepresentation::kNone |
| ? nullptr |
| : Push(GetReturnType(sig)); |
| interface_.AtomicOp(this, opcode, vec2vec(args), operand, result); |
| } else { |
| this->error("invalid atomic opcode"); |
| } |
| return len; |
| } |
| |
| void DoReturn() { |
| // TODO(clemensh): Optimize memory usage here (it will be mostly 0 or 1 |
| // returned values). |
| int return_count = static_cast<int>(this->sig_->return_count()); |
| std::vector<Value> values(return_count); |
| |
| // Pop return values off the stack in reverse order. |
| for (int i = return_count - 1; i >= 0; --i) { |
| values[i] = Pop(i, this->sig_->GetReturn(i)); |
| } |
| |
| interface_.DoReturn(this, vec2vec(values)); |
| EndControl(); |
| } |
| |
| inline Value* Push(ValueType type) { |
| DCHECK_NE(kWasmStmt, type); |
| stack_.push_back(Value::New(this->pc_, type)); |
| return &stack_.back(); |
| } |
| |
| void PushEndValues(Control* c) { |
| DCHECK_EQ(c, &control_.back()); |
| stack_.resize(c->stack_depth); |
| if (c->merge.arity == 1) { |
| stack_.push_back(c->merge.vals.first); |
| } else { |
| for (unsigned i = 0; i < c->merge.arity; i++) { |
| stack_.push_back(c->merge.vals.array[i]); |
| } |
| } |
| DCHECK_EQ(c->stack_depth + c->merge.arity, stack_.size()); |
| } |
| |
| Value* PushReturns(FunctionSig* sig) { |
| size_t return_count = sig->return_count(); |
| if (return_count == 0) return nullptr; |
| size_t old_size = stack_.size(); |
| for (size_t i = 0; i < return_count; ++i) { |
| Push(sig->GetReturn(i)); |
| } |
| return stack_.data() + old_size; |
| } |
| |
| Value Pop(int index, ValueType expected) { |
| auto val = Pop(); |
| if (!VALIDATE(val.type == expected || val.type == kWasmVar || |
| expected == kWasmVar)) { |
| this->errorf(val.pc, "%s[%d] expected type %s, found %s of type %s", |
| SafeOpcodeNameAt(this->pc_), index, |
| WasmOpcodes::TypeName(expected), SafeOpcodeNameAt(val.pc), |
| WasmOpcodes::TypeName(val.type)); |
| } |
| return val; |
| } |
| |
| Value Pop() { |
| DCHECK(!control_.empty()); |
| uint32_t limit = control_.back().stack_depth; |
| if (stack_.size() <= limit) { |
| // Popping past the current control start in reachable code. |
| if (!VALIDATE(control_.back().unreachable)) { |
| this->errorf(this->pc_, "%s found empty stack", |
| SafeOpcodeNameAt(this->pc_)); |
| } |
| return Value::Unreachable(this->pc_); |
| } |
| auto val = stack_.back(); |
| stack_.pop_back(); |
| return val; |
| } |
| |
| int startrel(const byte* ptr) { return static_cast<int>(ptr - this->start_); } |
| |
| void FallThruTo(Control* c) { |
| DCHECK_EQ(c, &control_.back()); |
| if (!TypeCheckFallThru(c)) return; |
| c->unreachable = false; |
| |
| interface_.FallThruTo(this, c); |
| } |
| |
| bool TypeCheckMergeValues(Control* c) { |
| DCHECK_GE(stack_.size(), c->stack_depth + c->merge.arity); |
| // Typecheck the topmost {c->merge.arity} values on the stack. |
| for (uint32_t i = 0; i < c->merge.arity; ++i) { |
| auto& val = GetMergeValueFromStack(c, i); |
| auto& old = c->merge[i]; |
| if (val.type != old.type) { |
| // If {val.type} is polymorphic, which results from unreachable, make |
| // it more specific by using the merge value's expected type. |
| // If it is not polymorphic, this is a type error. |
| if (!VALIDATE(val.type == kWasmVar)) { |
| this->errorf( |
| this->pc_, "type error in merge[%u] (expected %s, got %s)", i, |
| WasmOpcodes::TypeName(old.type), WasmOpcodes::TypeName(val.type)); |
| return false; |
| } |
| val.type = old.type; |
| } |
| } |
| |
| return true; |
| } |
| |
| bool TypeCheckFallThru(Control* c) { |
| DCHECK_EQ(c, &control_.back()); |
| if (!validate) return true; |
| uint32_t expected = c->merge.arity; |
| DCHECK_GE(stack_.size(), c->stack_depth); |
| uint32_t actual = static_cast<uint32_t>(stack_.size()) - c->stack_depth; |
| // Fallthrus must match the arity of the control exactly. |
| if (!InsertUnreachablesIfNecessary(expected, actual) || actual > expected) { |
| this->errorf( |
| this->pc_, |
| "expected %u elements on the stack for fallthru to @%d, found %u", |
| expected, startrel(c->pc), actual); |
| return false; |
| } |
| |
| return TypeCheckMergeValues(c); |
| } |
| |
| bool TypeCheckBreak(unsigned depth) { |
| Control* c = control_at(depth); |
| if (c->is_loop()) { |
| // This is the inner loop block, which does not have a value. |
| return true; |
| } |
| // Breaks must have at least the number of values expected; can have more. |
| uint32_t expected = c->merge.arity; |
| DCHECK_GE(stack_.size(), control_.back().stack_depth); |
| uint32_t actual = |
| static_cast<uint32_t>(stack_.size()) - control_.back().stack_depth; |
| if (!InsertUnreachablesIfNecessary(expected, actual)) { |
| this->errorf(this->pc_, |
| "expected %u elements on the stack for br to @%d, found %u", |
| expected, startrel(c->pc), actual); |
| return false; |
| } |
| return TypeCheckMergeValues(c); |
| } |
| |
| inline bool InsertUnreachablesIfNecessary(uint32_t expected, |
| uint32_t actual) { |
| if (V8_LIKELY(actual >= expected)) { |
| return true; // enough actual values are there. |
| } |
| if (!VALIDATE(control_.back().unreachable)) { |
| // There aren't enough values on the stack. |
| return false; |
| } |
| // A slow path. When the actual number of values on the stack is less |
| // than the expected number of values and the current control is |
| // unreachable, insert unreachable values below the actual values. |
| // This simplifies {TypeCheckMergeValues}. |
| auto pos = stack_.begin() + (stack_.size() - actual); |
| stack_.insert(pos, (expected - actual), Value::Unreachable(this->pc_)); |
| return true; |
| } |
| |
| virtual void onFirstError() { |
| this->end_ = this->pc_; // Terminate decoding loop. |
| TRACE(" !%s\n", this->error_msg_.c_str()); |
| } |
| |
| inline void BuildSimpleOperator(WasmOpcode opcode, FunctionSig* sig) { |
| switch (sig->parameter_count()) { |
| case 1: { |
| auto val = Pop(0, sig->GetParam(0)); |
| auto* ret = |
| sig->return_count() == 0 ? nullptr : Push(sig->GetReturn(0)); |
| interface_.UnOp(this, opcode, sig, 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)); |
| interface_.BinOp(this, opcode, sig, lval, rval, ret); |
| break; |
| } |
| default: |
| UNREACHABLE(); |
| } |
| } |
| }; |
| |
| class EmptyInterface { |
| public: |
| constexpr static bool validate = true; |
| using Value = ValueBase; |
| using Control = ControlBase<Value>; |
| using Decoder = WasmFullDecoder<validate, EmptyInterface>; |
| |
| #define DEFINE_EMPTY_CALLBACK(name, ...) \ |
| void name(Decoder* decoder, ##__VA_ARGS__) {} |
| INTERFACE_FUNCTIONS(DEFINE_EMPTY_CALLBACK) |
| #undef DEFINE_EMPTY_CALLBACK |
| }; |
| |
| #undef TRACE |
| #undef VALIDATE |
| #undef CHECK_PROTOTYPE_OPCODE |
| #undef OPCODE_ERROR |
| |
| } // namespace wasm |
| } // namespace internal |
| } // namespace v8 |
| |
| #endif // V8_WASM_FUNCTION_BODY_DECODER_IMPL_H_ |