diff options
Diffstat (limited to 'deps/v8/src/wasm/function-body-decoder-impl.h')
-rw-r--r-- | deps/v8/src/wasm/function-body-decoder-impl.h | 1844 |
1 files changed, 1840 insertions, 4 deletions
diff --git a/deps/v8/src/wasm/function-body-decoder-impl.h b/deps/v8/src/wasm/function-body-decoder-impl.h index ec295cb0e0..1a7278c78e 100644 --- a/deps/v8/src/wasm/function-body-decoder-impl.h +++ b/deps/v8/src/wasm/function-body-decoder-impl.h @@ -5,7 +5,14 @@ #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 { @@ -13,15 +20,61 @@ 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 +// and always return true otherwise. +#define VALIDATE(condition) \ + (validate ? (condition) : [&] { \ + DCHECK(condition); \ + return true; \ + }()) + +// Return the evaluation of `condition` if validate==true, DCHECK that it's +// false and always return false otherwise. +#define CHECK_ERROR(condition) \ + (validate ? (condition) : [&] { \ + DCHECK(!(condition)); \ + return false; \ + }()) // Use this macro to check a condition if checked == true, and DCHECK the // condition otherwise. +// TODO(clemensh): Rename all "checked" to "validate" and replace +// "CHECKED_COND" with "CHECK_ERROR". #define CHECKED_COND(cond) \ (checked ? (cond) : ([&] { \ DCHECK(cond); \ return true; \ })()) +#define CHECK_PROTOTYPE_OPCODE(flag) \ + if (this->module_ != nullptr && this->module_->is_asm_js()) { \ + this->error("Opcode not supported for asmjs modules"); \ + } \ + 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))) + +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 checked> struct LocalIndexOperand { @@ -35,6 +88,17 @@ struct LocalIndexOperand { }; template <bool checked> +struct ExceptionIndexOperand { + uint32_t index; + const WasmException* exception = nullptr; + unsigned length; + + inline ExceptionIndexOperand(Decoder* decoder, const byte* pc) { + index = decoder->read_u32v<checked>(pc + 1, &length, "exception index"); + } +}; + +template <bool checked> struct ImmI32Operand { int32_t value; unsigned length; @@ -157,19 +221,20 @@ struct BlockTypeOperand { return false; } } + ValueType read_entry(unsigned index) { DCHECK_LT(index, arity); ValueType result; - CHECK(decode_local_type(types[index], &result)); + bool success = decode_local_type(types[index], &result); + DCHECK(success); + USE(success); return result; } }; -struct Control; template <bool checked> struct BreakDepthOperand { uint32_t depth; - Control* target = nullptr; unsigned length; inline BreakDepthOperand(Decoder* decoder, const byte* pc) { depth = decoder->read_u32v<checked>(pc + 1, &length, "break depth"); @@ -253,7 +318,8 @@ class BranchTableIterator { } const byte* pc() { return pc_; } - BranchTableIterator(Decoder* decoder, BranchTableOperand<checked>& operand) + BranchTableIterator(Decoder* decoder, + const BranchTableOperand<checked>& operand) : decoder_(decoder), start_(operand.start), pc_(operand.table), @@ -325,7 +391,1777 @@ struct Simd8x16ShuffleOperand { } }; +// An entry on the value stack. +template <typename Interface> +struct AbstractValue { + const byte* pc; + ValueType type; + typename Interface::IValue interface_data; + + // Named constructors. + static AbstractValue Unreachable(const byte* pc) { + return {pc, kWasmVar, Interface::IValue::Unreachable()}; + } + + static AbstractValue New(const byte* pc, ValueType type) { + return {pc, type, Interface::IValue::New()}; + } +}; + +template <typename Interface> +struct AbstractMerge { + uint32_t arity; + union { + AbstractValue<Interface>* array; + AbstractValue<Interface> first; + } vals; // Either multiple values or a single value. + + AbstractValue<Interface>& operator[](size_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 Interface> +struct AbstractControl { + const byte* pc; + ControlKind kind; + size_t stack_depth; // stack height at the beginning of the construct. + typename Interface::IControl interface_data; + bool unreachable; // The current block has been ended. + + // Values merged into the end of this control construct. + AbstractMerge<Interface> 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 AbstractControl Block(const byte* pc, size_t stack_depth) { + return {pc, kControlBlock, stack_depth, Interface::IControl::Block(), false, + {}}; + } + + static AbstractControl If(const byte* pc, size_t stack_depth) { + return {pc, kControlIf, stack_depth, Interface::IControl::If(), false, {}}; + } + + static AbstractControl Loop(const byte* pc, size_t stack_depth) { + return {pc, kControlLoop, stack_depth, Interface::IControl::Loop(), false, + {}}; + } + + static AbstractControl Try(const byte* pc, size_t stack_depth) { + return {pc, kControlTry, stack_depth, Interface::IControl::Try(), + false, {}}; + } +}; + +// 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, Control* block) \ + F(BrIf, const Value& cond, Control* block) \ + 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>&) \ + F(Catch, const ExceptionIndexOperand<validate>& operand, Control* block) \ + F(AtomicOp, WasmOpcode opcode, Vector<Value> args, 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_; + + size_t total_locals() const { + return local_types_ == nullptr ? 0 : 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: + type = kWasmS128; + break; + 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, + int locals_count, Zone* zone) { + if (pc >= decoder->end()) return nullptr; + if (*pc != kExprLoop) return nullptr; + + 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 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())) { + if (local_types_) { + operand.type = local_types_->at(operand.index); + } else { + operand.type = kWasmStmt; + } + return true; + } + errorf(pc + 1, "invalid local index: %u", operand.index); + return false; + } + + inline bool Validate(const byte* pc, + ExceptionIndexOperand<validate>& operand) { + if (module_ != nullptr && operand.index < module_->exceptions.size()) { + operand.exception = &module_->exceptions[operand.index]; + return true; + } + errorf(pc + 1, "Invalid exception index: %u", operand.index); + return false; + } + + inline bool Validate(const byte* pc, GlobalIndexOperand<validate>& operand) { + if (VALIDATE(module_ != nullptr && + operand.index < module_->globals.size())) { + operand.global = &module_->globals[operand.index]; + operand.type = operand.global->type; + return true; + } + errorf(pc + 1, "invalid global index: %u", operand.index); + return false; + } + + inline bool Complete(const byte* pc, CallFunctionOperand<validate>& operand) { + if (VALIDATE(module_ != nullptr && + operand.index < module_->functions.size())) { + operand.sig = module_->functions[operand.index].sig; + return true; + } + return false; + } + + 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())) { + operand.sig = module_->signatures[operand.index]; + return true; + } + return false; + } + + inline bool Validate(const byte* pc, CallIndirectOperand<validate>& operand) { + if (CHECK_ERROR(module_ == nullptr || module_->function_tables.empty())) { + error("function table has to exist to execute call_indirect"); + return false; + } + if (Complete(pc, operand)) { + return true; + } + errorf(pc + 1, "invalid signature index: #%u", operand.index); + return false; + } + + inline bool Validate(const byte* pc, BreakDepthOperand<validate>& operand, + size_t control_depth) { + if (VALIDATE(operand.depth < control_depth)) { + return true; + } + errorf(pc + 1, "invalid break depth: %u", operand.depth); + return false; + } + + bool Validate(const byte* pc, BranchTableOperand<validate>& operand, + size_t block_depth) { + if (CHECK_ERROR(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 (CHECK_ERROR(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 (CHECK_ERROR(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 (CHECK_ERROR(max_lane > 2 * kSimd128Size)) { + error(pc_ + 2, "invalid shuffle mask"); + return false; + } else { + 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 = AbstractValue<Interface>; + using Control = AbstractControl<Interface>; + using MergeValues = AbstractMerge<Interface>; + + // 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 const Value& GetMergeValueFromStack(Control* c, size_t i) { + DCHECK_GT(c->merge.arity, i); + DCHECK_GE(stack_.size(), 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)) return true; + this->error(this->pc_ - 1, "memory instruction with no memory"); + return false; + } + + // 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; + if (operand.exception->sig->parameter_count() > 0) { + // TODO(kschimpf): Fix to pull values off stack and build throw. + OPCODE_ERROR(opcode, "can't handle exceptions with values yet"); + break; + } + interface_.Throw(this, operand); + // TODO(titzer): Throw should end control, but currently we build a + // (reachable) runtime call instead of connecting it directly to + // end. + // EndControl(); + 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 (CHECK_ERROR(control_.empty())) { + this->error("catch does not match any try"); + break; + } + + Control* c = &control_.back(); + if (CHECK_ERROR(!c->is_try())) { + this->error("catch does not match any try"); + break; + } + + if (CHECK_ERROR(c->is_try_catch())) { + OPCODE_ERROR(opcode, "multiple catch blocks not implemented"); + break; + } + c->kind = kControlTryCatch; + FallThruTo(c); + stack_.resize(c->stack_depth); + + interface_.Catch(this, operand, c); + 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 (CHECK_ERROR(control_.empty())) { + this->error("else does not match any if"); + break; + } + Control* c = &control_.back(); + if (CHECK_ERROR(!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 (CHECK_ERROR(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); + if (c->unreachable) PushEndValues(c); + PopControl(c); + break; + } + if (c->is_onearmed_if()) { + // End the true branch of a one-armed if. + if (CHECK_ERROR(!c->unreachable && + stack_.size() != c->stack_depth)) { + this->error("end of if expected empty stack"); + stack_.resize(c->stack_depth); + } + if (CHECK_ERROR(c->merge.arity > 0)) { + this->error("non-void one-armed if"); + } + } else if (CHECK_ERROR(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 (CHECK_ERROR(this->pc_ + 1 != this->end_)) { + this->error(this->pc_ + 1, "trailing code after function end"); + break; + } + last_end_found_ = true; + if (c->unreachable) { + TypeCheckFallThru(c); + } else { + // 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 (VALIDATE(this->Validate(this->pc_, operand, control_.size()) && + TypeCheckBreak(operand.depth))) { + interface_.BreakTo(this, control_at(operand.depth)); + } + len = 1 + operand.length; + EndControl(); + break; + } + case kExprBrIf: { + BreakDepthOperand<validate> operand(this, this->pc_); + auto cond = Pop(0, kWasmI32); + if (VALIDATE(this->ok() && + this->Validate(this->pc_, operand, control_.size()) && + TypeCheckBreak(operand.depth))) { + interface_.BrIf(this, cond, control_at(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); + MergeValues* merge = nullptr; + while (iterator.has_next()) { + const uint32_t i = iterator.cur_index(); + const byte* pos = iterator.pc(); + uint32_t target = iterator.next(); + if (CHECK_ERROR(target >= control_.size())) { + this->error(pos, "improper branch in br_table"); + break; + } + // Check that label types match up. + static MergeValues loop_dummy = {0, {nullptr}}; + Control* c = control_at(target); + MergeValues* current = c->is_loop() ? &loop_dummy : &c->merge; + if (i == 0) { + merge = current; + } else if (CHECK_ERROR(merge->arity != current->arity)) { + this->errorf(pos, + "inconsistent arity in br_table target %d" + " (previous was %u, this one %u)", + i, merge->arity, current->arity); + } else if (control_at(0)->unreachable) { + for (uint32_t j = 0; VALIDATE(this->ok()) && j < merge->arity; + ++j) { + if (CHECK_ERROR((*merge)[j].type != (*current)[j].type)) { + this->errorf(pos, + "type error in br_table target %d operand %d" + " (previous expected %s, this one %s)", + i, j, WasmOpcodes::TypeName((*merge)[j].type), + WasmOpcodes::TypeName((*current)[j].type)); + } + } + } + bool valid = TypeCheckBreak(target); + if (CHECK_ERROR(!valid)) break; + } + if (CHECK_ERROR(this->failed())) 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 (CHECK_ERROR(target >= control_.size())) { + this->error(pos, "improper branch in br_table"); + break; + } + interface_.BreakTo(this, control_at(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 (CHECK_ERROR(!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 (CHECK_ERROR(!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); + 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: { + LocalIndexOperand<validate> operand(this, val.pc); + PrintF("[%u]", operand.index); + break; + } + case kExprSetLocal: // fallthru + case kExprTeeLocal: { + LocalIndexOperand<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 (CHECK_ERROR(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; + FunctionSig* sig = WasmOpcodes::AtomicSignature(opcode); + if (sig != nullptr) { + // TODO(clemensh): Better memory management here. + std::vector<Value> args(sig->parameter_count()); + for (int i = static_cast<int>(sig->parameter_count() - 1); i >= 0; --i) { + args[i] = Pop(i, sig->GetParam(i)); + } + auto* result = Push(GetReturnType(sig)); + interface_.AtomicOp(this, opcode, vec2vec(args), 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(type != kWasmStmt); + 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 (CHECK_ERROR(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()); + size_t limit = control_.back().stack_depth; + if (stack_.size() <= limit) { + // Popping past the current control start in reachable code. + if (CHECK_ERROR(!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_); } + + bool TypeCheckBreak(unsigned depth) { + DCHECK(validate); // Only call this for validation. + Control* c = control_at(depth); + if (c->is_loop()) { + // This is the inner loop block, which does not have a value. + return true; + } + size_t expected = control_.back().stack_depth + c->merge.arity; + if (stack_.size() < expected && !control_.back().unreachable) { + this->errorf( + this->pc_, + "expected at least %u values on the stack for br to @%d, found %d", + c->merge.arity, startrel(c->pc), + static_cast<int>(stack_.size() - c->stack_depth)); + return false; + } + + return TypeCheckMergeValues(c); + } + + void FallThruTo(Control* c) { + DCHECK_EQ(c, &control_.back()); + if (!TypeCheckFallThru(c)) return; + c->unreachable = false; + + interface_.FallThruTo(this, c); + } + + bool TypeCheckMergeValues(Control* c) { + // Typecheck the values left on the stack. + size_t avail = stack_.size() - c->stack_depth; + size_t start = avail >= c->merge.arity ? 0 : c->merge.arity - avail; + for (size_t i = start; i < c->merge.arity; ++i) { + auto& val = GetMergeValueFromStack(c, i); + auto& old = c->merge[i]; + if (val.type != old.type && val.type != kWasmVar) { + this->errorf( + this->pc_, "type error in merge[%zu] (expected %s, got %s)", i, + WasmOpcodes::TypeName(old.type), WasmOpcodes::TypeName(val.type)); + return false; + } + } + + return true; + } + + bool TypeCheckFallThru(Control* c) { + DCHECK_EQ(c, &control_.back()); + if (!validate) return true; + // Fallthru must match arity exactly. + size_t expected = c->stack_depth + c->merge.arity; + if (stack_.size() != expected && + (stack_.size() > expected || !c->unreachable)) { + this->errorf(this->pc_, + "expected %u elements on the stack for fallthru to @%d", + c->merge.arity, startrel(c->pc)); + return false; + } + + return TypeCheckMergeValues(c); + } + + 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(); + } + } +}; + +template <bool decoder_validate, typename Interface> +class InterfaceTemplate { + public: + constexpr static bool validate = decoder_validate; + using Decoder = WasmFullDecoder<validate, Interface>; + using Control = AbstractControl<Interface>; + using Value = AbstractValue<Interface>; + using MergeValues = AbstractMerge<Interface>; +}; + +class EmptyInterface : public InterfaceTemplate<true, EmptyInterface> { + public: + struct IValue { + static IValue Unreachable() { return {}; } + static IValue New() { return {}; } + }; + struct IControl { + static IControl Block() { return {}; } + static IControl If() { return {}; } + static IControl Loop() { return {}; } + static IControl Try() { return {}; } + }; + +#define DEFINE_EMPTY_CALLBACK(name, ...) \ + void name(Decoder* decoder, ##__VA_ARGS__) {} + INTERFACE_FUNCTIONS(DEFINE_EMPTY_CALLBACK) +#undef DEFINE_EMPTY_CALLBACK +}; + #undef CHECKED_COND +#undef VALIDATE +#undef CHECK_ERROR +#undef TRACE +#undef CHECK_PROTOTYPE_OPCODE +#undef PROTOTYPE_NOT_FUNCTIONAL } // namespace wasm } // namespace internal |