From 071a1acf326633f4ddfe93b8c4d8ea224f20e0c7 Mon Sep 17 00:00:00 2001 From: Manos Koukoutos Date: Tue, 29 Jun 2021 15:00:01 +0000 Subject: [PATCH] [wasm] Refactor initializer expression handling Design doc: https://bit.ly/3xPxWUe This CL introduces two main changes: - Initializer expressions are now decoded by WasmFullDecoder. With wasm-gc, initializer expressions are no longer just constants, and require complex decoding (including stack tracking). This resulted in extensive code duplication. - Initializer expressions are not stored explicitly by module-decoder as an AST (WasmInitExpr), but rather as a WireBytesRef, and are decoded again during module instantiation. This should reduce memory consumption for globals and other module elements with initializer expressions (which has been observed in the 40MB range in some real-world benchmarks. Summary of changes: - Add a static parameter {kFunctionBody, kInitExpression} to the WasmDecoder. Use it to specialize validation to function bodies/init. expressions. - Introduce a new Interface for the WasmFullDecoder for init. expressions. - Differentiate between constant and non-constant opcodes in WasmFullDecoder. - Change representation of init. expressions in WasmModule to WireBytesRef. - Reimplement EvaluateInitExpression in module-instantiate to re-decode initializer expressions. - Remove some now-invalid module decoder tests. Pending changes: - Also refactor initializer expressions for element segment entries. - Reintroduce deleted tests. Bug: v8:11895 Change-Id: I76512bfe1386c8338667d30fa6db93880a1e4b42 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2972910 Reviewed-by: Jakob Kummerow Commit-Queue: Manos Koukoutos Cr-Commit-Position: refs/heads/master@{#75476} --- BUILD.bazel | 2 + BUILD.gn | 2 + src/wasm/function-body-decoder-impl.h | 227 ++++++++--- src/wasm/init-expr-interface.cc | 143 +++++++ src/wasm/init-expr-interface.h | 97 +++++ src/wasm/module-decoder.cc | 352 ++---------------- src/wasm/module-decoder.h | 2 +- src/wasm/module-instantiate.cc | 124 ++---- src/wasm/wasm-module.h | 12 +- test/cctest/wasm/wasm-run-utils.cc | 2 +- test/fuzzer/wasm-fuzzer-common.cc | 50 ++- .../wasm/function-body-decoder-unittest.cc | 3 +- .../unittests/wasm/module-decoder-unittest.cc | 185 ++------- 13 files changed, 588 insertions(+), 613 deletions(-) create mode 100644 src/wasm/init-expr-interface.cc create mode 100644 src/wasm/init-expr-interface.h diff --git a/BUILD.bazel b/BUILD.bazel index b4df65c9ff..ba90633387 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -1937,6 +1937,8 @@ filegroup( "src/wasm/function-compiler.h", "src/wasm/graph-builder-interface.cc", "src/wasm/graph-builder-interface.h", + "src/wasm/init-expr-interface.cc", + "src/wasm/init-expr-interface.h", "src/wasm/jump-table-assembler.cc", "src/wasm/jump-table-assembler.h", "src/wasm/leb-helper.h", diff --git a/BUILD.gn b/BUILD.gn index a81ed785f6..a1b104a72c 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -3167,6 +3167,7 @@ v8_header_set("v8_internal_headers") { "src/wasm/function-body-decoder.h", "src/wasm/function-compiler.h", "src/wasm/graph-builder-interface.h", + "src/wasm/init-expr-interface.h", "src/wasm/jump-table-assembler.h", "src/wasm/leb-helper.h", "src/wasm/local-decl-encoder.h", @@ -4103,6 +4104,7 @@ v8_source_set("v8_base_without_compiler") { "src/wasm/function-body-decoder.cc", "src/wasm/function-compiler.cc", "src/wasm/graph-builder-interface.cc", + "src/wasm/init-expr-interface.cc", "src/wasm/jump-table-assembler.cc", "src/wasm/local-decl-encoder.cc", "src/wasm/memory-protection-key.cc", diff --git a/src/wasm/function-body-decoder-impl.h b/src/wasm/function-body-decoder-impl.h index e2c0f09789..ad506217ac 100644 --- a/src/wasm/function-body-decoder-impl.h +++ b/src/wasm/function-body-decoder-impl.h @@ -418,6 +418,8 @@ ValueType read_value_type(Decoder* decoder, const byte* pc, } } // namespace value_type_reader +enum DecodingMode { kFunctionBody, kInitExpression }; + // Helpers for decoding different kinds of immediates which follow bytecodes. template struct ImmI32Immediate { @@ -677,7 +679,8 @@ class BranchTableIterator { const uint32_t table_count_; // the count of entries, not including default. }; -template +template class WasmDecoder; template @@ -1088,7 +1091,7 @@ struct ControlBase : public PcForErrors { // Generic Wasm bytecode decoder with utilities for decoding immediates, // lengths, etc. -template +template class WasmDecoder : public Decoder { public: WasmDecoder(Zone* zone, const WasmModule* module, const WasmFeatures& enabled, @@ -1268,10 +1271,26 @@ class WasmDecoder : public Decoder { bool Validate(const byte* pc, GlobalIndexImmediate& imm) { if (!VALIDATE(imm.index < module_->globals.size())) { - DecodeError(pc, "invalid global index: %u", imm.index); + DecodeError(pc, "Invalid global index: %u", imm.index); return false; } imm.global = &module_->globals[imm.index]; + + if (decoding_mode == kInitExpression) { + if (!VALIDATE(!imm.global->mutability)) { + this->DecodeError(pc, + "mutable globals cannot be used in initializer " + "expressions"); + return false; + } + if (!VALIDATE(imm.global->imported || this->enabled_.has_gc())) { + this->DecodeError( + pc, + "non-imported globals cannot be used in initializer expressions"); + return false; + } + } + return true; } @@ -1553,7 +1572,8 @@ class WasmDecoder : public Decoder { DecodeError(pc, "function index #%u is out of bounds", imm.index); return false; } - if (!VALIDATE(module_->functions[imm.index].declared)) { + if (decoding_mode == kFunctionBody && + !VALIDATE(module_->functions[imm.index].declared)) { DecodeError(pc, "undeclared reference to function #%u", imm.index); return false; } @@ -2106,8 +2126,9 @@ class WasmDecoder : public Decoder { } \ } while (false) -template -class WasmFullDecoder : public WasmDecoder { +template +class WasmFullDecoder : public WasmDecoder { using Value = typename Interface::Value; using Control = typename Interface::Control; using ArgVector = base::Vector; @@ -2122,8 +2143,9 @@ class WasmFullDecoder : public WasmDecoder { WasmFullDecoder(Zone* zone, const WasmModule* module, const WasmFeatures& enabled, WasmFeatures* detected, const FunctionBody& body, InterfaceArgs&&... interface_args) - : WasmDecoder(zone, module, enabled, detected, body.sig, - body.start, body.end, body.offset), + : WasmDecoder(zone, module, enabled, detected, + body.sig, body.start, body.end, + body.offset), interface_(std::forward(interface_args)...), control_(zone) {} @@ -2741,36 +2763,43 @@ class WasmFullDecoder : public WasmDecoder { DECODE(End) { DCHECK(!control_.empty()); - Control* c = &control_.back(); - if (c->is_incomplete_try()) { - // Catch-less try, fall through to the implicit catch-all. - c->kind = kControlTryCatch; - current_catch_ = c->previous_catch; // Pop try scope. - } - if (c->is_try_catch()) { - // Emulate catch-all + re-throw. - FallThrough(); - c->reachability = control_at(1)->innerReachability(); - CALL_INTERFACE_IF_OK_AND_PARENT_REACHABLE(CatchAll, c); - current_code_reachable_and_ok_ = - this->ok() && control_.back().reachable(); - CALL_INTERFACE_IF_OK_AND_REACHABLE(Rethrow, c); - EndControl(); - PopControl(); - return 1; - } - if (c->is_onearmed_if()) { - if (!VALIDATE(TypeCheckOneArmedIf(c))) return 0; - } + if (decoding_mode == kFunctionBody) { + Control* c = &control_.back(); + if (c->is_incomplete_try()) { + // Catch-less try, fall through to the implicit catch-all. + c->kind = kControlTryCatch; + current_catch_ = c->previous_catch; // Pop try scope. + } + if (c->is_try_catch()) { + // Emulate catch-all + re-throw. + FallThrough(); + c->reachability = control_at(1)->innerReachability(); + CALL_INTERFACE_IF_OK_AND_PARENT_REACHABLE(CatchAll, c); + current_code_reachable_and_ok_ = + this->ok() && control_.back().reachable(); + CALL_INTERFACE_IF_OK_AND_REACHABLE(Rethrow, c); + EndControl(); + PopControl(); + return 1; + } + if (c->is_onearmed_if()) { + if (!VALIDATE(TypeCheckOneArmedIf(c))) return 0; + } - if (c->is_let()) { - CALL_INTERFACE_IF_OK_AND_REACHABLE(DeallocateLocals, c->locals_count); - this->local_types_.erase(this->local_types_.begin(), - this->local_types_.begin() + c->locals_count); - this->num_locals_ -= c->locals_count; + if (c->is_let()) { + CALL_INTERFACE_IF_OK_AND_REACHABLE(DeallocateLocals, c->locals_count); + this->local_types_.erase(this->local_types_.begin(), + this->local_types_.begin() + c->locals_count); + this->num_locals_ -= c->locals_count; + } } if (control_.size() == 1) { + // We need to call this first because the interface might set + // {this->end_}, making the next check pass. + DoReturn(); // If at the last (implicit) control, check we are at end. if (!VALIDATE(this->pc_ + 1 == this->end_)) { this->DecodeError(this->pc_ + 1, "trailing code after function end"); @@ -2779,7 +2808,6 @@ class WasmFullDecoder : public WasmDecoder { // The result of the block is the return value. trace_msg->Append("\n" TRACE_INST_FORMAT, startrel(this->pc_), "(implicit) return"); - DoReturn(); control_.clear(); return 1; } @@ -3339,6 +3367,12 @@ class WasmFullDecoder : public WasmDecoder { #undef DECODE + static int NonConstError(WasmFullDecoder* decoder, WasmOpcode opcode) { + decoder->DecodeError("opcode %s is not allowed in init. expressions", + WasmOpcodes::OpcodeName(opcode)); + return 0; + } + using OpcodeHandler = int (*)(WasmFullDecoder*, WasmOpcode); // Ideally we would use template specialization for the different opcodes, but @@ -3348,7 +3382,16 @@ class WasmFullDecoder : public WasmDecoder { // Hence just list all implementations explicitly here, which also gives more // freedom to use the same implementation for different opcodes. #define DECODE_IMPL(opcode) DECODE_IMPL2(kExpr##opcode, opcode) -#define DECODE_IMPL2(opcode, name) \ +#define DECODE_IMPL2(opcode, name) \ + if (idx == opcode) { \ + if (decoding_mode == kInitExpression) { \ + return &WasmFullDecoder::NonConstError; \ + } else { \ + return &WasmFullDecoder::Decode##name; \ + } \ + } +#define DECODE_IMPL_CONST(opcode) DECODE_IMPL_CONST2(kExpr##opcode, opcode) +#define DECODE_IMPL_CONST2(opcode, name) \ if (idx == opcode) return &WasmFullDecoder::Decode##name static constexpr OpcodeHandler GetOpcodeHandlerTableEntry(size_t idx) { @@ -3369,7 +3412,7 @@ class WasmFullDecoder : public WasmDecoder { DECODE_IMPL(Loop); DECODE_IMPL(If); DECODE_IMPL(Else); - DECODE_IMPL(End); + DECODE_IMPL_CONST(End); DECODE_IMPL(Select); DECODE_IMPL(SelectWithType); DECODE_IMPL(Br); @@ -3378,19 +3421,19 @@ class WasmFullDecoder : public WasmDecoder { DECODE_IMPL(Return); DECODE_IMPL(Unreachable); DECODE_IMPL(NopForTestingUnsupportedInLiftoff); - DECODE_IMPL(I32Const); - DECODE_IMPL(I64Const); - DECODE_IMPL(F32Const); - DECODE_IMPL(F64Const); - DECODE_IMPL(RefNull); + DECODE_IMPL_CONST(I32Const); + DECODE_IMPL_CONST(I64Const); + DECODE_IMPL_CONST(F32Const); + DECODE_IMPL_CONST(F64Const); + DECODE_IMPL_CONST(RefNull); DECODE_IMPL(RefIsNull); - DECODE_IMPL(RefFunc); + DECODE_IMPL_CONST(RefFunc); DECODE_IMPL(RefAsNonNull); DECODE_IMPL(LocalGet); DECODE_IMPL(LocalSet); DECODE_IMPL(LocalTee); DECODE_IMPL(Drop); - DECODE_IMPL(GlobalGet); + DECODE_IMPL_CONST(GlobalGet); DECODE_IMPL(GlobalSet); DECODE_IMPL(TableGet); DECODE_IMPL(TableSet); @@ -3409,9 +3452,9 @@ class WasmFullDecoder : public WasmDecoder { DECODE_IMPL(CallRef); DECODE_IMPL(ReturnCallRef); DECODE_IMPL2(kNumericPrefix, Numeric); - DECODE_IMPL2(kSimdPrefix, Simd); + DECODE_IMPL_CONST2(kSimdPrefix, Simd); DECODE_IMPL2(kAtomicPrefix, Atomic); - DECODE_IMPL2(kGCPrefix, GC); + DECODE_IMPL_CONST2(kGCPrefix, GC); #define SIMPLE_PROTOTYPE_CASE(name, opc, sig) DECODE_IMPL(name); FOREACH_SIMPLE_PROTOTYPE_OPCODE(SIMPLE_PROTOTYPE_CASE) #undef SIMPLE_PROTOTYPE_CASE @@ -3736,6 +3779,15 @@ class WasmFullDecoder : public WasmDecoder { } uint32_t DecodeSimdOpcode(WasmOpcode opcode, uint32_t opcode_length) { + if (decoding_mode == kInitExpression) { + // Currently, only s128.const is allowed in initializer expressions. + if (opcode != kExprS128Const) { + this->DecodeError("opcode %s is not allowed in init. expressions", + this->SafeOpcodeNameAt(this->pc())); + return 0; + } + return SimdConstOp(opcode_length); + } // opcode_length is the number of bytes that this SIMD-specific opcode takes // up in the LEB128 encoded form. switch (opcode) { @@ -3870,6 +3922,13 @@ class WasmFullDecoder : public WasmDecoder { this->module_); } +#define NON_CONST_ONLY \ + if (decoding_mode == kInitExpression) { \ + this->DecodeError("opcode %s is not allowed in init. expressions", \ + this->SafeOpcodeNameAt(this->pc())); \ + return 0; \ + } + int DecodeGCOpcode(WasmOpcode opcode, uint32_t opcode_length) { switch (opcode) { case kExprStructNewWithRtt: { @@ -3899,6 +3958,7 @@ class WasmFullDecoder : public WasmDecoder { return opcode_length + imm.length; } case kExprStructNewDefault: { + NON_CONST_ONLY StructIndexImmediate imm(this, this->pc_ + opcode_length); if (!this->Validate(this->pc_ + opcode_length, imm)) return 0; if (validate) { @@ -3934,6 +3994,7 @@ class WasmFullDecoder : public WasmDecoder { return opcode_length + imm.length; } case kExprStructGet: { + NON_CONST_ONLY FieldImmediate field(this, this->pc_ + opcode_length); if (!this->Validate(this->pc_ + opcode_length, field)) return 0; ValueType field_type = @@ -3957,6 +4018,7 @@ class WasmFullDecoder : public WasmDecoder { } case kExprStructGetU: case kExprStructGetS: { + NON_CONST_ONLY FieldImmediate field(this, this->pc_ + opcode_length); if (!this->Validate(this->pc_ + opcode_length, field)) return 0; ValueType field_type = @@ -3979,6 +4041,7 @@ class WasmFullDecoder : public WasmDecoder { return opcode_length + field.length; } case kExprStructSet: { + NON_CONST_ONLY FieldImmediate field(this, this->pc_ + opcode_length); if (!this->Validate(this->pc_ + opcode_length, field)) return 0; const StructType* struct_type = field.struct_imm.struct_type; @@ -3997,6 +4060,7 @@ class WasmFullDecoder : public WasmDecoder { return opcode_length + field.length; } case kExprArrayNewWithRtt: { + NON_CONST_ONLY ArrayIndexImmediate imm(this, this->pc_ + opcode_length); if (!this->Validate(this->pc_ + opcode_length, imm)) return 0; Value rtt = Peek(0, 2); @@ -4024,6 +4088,7 @@ class WasmFullDecoder : public WasmDecoder { return opcode_length + imm.length; } case kExprArrayNewDefault: { + NON_CONST_ONLY ArrayIndexImmediate imm(this, this->pc_ + opcode_length); if (!this->Validate(this->pc_ + opcode_length, imm)) return 0; if (!VALIDATE(imm.array_type->element_type().is_defaultable())) { @@ -4057,6 +4122,7 @@ class WasmFullDecoder : public WasmDecoder { } case kExprArrayGetS: case kExprArrayGetU: { + NON_CONST_ONLY ArrayIndexImmediate imm(this, this->pc_ + opcode_length); if (!this->Validate(this->pc_ + opcode_length, imm)) return 0; if (!VALIDATE(imm.array_type->element_type().is_packed())) { @@ -4077,6 +4143,7 @@ class WasmFullDecoder : public WasmDecoder { return opcode_length + imm.length; } case kExprArrayGet: { + NON_CONST_ONLY ArrayIndexImmediate imm(this, this->pc_ + opcode_length); if (!this->Validate(this->pc_ + opcode_length, imm)) return 0; if (!VALIDATE(!imm.array_type->element_type().is_packed())) { @@ -4096,6 +4163,7 @@ class WasmFullDecoder : public WasmDecoder { return opcode_length + imm.length; } case kExprArraySet: { + NON_CONST_ONLY ArrayIndexImmediate imm(this, this->pc_ + opcode_length); if (!this->Validate(this->pc_ + opcode_length, imm)) return 0; if (!VALIDATE(imm.array_type->mutability())) { @@ -4112,6 +4180,7 @@ class WasmFullDecoder : public WasmDecoder { return opcode_length + imm.length; } case kExprArrayLen: { + NON_CONST_ONLY ArrayIndexImmediate imm(this, this->pc_ + opcode_length); if (!this->Validate(this->pc_ + opcode_length, imm)) return 0; Value array_obj = Peek(0, 0, ValueType::Ref(imm.index, kNullable)); @@ -4122,6 +4191,7 @@ class WasmFullDecoder : public WasmDecoder { return opcode_length + imm.length; } case kExprArrayCopy: { + NON_CONST_ONLY CHECK_PROTOTYPE_OPCODE(gc_experiments); ArrayIndexImmediate dst_imm(this, this->pc_ + opcode_length); if (!this->Validate(this->pc_ + opcode_length, dst_imm)) return 0; @@ -4156,7 +4226,41 @@ class WasmFullDecoder : public WasmDecoder { Drop(5); return opcode_length + dst_imm.length + src_imm.length; } + case kExprArrayInit: { + CHECK_PROTOTYPE_OPCODE(gc_experiments); + if (decoding_mode != kInitExpression) { + this->DecodeError("array.init is only allowed in init. expressions"); + return 0; + } + ArrayIndexImmediate array_imm(this, + this->pc_ + opcode_length); + if (!this->Validate(this->pc_ + opcode_length, array_imm)) return 0; + IndexImmediate length_imm( + this, this->pc_ + opcode_length + array_imm.length, + "array.init length"); + uint32_t elem_count = length_imm.index; + if (!VALIDATE(elem_count <= kV8MaxWasmArrayInitLength)) { + this->DecodeError( + "Requested length %u for array.init too large, maximum is %zu", + length_imm.index, kV8MaxWasmArrayInitLength); + return 0; + } + ValueType element_type = array_imm.array_type->element_type(); + std::vector element_types(elem_count, + element_type.Unpacked()); + FunctionSig element_sig(0, elem_count, element_types.data()); + ArgVector elements = PeekArgs(&element_sig, 1); + Value rtt = Peek(0, elem_count, ValueType::Rtt(array_imm.index)); + Value result = + CreateValue(ValueType::Ref(array_imm.index, kNonNullable)); + CALL_INTERFACE_IF_OK_AND_REACHABLE(ArrayInit, array_imm, elements, rtt, + &result); + Drop(elem_count + 1); + Push(result); + return opcode_length + array_imm.length + length_imm.length; + } case kExprI31New: { + NON_CONST_ONLY Value input = Peek(0, 0, kWasmI32); Value value = CreateValue(kWasmI31Ref); CALL_INTERFACE_IF_OK_AND_REACHABLE(I31New, input, &value); @@ -4165,6 +4269,7 @@ class WasmFullDecoder : public WasmDecoder { return opcode_length; } case kExprI31GetS: { + NON_CONST_ONLY Value i31 = Peek(0, 0, kWasmI31Ref); Value value = CreateValue(kWasmI32); CALL_INTERFACE_IF_OK_AND_REACHABLE(I31GetS, i31, &value); @@ -4173,6 +4278,7 @@ class WasmFullDecoder : public WasmDecoder { return opcode_length; } case kExprI31GetU: { + NON_CONST_ONLY Value i31 = Peek(0, 0, kWasmI31Ref); Value value = CreateValue(kWasmI32); CALL_INTERFACE_IF_OK_AND_REACHABLE(I31GetU, i31, &value); @@ -4225,6 +4331,7 @@ class WasmFullDecoder : public WasmDecoder { return opcode_length + imm.length; } case kExprRefTest: { + NON_CONST_ONLY // "Tests whether {obj}'s runtime type is a runtime subtype of {rtt}." Value rtt = Peek(0, 1); Value obj = Peek(1, 0); @@ -4256,6 +4363,7 @@ class WasmFullDecoder : public WasmDecoder { return opcode_length; } case kExprRefCast: { + NON_CONST_ONLY Value rtt = Peek(0, 1); Value obj = Peek(1, 0); if (!VALIDATE(rtt.type.is_rtt() || rtt.type.is_bottom())) { @@ -4301,6 +4409,7 @@ class WasmFullDecoder : public WasmDecoder { return opcode_length; } case kExprBrOnCast: { + NON_CONST_ONLY BranchDepthImmediate branch_depth(this, this->pc_ + opcode_length); if (!this->Validate(this->pc_ + opcode_length, branch_depth, @@ -4358,6 +4467,7 @@ class WasmFullDecoder : public WasmDecoder { return opcode_length + branch_depth.length; } case kExprBrOnCastFail: { + NON_CONST_ONLY BranchDepthImmediate branch_depth(this, this->pc_ + opcode_length); if (!this->Validate(this->pc_ + opcode_length, branch_depth, @@ -4420,6 +4530,7 @@ class WasmFullDecoder : public WasmDecoder { } #define ABSTRACT_TYPE_CHECK(heap_type) \ case kExprRefIs##heap_type: { \ + NON_CONST_ONLY \ Value arg = Peek(0, 0, kWasmAnyRef); \ Value result = CreateValue(kWasmI32); \ CALL_INTERFACE_IF_OK_AND_REACHABLE(RefIs##heap_type, arg, &result); \ @@ -4435,6 +4546,7 @@ class WasmFullDecoder : public WasmDecoder { #define ABSTRACT_TYPE_CAST(heap_type) \ case kExprRefAs##heap_type: { \ + NON_CONST_ONLY \ Value arg = Peek(0, 0, kWasmAnyRef); \ Value result = \ CreateValue(ValueType::Ref(HeapType::k##heap_type, kNonNullable)); \ @@ -4452,6 +4564,7 @@ class WasmFullDecoder : public WasmDecoder { case kExprBrOnData: case kExprBrOnFunc: case kExprBrOnI31: { + NON_CONST_ONLY BranchDepthImmediate branch_depth(this, this->pc_ + opcode_length); if (!this->Validate(this->pc_ + opcode_length, branch_depth, @@ -4502,6 +4615,7 @@ class WasmFullDecoder : public WasmDecoder { case kExprBrOnNonData: case kExprBrOnNonFunc: case kExprBrOnNonI31: { + NON_CONST_ONLY BranchDepthImmediate branch_depth(this, this->pc_ + opcode_length); if (!this->Validate(this->pc_ + opcode_length, branch_depth, @@ -4547,6 +4661,7 @@ class WasmFullDecoder : public WasmDecoder { return 0; } } +#undef NON_CONST_ONLY uint32_t DecodeAtomicOpcode(WasmOpcode opcode, uint32_t opcode_length) { ValueType ret_type; @@ -4765,6 +4880,7 @@ class WasmFullDecoder : public WasmDecoder { } void PushMergeValues(Control* c, Merge* merge) { + if (decoding_mode == kInitExpression) return; DCHECK_EQ(c, &control_.back()); DCHECK(merge == &c->start_merge || merge == &c->end_merge); DCHECK_LE(stack_ + c->stack_depth, stack_end_); @@ -4833,7 +4949,8 @@ class WasmFullDecoder : public WasmDecoder { uint32_t limit = control_.back().stack_depth; if (V8_UNLIKELY(stack_size() <= limit + depth)) { // Peeking past the current control start in reachable code. - if (!VALIDATE(control_.back().unreachable())) { + if (!VALIDATE(decoding_mode == kFunctionBody && + control_.back().unreachable())) { NotEnoughArgumentsError(index); } return UnreachableValue(this->pc_); @@ -4872,7 +4989,12 @@ class WasmFullDecoder : public WasmDecoder { kStrictCounting = true }; - enum MergeType { kBranchMerge, kReturnMerge, kFallthroughMerge }; + enum MergeType { + kBranchMerge, + kReturnMerge, + kFallthroughMerge, + kInitExprMerge + }; // - If the current code is reachable, check if the current stack values are // compatible with {merge} based on their number and types. Disregard the @@ -4894,12 +5016,16 @@ class WasmFullDecoder : public WasmDecoder { constexpr const char* merge_description = merge_type == kBranchMerge ? "branch" - : merge_type == kReturnMerge ? "return" : "fallthru"; + : merge_type == kReturnMerge + ? "return" + : merge_type == kInitExprMerge ? "init. expression" + : "fallthru"; uint32_t arity = merge->arity; uint32_t actual = stack_size() - control_.back().stack_depth; // Here we have to check for !unreachable(), because we need to typecheck as // if the current code is reachable even if it is spec-only reachable. - if (V8_LIKELY(!control_.back().unreachable())) { + if (V8_LIKELY(decoding_mode == kInitExpression || + !control_.back().unreachable())) { if (V8_UNLIKELY(strict_count ? actual != drop_values + arity : actual < drop_values + arity)) { this->DecodeError("expected %u elements on the stack for %s, found %u", @@ -5086,6 +5212,7 @@ class WasmFullDecoder : public WasmDecoder { class EmptyInterface { public: static constexpr Decoder::ValidateFlag validate = Decoder::kFullValidation; + static constexpr DecodingMode decoding_mode = kFunctionBody; using Value = ValueBase; using Control = ControlBase; using FullDecoder = WasmFullDecoder; diff --git a/src/wasm/init-expr-interface.cc b/src/wasm/init-expr-interface.cc new file mode 100644 index 0000000000..52c45bd18b --- /dev/null +++ b/src/wasm/init-expr-interface.cc @@ -0,0 +1,143 @@ +// Copyright 2021 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. + +#include "src/wasm/init-expr-interface.h" + +#include "src/execution/isolate.h" +#include "src/handles/handles-inl.h" +#include "src/objects/fixed-array-inl.h" +#include "src/objects/oddball.h" +#include "src/roots/roots-inl.h" +#include "src/wasm/decoder.h" +#include "src/wasm/function-body-decoder-impl.h" +#include "src/wasm/wasm-objects.h" + +namespace v8 { +namespace internal { +namespace wasm { + +void InitExprInterface::I32Const(FullDecoder* decoder, Value* result, + int32_t value) { + if (isolate_ != nullptr) result->runtime_value = WasmValue(value); +} + +void InitExprInterface::I64Const(FullDecoder* decoder, Value* result, + int64_t value) { + if (isolate_ != nullptr) result->runtime_value = WasmValue(value); +} + +void InitExprInterface::F32Const(FullDecoder* decoder, Value* result, + float value) { + if (isolate_ != nullptr) result->runtime_value = WasmValue(value); +} + +void InitExprInterface::F64Const(FullDecoder* decoder, Value* result, + double value) { + if (isolate_ != nullptr) result->runtime_value = WasmValue(value); +} + +void InitExprInterface::S128Const(FullDecoder* decoder, + Simd128Immediate& imm, + Value* result) { + if (isolate_ == nullptr) return; + result->runtime_value = WasmValue(imm.value, kWasmS128); +} + +void InitExprInterface::RefNull(FullDecoder* decoder, ValueType type, + Value* result) { + if (isolate_ == nullptr) return; + result->runtime_value = WasmValue(isolate_->factory()->null_value(), type); +} + +void InitExprInterface::RefFunc(FullDecoder* decoder, uint32_t function_index, + Value* result) { + if (isolate_ != nullptr) { + auto function = WasmInstanceObject::GetOrCreateWasmExternalFunction( + isolate_, instance_, function_index); + result->runtime_value = WasmValue( + function, ValueType::Ref(module_->functions[function_index].sig_index, + kNonNullable)); + } else { + outer_module_->functions[function_index].declared = true; + } +} + +void InitExprInterface::GlobalGet(FullDecoder* decoder, Value* result, + const GlobalIndexImmediate& imm) { + if (isolate_ == nullptr) return; + const WasmGlobal& global = module_->globals[imm.index]; + result->runtime_value = + global.type.is_numeric() + ? WasmValue(GetRawUntaggedGlobalPtr(global), global.type) + : WasmValue(handle(tagged_globals_->get(global.offset), isolate_), + global.type); +} + +void InitExprInterface::StructNewWithRtt( + FullDecoder* decoder, const StructIndexImmediate& imm, + const Value& rtt, const Value args[], Value* result) { + if (isolate_ == nullptr) return; + std::vector field_values(imm.struct_type->field_count()); + for (size_t i = 0; i < field_values.size(); i++) { + field_values[i] = args[i].runtime_value; + } + result->runtime_value = + WasmValue(isolate_->factory()->NewWasmStruct( + imm.struct_type, field_values.data(), + Handle::cast(rtt.runtime_value.to_ref())), + ValueType::Ref(HeapType(imm.index), kNonNullable)); +} + +void InitExprInterface::ArrayInit(FullDecoder* decoder, + const ArrayIndexImmediate& imm, + const base::Vector& elements, + const Value& rtt, Value* result) { + if (isolate_ == nullptr) return; + std::vector element_values; + for (Value elem : elements) element_values.push_back(elem.runtime_value); + result->runtime_value = + WasmValue(isolate_->factory()->NewWasmArray( + imm.array_type, element_values, + Handle::cast(rtt.runtime_value.to_ref())), + ValueType::Ref(HeapType(imm.index), kNonNullable)); +} + +void InitExprInterface::RttCanon(FullDecoder* decoder, uint32_t type_index, + Value* result) { + if (isolate_ == nullptr) return; + result->runtime_value = WasmValue( + handle(instance_->managed_object_maps().get(type_index), isolate_), + ValueType::Rtt(type_index, 0)); +} + +void InitExprInterface::RttSub(FullDecoder* decoder, uint32_t type_index, + const Value& parent, Value* result, + WasmRttSubMode mode) { + if (isolate_ == nullptr) return; + ValueType type = parent.type.has_depth() + ? ValueType::Rtt(type_index, parent.type.depth() + 1) + : ValueType::Rtt(type_index); + result->runtime_value = + WasmValue(Handle::cast(AllocateSubRtt( + isolate_, instance_, type_index, + Handle::cast(parent.runtime_value.to_ref()), mode)), + type); +} + +void InitExprInterface::DoReturn(FullDecoder* decoder, + uint32_t /*drop_values*/) { + end_found_ = true; + // End decoding on "end". + decoder->set_end(decoder->pc() + 1); + if (isolate_ != nullptr) result_ = decoder->stack_value(1)->runtime_value; +} + +byte* InitExprInterface::GetRawUntaggedGlobalPtr(const WasmGlobal& global) { + return reinterpret_cast(untagged_globals_->backing_store()) + + global.offset; +} + +} // namespace wasm +} // namespace internal +} // namespace v8 diff --git a/src/wasm/init-expr-interface.h b/src/wasm/init-expr-interface.h new file mode 100644 index 0000000000..535d2286c6 --- /dev/null +++ b/src/wasm/init-expr-interface.h @@ -0,0 +1,97 @@ +// Copyright 2021 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. + +#if !V8_ENABLE_WEBASSEMBLY +#error This header should only be included if WebAssembly is enabled. +#endif // !V8_ENABLE_WEBASSEMBLY + +#ifndef V8_WASM_INIT_EXPR_INTERFACE_H_ +#define V8_WASM_INIT_EXPR_INTERFACE_H_ + +#include "src/wasm/decoder.h" +#include "src/wasm/function-body-decoder-impl.h" +#include "src/wasm/wasm-value.h" + +namespace v8 { +namespace internal { + +class WasmInstanceObject; +class JSArrayBuffer; + +namespace wasm { + +// An interface for WasmFullDecoder used to decode initializer expressions. This +// interface has two modes: only validation (when {isolate_ == nullptr}), which +// is used in module-decoder, and code-generation (when {isolate_ != nullptr}), +// which is used in module-instantiate. We merge two distinct functionalities +// in one class to reduce the number of WasmFullDecoder instantiations, and thus +// V8 binary code size. +class InitExprInterface { + public: + static constexpr Decoder::ValidateFlag validate = Decoder::kFullValidation; + static constexpr DecodingMode decoding_mode = kInitExpression; + + struct Value : public ValueBase { + WasmValue runtime_value; + + template + explicit Value(Args&&... args) V8_NOEXCEPT + : ValueBase(std::forward(args)...) {} + }; + + using Control = ControlBase; + using FullDecoder = + WasmFullDecoder; + + InitExprInterface(const WasmModule* module, Isolate* isolate, + Handle instance, + Handle tagged_globals, + Handle untagged_globals) + : module_(module), + outer_module_(nullptr), + isolate_(isolate), + instance_(instance), + tagged_globals_(tagged_globals), + untagged_globals_(untagged_globals) { + DCHECK_NOT_NULL(isolate); + } + + explicit InitExprInterface(WasmModule* outer_module) + : module_(nullptr), outer_module_(outer_module), isolate_(nullptr) {} + +#define EMPTY_INTERFACE_FUNCTION(name, ...) \ + V8_INLINE void name(FullDecoder* decoder, ##__VA_ARGS__) {} + INTERFACE_META_FUNCTIONS(EMPTY_INTERFACE_FUNCTION) + INTERFACE_NON_CONSTANT_FUNCTIONS(EMPTY_INTERFACE_FUNCTION) +#undef EMPTY_INTERFACE_FUNCTION + +#define DECLARE_INTERFACE_FUNCTION(name, ...) \ + void name(FullDecoder* decoder, ##__VA_ARGS__); + INTERFACE_CONSTANT_FUNCTIONS(DECLARE_INTERFACE_FUNCTION) +#undef DECLARE_INTERFACE_FUNCTION + + WasmValue result() { + DCHECK_NOT_NULL(isolate_); + return result_; + } + bool end_found() { return end_found_; } + + private: + byte* GetRawUntaggedGlobalPtr(const WasmGlobal& global); + + bool end_found_ = false; + WasmValue result_; + const WasmModule* module_; + WasmModule* outer_module_; + Isolate* isolate_; + Handle instance_; + Handle tagged_globals_; + Handle untagged_globals_; +}; + +} // namespace wasm +} // namespace internal +} // namespace v8 + +#endif // V8_WASM_INIT_EXPR_INTERFACE_H_ diff --git a/src/wasm/module-decoder.cc b/src/wasm/module-decoder.cc index 84c5fecc5a..2a64fd25dd 100644 --- a/src/wasm/module-decoder.cc +++ b/src/wasm/module-decoder.cc @@ -15,10 +15,12 @@ #include "src/utils/ostreams.h" #include "src/wasm/decoder.h" #include "src/wasm/function-body-decoder-impl.h" +#include "src/wasm/init-expr-interface.h" #include "src/wasm/struct-types.h" #include "src/wasm/wasm-constants.h" #include "src/wasm/wasm-engine.h" #include "src/wasm/wasm-limits.h" +#include "src/wasm/wasm-opcodes-inl.h" namespace v8 { namespace internal { @@ -669,8 +671,7 @@ class ModuleDecoderImpl : public Decoder { case kExternalGlobal: { // ===== Imported global ============================================= import->index = static_cast(module_->globals.size()); - module_->globals.push_back( - {kWasmVoid, false, WasmInitExpr(), {0}, true, false}); + module_->globals.push_back({kWasmVoid, false, {}, {0}, true, false}); WasmGlobal* global = &module_->globals.back(); global->type = consume_value_type(); global->mutability = consume_mutability(); @@ -780,9 +781,8 @@ class ModuleDecoderImpl : public Decoder { ValueType type = consume_value_type(); bool mutability = consume_mutability(); if (failed()) break; - WasmInitExpr init = consume_init_expr(module_.get(), type); - module_->globals.push_back( - {type, mutability, std::move(init), {0}, false, false}); + WireBytesRef init = consume_init_expr(module_.get(), type); + module_->globals.push_back({type, mutability, init, {0}, false, false}); } if (ok()) CalculateGlobalOffsets(module_.get()); } @@ -1009,7 +1009,7 @@ class ModuleDecoderImpl : public Decoder { bool is_active; uint32_t memory_index; - WasmInitExpr dest_addr; + WireBytesRef dest_addr; consume_data_segment_header(&is_active, &memory_index, &dest_addr); if (failed()) break; @@ -1383,7 +1383,7 @@ class ModuleDecoderImpl : public Decoder { return ok() ? result : nullptr; } - WasmInitExpr DecodeInitExprForTesting(ValueType expected) { + WireBytesRef DecodeInitExprForTesting(ValueType expected) { return consume_init_expr(module_.get(), expected); } @@ -1420,6 +1420,8 @@ class ModuleDecoderImpl : public Decoder { "not enough bits"); WasmError intermediate_error_; ModuleOrigin origin_; + AccountingAllocator allocator_; + Zone init_expr_zone_{&allocator_, "initializer expression zone"}; ValueType TypeOf(const WasmInitExpr& expr) { return expr.type(module_.get(), enabled_features_); @@ -1687,324 +1689,32 @@ class ModuleDecoderImpl : public Decoder { return true; } - WasmInitExpr consume_init_expr(WasmModule* module, ValueType expected) { - constexpr Decoder::ValidateFlag validate = Decoder::kFullValidation; - WasmOpcode opcode = kExprNop; - std::vector stack; - while (pc() < end() && opcode != kExprEnd) { - uint32_t len = 1; - opcode = static_cast(read_u8(pc(), "opcode")); - switch (opcode) { - case kExprGlobalGet: { - GlobalIndexImmediate imm(this, pc() + 1); - len = 1 + imm.length; - if (V8_UNLIKELY(imm.index >= module->globals.size())) { - errorf(pc() + 1, "Invalid global index: %u", imm.index); - return {}; - } - WasmGlobal* global = &module->globals[imm.index]; - if (V8_UNLIKELY(global->mutability)) { - error(pc() + 1, - "mutable globals cannot be used in initializer " - "expressions"); - return {}; - } - if (V8_UNLIKELY(!global->imported && !enabled_features_.has_gc())) { - error(pc() + 1, - "non-imported globals cannot be used in initializer " - "expressions"); - return {}; - } - stack.push_back(WasmInitExpr::GlobalGet(imm.index)); - break; - } - case kExprI32Const: { - ImmI32Immediate imm(this, pc() + 1); - stack.emplace_back(imm.value); - len = 1 + imm.length; - break; - } - case kExprF32Const: { - ImmF32Immediate imm(this, pc() + 1); - stack.emplace_back(imm.value); - len = 1 + imm.length; - break; - } - case kExprI64Const: { - ImmI64Immediate imm(this, pc() + 1); - stack.emplace_back(imm.value); - len = 1 + imm.length; - break; - } - case kExprF64Const: { - ImmF64Immediate imm(this, pc() + 1); - stack.emplace_back(imm.value); - len = 1 + imm.length; - break; - } - case kExprRefNull: { - if (V8_UNLIKELY(!enabled_features_.has_reftypes() && - !enabled_features_.has_eh())) { - errorf(pc(), - "invalid opcode 0x%x in initializer expression, enable with " - "--experimental-wasm-reftypes or --experimental-wasm-eh", - kExprRefNull); - return {}; - } - HeapTypeImmediate imm( - enabled_features_, this, pc() + 1, module_.get()); - if (V8_UNLIKELY(failed())) return {}; - len = 1 + imm.length; - stack.push_back( - WasmInitExpr::RefNullConst(imm.type.representation())); - break; - } - case kExprRefFunc: { - if (V8_UNLIKELY(!enabled_features_.has_reftypes())) { - errorf(pc(), - "invalid opcode 0x%x in initializer expression, enable with " - "--experimental-wasm-reftypes", - kExprRefFunc); - return {}; - } + WireBytesRef consume_init_expr(WasmModule* module, ValueType expected) { + FunctionBody body(FunctionSig::Build(&init_expr_zone_, {expected}, {}), + buffer_offset_, pc_, end_); + WasmFeatures detected; + WasmFullDecoder + decoder(&init_expr_zone_, module, enabled_features_, &detected, body, + module); - IndexImmediate imm(this, pc() + 1, - "function index"); - len = 1 + imm.length; - if (V8_UNLIKELY(module->functions.size() <= imm.index)) { - errorf(pc(), "invalid function index: %u", imm.index); - return {}; - } - stack.push_back(WasmInitExpr::RefFuncConst(imm.index)); - // Functions referenced in the globals section count as "declared". - module->functions[imm.index].declared = true; - break; - } - case kSimdPrefix: { - // No need to check for Simd in enabled_features_ here; we either - // failed to validate the global's type earlier, or will fail in - // the type check or stack height check at the end. - opcode = read_prefixed_opcode(pc(), &len); - if (V8_UNLIKELY(opcode != kExprS128Const)) { - errorf(pc(), "invalid SIMD opcode 0x%x in initializer expression", - opcode); - return {}; - } + uint32_t offset = this->pc_offset(); - Simd128Immediate imm(this, pc() + len); - len += kSimd128Size; - stack.emplace_back(imm.value); - break; - } - case kGCPrefix: { - // No need to check for GC in enabled_features_ here; we either - // failed to validate the global's type earlier, or will fail in - // the type check or stack height check at the end. - opcode = read_prefixed_opcode(pc(), &len); - switch (opcode) { - case kExprStructNewWithRtt: { - if (!V8_LIKELY(enabled_features_.has_gc_experiments())) { - error(pc(), - "invalid opcode struct.new_with_rtt in init. expression, " - "enable with --experimental-wasm-gc-experiments"); - return {}; - } - IndexImmediate imm(this, pc() + len, "struct index"); - if (!V8_LIKELY(module->has_struct(imm.index))) { - errorf(pc() + len, "invalid struct type index #%u", imm.index); - return {}; - } - len += imm.length; - const StructType* type = module->struct_type(imm.index); - if (!V8_LIKELY(stack.size() >= type->field_count() + 1)) { - errorf(pc(), - "not enough arguments on the stack for struct.new: " - "expected %u, found %zu", - type->field_count() + 1, stack.size()); - return {}; - } - std::vector arguments(type->field_count() + 1); - WasmInitExpr* stack_args = &stack.back() - type->field_count(); - for (uint32_t i = 0; i < type->field_count(); i++) { - WasmInitExpr& argument = stack_args[i]; - if (!IsSubtypeOf(TypeOf(argument), type->field(i).Unpacked(), - module)) { - errorf(pc(), "struct.new[%u]: expected %s, found %s instead", - i, type->field(i).name().c_str(), - TypeOf(argument).name().c_str()); - return {}; - } - arguments[i] = std::move(argument); - } - WasmInitExpr& rtt = stack.back(); - if (!IsSubtypeOf(TypeOf(rtt), ValueType::Rtt(imm.index), - module)) { - errorf(pc(), "struct.new[%u]: expected %s, found %s instead", - type->field_count(), - ValueType::Rtt(imm.index).name().c_str(), - TypeOf(rtt).name().c_str()); - return {}; - } - arguments[type->field_count()] = std::move(rtt); - for (uint32_t i = 0; i <= type->field_count(); i++) { - stack.pop_back(); - } - stack.push_back(WasmInitExpr::StructNewWithRtt( - imm.index, std::move(arguments))); - break; - } - case kExprArrayInit: { - if (!V8_LIKELY(enabled_features_.has_gc_experiments())) { - error(pc(), - "invalid opcode array.init in init. expression, enable " - "with --experimental-wasm-gc-experiments"); - return {}; - } - IndexImmediate array_imm(this, pc() + len, - "array index"); - if (!V8_LIKELY(module->has_array(array_imm.index))) { - errorf(pc() + len, "invalid array type index #%u", - array_imm.index); - return {}; - } - IndexImmediate length_imm( - this, pc() + len + array_imm.length, "array.init length"); - uint32_t elem_count = length_imm.index; - if (elem_count > kV8MaxWasmArrayInitLength) { - errorf(pc() + len + array_imm.length, - "Requested length %u for array.init too large, maximum " - "is %zu", - length_imm.index, kV8MaxWasmArrayInitLength); - return {}; - } - len += array_imm.length + length_imm.length; - const ArrayType* array_type = - module_->array_type(array_imm.index); - if (stack.size() < elem_count + 1) { - errorf(pc(), - "not enough arguments on the stack for array.init: " - "expected %u, found %zu", - elem_count + 1, stack.size()); - return {}; - } - std::vector arguments(elem_count + 1); - WasmInitExpr* stack_args = &stack.back() - elem_count; - for (uint32_t i = 0; i < elem_count; i++) { - WasmInitExpr& argument = stack_args[i]; - if (!IsSubtypeOf(TypeOf(argument), - array_type->element_type().Unpacked(), - module)) { - errorf(pc(), "array.init[%u]: expected %s, found %s instead", - i, array_type->element_type().name().c_str(), - TypeOf(argument).name().c_str()); - return {}; - } - arguments[i] = std::move(argument); - } - WasmInitExpr& rtt = stack.back(); - if (!IsSubtypeOf(TypeOf(rtt), ValueType::Rtt(array_imm.index), - module)) { - errorf(pc(), "array.init[%u]: expected %s, found %s instead", - elem_count, - ValueType::Rtt(array_imm.index).name().c_str(), - TypeOf(rtt).name().c_str()); - return {}; - } - arguments[elem_count] = std::move(rtt); - for (uint32_t i = 0; i <= elem_count; i++) { - stack.pop_back(); - } - stack.push_back(WasmInitExpr::ArrayInit(array_imm.index, - std::move(arguments))); - break; - } - case kExprRttCanon: { - IndexImmediate imm(this, pc() + len, "type index"); - if (V8_UNLIKELY(!module_->has_type(imm.index))) { - errorf(pc() + len, "type index %u is out of bounds", imm.index); - return {}; - } - len += imm.length; - stack.push_back(WasmInitExpr::RttCanon(imm.index)); - break; - } - case kExprRttFreshSub: - if (!V8_LIKELY(enabled_features_.has_gc_experiments())) { - error(pc(), - "rtt.fresh requires --experimental-wasm-gc-experiments"); - return {}; - } - V8_FALLTHROUGH; - case kExprRttSub: { - IndexImmediate imm(this, pc() + len, "type index"); - if (V8_UNLIKELY(!module_->has_type(imm.index))) { - errorf(pc() + len, "type index %u is out of bounds", imm.index); - return {}; - } - len += imm.length; - if (stack.empty()) { - errorf(pc(), "calling %s without arguments", - opcode == kExprRttSub ? "rtt.sub" : "rtt.fresh_sub"); - return {}; - } - WasmInitExpr parent = std::move(stack.back()); - stack.pop_back(); - ValueType parent_type = TypeOf(parent); - if (V8_UNLIKELY(!parent_type.is_rtt() || - !IsHeapSubtypeOf(imm.index, - parent_type.ref_index(), - module_.get()))) { - errorf(pc(), "%s requires a supertype rtt on stack", - opcode == kExprRttSub ? "rtt.sub" : "rtt.fresh_sub"); - return {}; - } - stack.push_back( - opcode == kExprRttSub - ? WasmInitExpr::RttSub(imm.index, std::move(parent)) - : WasmInitExpr::RttFreshSub(imm.index, - std::move(parent))); - break; - } - default: { - errorf(pc(), "invalid opcode 0x%x in initializer expression", - opcode); - return {}; - } - } - break; // case kGCPrefix - } - case kExprEnd: - break; - default: { - errorf(pc(), "invalid opcode 0x%x in initializer expression", opcode); - return {}; - } - } - pc_ += len; - } + decoder.DecodeFunctionBody(); - if (V8_UNLIKELY(pc() > end())) { - error(end(), "Initializer expression extending beyond code end"); - return {}; - } - if (V8_UNLIKELY(opcode != kExprEnd)) { - error(pc(), "Initializer expression is missing 'end'"); - return {}; - } - if (V8_UNLIKELY(stack.size() != 1)) { - errorf(pc(), - "Found 'end' in initializer expression, but %s expressions were " - "found on the stack", - stack.size() > 1 ? "more than one" : "no"); + this->pc_ = decoder.end(); + + if (decoder.failed()) { + error(decoder.error().offset(), decoder.error().message().c_str()); return {}; } - WasmInitExpr expr = std::move(stack.back()); - if (!IsSubtypeOf(TypeOf(expr), expected, module)) { - errorf(pc(), "type error in init expression, expected %s, got %s", - expected.name().c_str(), TypeOf(expr).name().c_str()); + if (!decoder.interface().end_found()) { + error("Initializer expression is missing 'end'"); + return {}; } - return expr; + + return {offset, static_cast(decoder.end() - decoder.start())}; } // Read a mutability flag @@ -2178,7 +1888,7 @@ class ModuleDecoderImpl : public Decoder { ValueType table_type = is_active ? module_->tables[table_index].type : kWasmBottom; - WasmInitExpr offset; + WireBytesRef offset; if (is_active) { offset = consume_init_expr(module_.get(), kWasmI32); // Failed to parse offset initializer, return early. @@ -2239,7 +1949,7 @@ class ModuleDecoderImpl : public Decoder { } void consume_data_segment_header(bool* is_active, uint32_t* index, - WasmInitExpr* offset) { + WireBytesRef* offset) { const byte* pos = pc(); uint32_t flag = consume_u32v("flag"); @@ -2437,7 +2147,7 @@ const FunctionSig* DecodeWasmSignatureForTesting(const WasmFeatures& enabled, return decoder.DecodeFunctionSignature(zone, start); } -WasmInitExpr DecodeWasmInitExprForTesting(const WasmFeatures& enabled, +WireBytesRef DecodeWasmInitExprForTesting(const WasmFeatures& enabled, const byte* start, const byte* end, ValueType expected) { ModuleDecoderImpl decoder(enabled, start, end, kWasmOrigin); diff --git a/src/wasm/module-decoder.h b/src/wasm/module-decoder.h index 41fac24e48..0a64326cff 100644 --- a/src/wasm/module-decoder.h +++ b/src/wasm/module-decoder.h @@ -171,7 +171,7 @@ V8_EXPORT_PRIVATE FunctionResult DecodeWasmFunctionForTesting( const WasmModule* module, const byte* function_start, const byte* function_end, Counters* counters); -V8_EXPORT_PRIVATE WasmInitExpr +V8_EXPORT_PRIVATE WireBytesRef DecodeWasmInitExprForTesting(const WasmFeatures& enabled, const byte* start, const byte* end, ValueType expected); diff --git a/src/wasm/module-instantiate.cc b/src/wasm/module-instantiate.cc index 0c764ebba4..fffd50f1cd 100644 --- a/src/wasm/module-instantiate.cc +++ b/src/wasm/module-instantiate.cc @@ -15,6 +15,7 @@ #include "src/tracing/trace-event.h" #include "src/utils/utils.h" #include "src/wasm/code-space-access.h" +#include "src/wasm/init-expr-interface.h" #include "src/wasm/module-compiler.h" #include "src/wasm/wasm-constants.h" #include "src/wasm/wasm-engine.h" @@ -22,6 +23,7 @@ #include "src/wasm/wasm-import-wrapper-cache.h" #include "src/wasm/wasm-module.h" #include "src/wasm/wasm-objects-inl.h" +#include "src/wasm/wasm-opcodes-inl.h" #include "src/wasm/wasm-subtyping.h" #include "src/wasm/wasm-value.h" @@ -387,7 +389,7 @@ class InstanceBuilder { // Process initialization of globals. void InitGlobals(Handle instance); - WasmValue EvaluateInitExpression(const WasmInitExpr& init, + WasmValue EvaluateInitExpression(WireBytesRef init, ValueType expected, Handle instance); // Process the exports, creating wrappers for functions, tables, memories, @@ -865,7 +867,8 @@ void InstanceBuilder::LoadDataSegments(Handle instance) { size_t dest_offset; if (module_->is_memory64) { uint64_t dest_offset_64 = - EvaluateInitExpression(segment.dest_addr, instance).to_u64(); + EvaluateInitExpression(segment.dest_addr, kWasmI64, instance) + .to_u64(); // Clamp to {std::numeric_limits::max()}, which is always an // invalid offset. DCHECK_GT(std::numeric_limits::max(), instance->memory_size()); @@ -873,7 +876,8 @@ void InstanceBuilder::LoadDataSegments(Handle instance) { dest_offset_64, uint64_t{std::numeric_limits::max()})); } else { dest_offset = - EvaluateInitExpression(segment.dest_addr, instance).to_u32(); + EvaluateInitExpression(segment.dest_addr, kWasmI32, instance) + .to_u32(); } if (!base::IsInBounds(dest_offset, size, instance->memory_size())) { @@ -1532,78 +1536,25 @@ T* InstanceBuilder::GetRawUntaggedGlobalPtr(const WasmGlobal& global) { } WasmValue InstanceBuilder::EvaluateInitExpression( - const WasmInitExpr& init, Handle instance) { - switch (init.kind()) { - case WasmInitExpr::kNone: - UNREACHABLE(); - case WasmInitExpr::kI32Const: - return WasmValue(init.immediate().i32_const); - case WasmInitExpr::kI64Const: - return WasmValue(init.immediate().i64_const); - case WasmInitExpr::kF32Const: - return WasmValue(init.immediate().f32_const); - case WasmInitExpr::kF64Const: - return WasmValue(init.immediate().f64_const); - case WasmInitExpr::kS128Const: - return WasmValue(Simd128(init.immediate().s128_const.data())); - case WasmInitExpr::kRefNullConst: - return WasmValue(handle(ReadOnlyRoots(isolate_).null_value(), isolate_), - init.type(module_, enabled_)); - case WasmInitExpr::kRefFuncConst: { - auto function = WasmInstanceObject::GetOrCreateWasmExternalFunction( - isolate_, instance, init.immediate().index); - return WasmValue(function, init.type(module_, enabled_)); - } - case WasmInitExpr::kGlobalGet: { - const WasmGlobal& global = module_->globals[init.immediate().index]; - if (global.type.is_numeric()) { - return WasmValue(GetRawUntaggedGlobalPtr(global), global.type); - } else { - return WasmValue(handle(tagged_globals_->get(global.offset), isolate_), - init.type(module_, enabled_)); - } - } - case WasmInitExpr::kStructNewWithRtt: { - const StructType* type = module_->struct_type(init.immediate().index); - std::vector fields(type->field_count()); - for (uint32_t i = 0; i < type->field_count(); i++) { - fields[i] = EvaluateInitExpression(init.operands()[i], instance); - } - auto rtt = Handle::cast( - EvaluateInitExpression(init.operands().back(), instance).to_ref()); - return WasmValue( - isolate_->factory()->NewWasmStruct(type, fields.data(), rtt), - init.type(module_, enabled_)); - } - case WasmInitExpr::kArrayInit: { - const ArrayType* type = module_->array_type(init.immediate().index); - std::vector elements(init.operands().size() - 1); - for (uint32_t i = 0; i < elements.size(); i++) { - elements[i] = EvaluateInitExpression(init.operands()[i], instance); - } - auto rtt = Handle::cast( - EvaluateInitExpression(init.operands().back(), instance).to_ref()); - return WasmValue(isolate_->factory()->NewWasmArray(type, elements, rtt), - init.type(module_, enabled_)); - } - case WasmInitExpr::kRttCanon: { - int map_index = init.immediate().index; - return WasmValue( - handle(instance->managed_object_maps().get(map_index), isolate_), - init.type(module_, enabled_)); - } - case WasmInitExpr::kRttSub: - case WasmInitExpr::kRttFreshSub: { - uint32_t type = init.immediate().index; - WasmValue parent = EvaluateInitExpression(init.operands()[0], instance); - return WasmValue(AllocateSubRtt(isolate_, instance, type, - Handle::cast(parent.to_ref()), - init.kind() == WasmInitExpr::kRttSub - ? WasmRttSubMode::kCanonicalize - : WasmRttSubMode::kFresh), - init.type(module_, enabled_)); - } - } + WireBytesRef init, ValueType expected, + Handle instance) { + AccountingAllocator allocator; + Zone zone(&allocator, "consume_init_expr"); + base::Vector module_bytes = + instance->module_object().native_module()->wire_bytes(); + FunctionBody body(FunctionSig::Build(&zone, {expected}, {}), init.offset(), + module_bytes.begin() + init.offset(), + module_bytes.begin() + init.end_offset()); + WasmFeatures detected; + // We use kFullValidation so we do not have to create another instance of + // WasmFullDecoder, which would cost us >50Kb binary code size. + WasmFullDecoder + decoder(&zone, module_, WasmFeatures::All(), &detected, body, module_, + isolate_, instance, tagged_globals_, untagged_globals_); + + decoder.DecodeFunctionBody(); + + return decoder.interface().result(); } // Process initialization of globals. @@ -1611,9 +1562,10 @@ void InstanceBuilder::InitGlobals(Handle instance) { for (const WasmGlobal& global : module_->globals) { if (global.mutability && global.imported) continue; // Happens with imported globals. - if (global.init.kind() == WasmInitExpr::kNone) continue; + if (!global.init.is_set()) continue; - WasmValue value = EvaluateInitExpression(global.init, instance); + WasmValue value = + EvaluateInitExpression(global.init, global.type, instance); if (global.type.is_reference()) { tagged_globals_->set(global.offset, *value.to_ref()); @@ -1832,16 +1784,16 @@ void InstanceBuilder::InitializeIndirectFunctionTables( } if (!table.type.is_defaultable()) { - // Function constant is currently the only viable initializer. - DCHECK(table.initial_value.kind() == WasmInitExpr::kRefFuncConst); - uint32_t func_index = table.initial_value.immediate().index; + WasmValue value = + EvaluateInitExpression(table.initial_value, table.type, instance); + DCHECK(WasmExportedFunction::IsWasmExportedFunction(*value.to_ref())); + auto wasm_external_function = + Handle::cast(value.to_ref()); + uint32_t func_index = wasm_external_function->function_index(); uint32_t sig_id = module_->canonicalized_type_ids[module_->functions[func_index] .sig_index]; - MaybeHandle wasm_external_function = - WasmInstanceObject::GetWasmExternalFunction(isolate_, instance, - func_index); auto table_object = handle( WasmTableObject::cast(instance->tables().get(table_index)), isolate_); for (uint32_t entry_index = 0; entry_index < table.initial_size; @@ -1857,8 +1809,7 @@ void InstanceBuilder::InitializeIndirectFunctionTables( WasmTableObject::SetFunctionTablePlaceholder( isolate_, table_object, entry_index, instance, func_index); } else { - table_object->entries().set( - entry_index, *wasm_external_function.ToHandleChecked()); + table_object->entries().set(entry_index, *wasm_external_function); } // UpdateDispatchTables() updates all other dispatch tables, since // we have not yet added the dispatch table we are currently building. @@ -1964,7 +1915,8 @@ void InstanceBuilder::LoadTableSegments(Handle instance) { uint32_t table_index = elem_segment.table_index; uint32_t dst = - EvaluateInitExpression(elem_segment.offset, instance).to_u32(); + EvaluateInitExpression(elem_segment.offset, kWasmI32, instance) + .to_u32(); uint32_t src = 0; size_t count = elem_segment.entries.size(); diff --git a/src/wasm/wasm-module.h b/src/wasm/wasm-module.h index 8b6ca84cf1..4749e6ad02 100644 --- a/src/wasm/wasm-module.h +++ b/src/wasm/wasm-module.h @@ -71,7 +71,7 @@ struct WasmFunction { struct WasmGlobal { ValueType type; // type of the global. bool mutability; // {true} if mutable. - WasmInitExpr init; // the initialization expression of the global. + WireBytesRef init; // the initialization expression of the global. union { uint32_t index; // index of imported mutable global. uint32_t offset; // offset into global memory (if not imported & mutable). @@ -95,13 +95,13 @@ struct WasmException { // Static representation of a wasm data segment. struct WasmDataSegment { // Construct an active segment. - explicit WasmDataSegment(WasmInitExpr dest_addr) + explicit WasmDataSegment(WireBytesRef dest_addr) : dest_addr(std::move(dest_addr)), active(true) {} // Construct a passive segment, which has no dest_addr. WasmDataSegment() : active(false) {} - WasmInitExpr dest_addr; // destination memory address of the data. + WireBytesRef dest_addr; // destination memory address of the data. WireBytesRef source; // start offset in the module bytes. bool active = true; // true if copied automatically during instantiation. }; @@ -109,7 +109,7 @@ struct WasmDataSegment { // Static representation of wasm element segment (table initializer). struct WasmElemSegment { // Construct an active segment. - WasmElemSegment(ValueType type, uint32_t table_index, WasmInitExpr offset) + WasmElemSegment(ValueType type, uint32_t table_index, WireBytesRef offset) : type(type), table_index(table_index), offset(std::move(offset)), @@ -134,7 +134,7 @@ struct WasmElemSegment { ValueType type; uint32_t table_index; - WasmInitExpr offset; + WireBytesRef offset; std::vector entries; enum Status { kStatusActive, // copied automatically during instantiation. @@ -377,7 +377,7 @@ struct WasmTable { bool has_maximum_size = false; // true if there is a maximum size. bool imported = false; // true if imported. bool exported = false; // true if exported. - WasmInitExpr initial_value; + WireBytesRef initial_value; }; inline bool is_asmjs_module(const WasmModule* module) { diff --git a/test/cctest/wasm/wasm-run-utils.cc b/test/cctest/wasm/wasm-run-utils.cc index 7b4a0e6a04..e9ebb3f076 100644 --- a/test/cctest/wasm/wasm-run-utils.cc +++ b/test/cctest/wasm/wasm-run-utils.cc @@ -325,7 +325,7 @@ const WasmGlobal* TestingModuleBuilder::AddGlobal(ValueType type) { byte size = type.element_size_bytes(); global_offset = (global_offset + size - 1) & ~(size - 1); // align test_module_->globals.push_back( - {type, true, WasmInitExpr(), {global_offset}, false, false}); + {type, true, {}, {global_offset}, false, false}); global_offset += size; // limit number of globals. CHECK_LT(global_offset, kMaxGlobalsSize); diff --git a/test/fuzzer/wasm-fuzzer-common.cc b/test/fuzzer/wasm-fuzzer-common.cc index 9bb49c1515..852cc783d1 100644 --- a/test/fuzzer/wasm-fuzzer-common.cc +++ b/test/fuzzer/wasm-fuzzer-common.cc @@ -194,6 +194,48 @@ std::ostream& operator<<(std::ostream& os, const WasmInitExpr& expr) { } return os << ")"; } + +// Appends an initializer expression encoded in {wire_bytes}, in the offset +// contained in {expr}. +// TODO(7748): Find a way to implement other expressions here. +void AppendInitExpr(std::ostream& os, ModuleWireBytes wire_bytes, + WireBytesRef expr) { + Decoder decoder(wire_bytes.module_bytes()); + const byte* pc = wire_bytes.module_bytes().begin() + expr.offset(); + uint32_t length; + os << "WasmInitExpr."; + switch (static_cast(pc[0])) { + case kExprGlobalGet: + os << "GlobalGet(" + << decoder.read_u32v(pc + 1, &length); + break; + case kExprI32Const: + os << "I32Const(" + << decoder.read_i32v(pc + 1, &length); + break; + case kExprI64Const: + os << "I64Const(" + << decoder.read_i64v(pc + 1, &length); + break; + case kExprF32Const: { + uint32_t result = decoder.read_u32(pc + 1); + os << "F32Const(" << bit_cast(result); + break; + } + case kExprF64Const: { + uint64_t result = decoder.read_u64(pc + 1); + os << "F64Const(" << bit_cast(result); + break; + } + case kExprRefFunc: + os << "RefFunc(" + << decoder.read_u32v(pc + 1, &length); + break; + default: + UNREACHABLE(); + } + os << ")"; +} } // namespace void GenerateTestCase(Isolate* isolate, ModuleWireBytes wire_bytes, @@ -249,7 +291,9 @@ void GenerateTestCase(Isolate* isolate, ModuleWireBytes wire_bytes, for (WasmGlobal& glob : module->globals) { os << "builder.addGlobal(" << ValueTypeToConstantName(glob.type) << ", " - << glob.mutability << ", " << glob.init << ");\n"; + << glob.mutability << ", "; + AppendInitExpr(os, wire_bytes, glob.init); + os << ");\n"; } // TODO(7748): Support array/struct types. @@ -287,7 +331,9 @@ void GenerateTestCase(Isolate* isolate, ModuleWireBytes wire_bytes, : "Declarative"; os << "builder.add" << status_str << "ElementSegment("; if (elem_segment.status == WasmElemSegment::kStatusActive) { - os << elem_segment.table_index << ", " << elem_segment.offset << ", "; + os << elem_segment.table_index << ", "; + AppendInitExpr(os, wire_bytes, elem_segment.offset); + os << ", "; } os << "["; for (uint32_t i = 0; i < elem_segment.entries.size(); i++) { diff --git a/test/unittests/wasm/function-body-decoder-unittest.cc b/test/unittests/wasm/function-body-decoder-unittest.cc index e8a9311f58..4d1b14d12b 100644 --- a/test/unittests/wasm/function-body-decoder-unittest.cc +++ b/test/unittests/wasm/function-body-decoder-unittest.cc @@ -82,8 +82,7 @@ class TestModuleBuilder { mod.origin = origin; } byte AddGlobal(ValueType type, bool mutability = true) { - mod.globals.push_back( - {type, mutability, WasmInitExpr(), {0}, false, false}); + mod.globals.push_back({type, mutability, {}, {0}, false, false}); CHECK_LE(mod.globals.size(), kMaxByteSizedLeb128); return static_cast(mod.globals.size() - 1); } diff --git a/test/unittests/wasm/module-decoder-unittest.cc b/test/unittests/wasm/module-decoder-unittest.cc index 97ead237ef..ae438cbd81 100644 --- a/test/unittests/wasm/module-decoder-unittest.cc +++ b/test/unittests/wasm/module-decoder-unittest.cc @@ -271,8 +271,6 @@ TEST_F(WasmModuleVerifyTest, OneGlobal) { EXPECT_EQ(kWasmI32, global->type); EXPECT_EQ(0u, global->offset); EXPECT_FALSE(global->mutability); - EXPECT_EQ(WasmInitExpr::kI32Const, global->init.kind()); - EXPECT_EQ(13, global->init.immediate().i32_const); } EXPECT_OFF_END_FAILURE(data, 1); @@ -293,8 +291,6 @@ TEST_F(WasmModuleVerifyTest, S128Global) { EXPECT_EQ(kWasmS128, global->type); EXPECT_EQ(0u, global->offset); EXPECT_FALSE(global->mutability); - EXPECT_EQ(WasmInitExpr::kS128Const, global->init.kind()); - EXPECT_EQ(global->init.immediate().s128_const, v); } TEST_F(WasmModuleVerifyTest, ExternRefGlobal) { @@ -335,13 +331,10 @@ TEST_F(WasmModuleVerifyTest, ExternRefGlobal) { const WasmGlobal* global = &result.value()->globals[0]; EXPECT_EQ(kWasmExternRef, global->type); EXPECT_FALSE(global->mutability); - EXPECT_EQ(WasmInitExpr::kRefNullConst, global->init.kind()); global = &result.value()->globals[1]; EXPECT_EQ(kWasmFuncRef, global->type); EXPECT_FALSE(global->mutability); - EXPECT_EQ(WasmInitExpr::kRefFuncConst, global->init.kind()); - EXPECT_EQ(uint32_t{1}, global->init.immediate().index); } } @@ -382,13 +375,10 @@ TEST_F(WasmModuleVerifyTest, FuncRefGlobal) { const WasmGlobal* global = &result.value()->globals[0]; EXPECT_EQ(kWasmFuncRef, global->type); EXPECT_FALSE(global->mutability); - EXPECT_EQ(WasmInitExpr::kRefNullConst, global->init.kind()); global = &result.value()->globals[1]; EXPECT_EQ(kWasmFuncRef, global->type); EXPECT_FALSE(global->mutability); - EXPECT_EQ(WasmInitExpr::kRefFuncConst, global->init.kind()); - EXPECT_EQ(uint32_t{1}, global->init.immediate().index); } } @@ -437,7 +427,6 @@ TEST_F(WasmModuleVerifyTest, ExternRefGlobalWithGlobalInit) { EXPECT_EQ(kWasmExternRef, global->type); EXPECT_FALSE(global->mutability); - EXPECT_EQ(WasmInitExpr::kGlobalGet, global->init.kind()); } } @@ -471,7 +460,6 @@ TEST_F(WasmModuleVerifyTest, NullGlobalWithGlobalInit) { EXPECT_EQ(kWasmExternRef, global->type); EXPECT_FALSE(global->mutability); - EXPECT_EQ(WasmInitExpr::kGlobalGet, global->init.kind()); } } @@ -516,9 +504,9 @@ TEST_F(WasmModuleVerifyTest, GlobalInitializer) { 1, // mutable kExprEnd) // -- }; - EXPECT_FAILURE_WITH_MSG(no_initializer, - "Found 'end' in initializer expression, but no " - "expressions were found on the stack"); + EXPECT_FAILURE_WITH_MSG( + no_initializer, + "expected 1 elements on the stack for init. expression, found 0"); static const byte too_many_initializers_no_end[] = { SECTION(Global, // -- @@ -539,9 +527,9 @@ TEST_F(WasmModuleVerifyTest, GlobalInitializer) { WASM_I32V_1(42), // one value is good WASM_I32V_1(43), // another value is too much kExprEnd)}; - EXPECT_FAILURE_WITH_MSG(too_many_initializers, - "Found 'end' in initializer expression, but more than" - " one expressions were found on the stack"); + EXPECT_FAILURE_WITH_MSG( + too_many_initializers, + "expected 1 elements on the stack for init. expression, found 2"); static const byte missing_end_opcode[] = { SECTION(Global, // -- @@ -760,14 +748,12 @@ TEST_F(WasmModuleVerifyTest, TwoGlobals) { EXPECT_EQ(kWasmF32, g0->type); EXPECT_EQ(0u, g0->offset); EXPECT_FALSE(g0->mutability); - EXPECT_EQ(WasmInitExpr::kF32Const, g0->init.kind()); const WasmGlobal* g1 = &result.value()->globals[1]; EXPECT_EQ(kWasmF64, g1->type); EXPECT_EQ(8u, g1->offset); EXPECT_TRUE(g1->mutability); - EXPECT_EQ(WasmInitExpr::kF64Const, g1->init.kind()); } EXPECT_OFF_END_FAILURE(data, 1); @@ -824,9 +810,9 @@ TEST_F(WasmModuleVerifyTest, RttCanonGlobalTypeError) { SECTION(Global, ENTRY_COUNT(1), WASM_RTT_WITH_DEPTH(1, 0), 1, WASM_RTT_CANON(0), kExprEnd)}; ModuleResult result = DecodeModule(data, data + sizeof(data)); - EXPECT_NOT_OK(result, - "type error in init expression, expected (rtt 1 0), got " - "(rtt 0 0)"); + EXPECT_NOT_OK( + result, + "type error in init. expression[0] (expected (rtt 1 0), got (rtt 0 0))"); } TEST_F(WasmModuleVerifyTest, GlobalRttSubOfCanon) { @@ -841,9 +827,7 @@ TEST_F(WasmModuleVerifyTest, GlobalRttSubOfCanon) { SECTION(Global, ENTRY_COUNT(1), WASM_RTT_WITH_DEPTH(1, 1), 1, WASM_RTT_SUB(1, WASM_RTT_CANON(0)), kExprEnd)}; ModuleResult result = DecodeModule(data, data + sizeof(data)); - WasmInitExpr expected = WasmInitExpr::RttSub(1, WasmInitExpr::RttCanon(0)); EXPECT_OK(result); - EXPECT_EQ(result.value()->globals.front().init, expected); } TEST_F(WasmModuleVerifyTest, GlobalRttFreshSubOfCanon) { @@ -860,10 +844,7 @@ TEST_F(WasmModuleVerifyTest, GlobalRttFreshSubOfCanon) { SECTION(Global, ENTRY_COUNT(1), WASM_RTT_WITH_DEPTH(1, 1), 1, WASM_RTT_FRESH_SUB(1, WASM_RTT_CANON(0)), kExprEnd)}; ModuleResult result = DecodeModule(data, data + sizeof(data)); - WasmInitExpr expected = - WasmInitExpr::RttFreshSub(1, WasmInitExpr::RttCanon(0)); EXPECT_OK(result); - EXPECT_EQ(result.value()->globals.front().init, expected); } TEST_F(WasmModuleVerifyTest, GlobalRttSubOfSubOfCanon) { @@ -878,10 +859,7 @@ TEST_F(WasmModuleVerifyTest, GlobalRttSubOfSubOfCanon) { SECTION(Global, ENTRY_COUNT(1), WASM_RTT_WITH_DEPTH(2, 1), 1, WASM_RTT_SUB(1, WASM_RTT_SUB(1, WASM_RTT_CANON(0))), kExprEnd)}; ModuleResult result = DecodeModule(data, data + sizeof(data)); - WasmInitExpr expected = WasmInitExpr::RttSub( - 1, WasmInitExpr::RttSub(1, WasmInitExpr::RttCanon(0))); EXPECT_OK(result); - EXPECT_EQ(result.value()->globals.front().init, expected); } TEST_F(WasmModuleVerifyTest, GlobalRttFreshSubOfSubOfCanon) { @@ -899,10 +877,7 @@ TEST_F(WasmModuleVerifyTest, GlobalRttFreshSubOfSubOfCanon) { WASM_RTT_FRESH_SUB(1, WASM_RTT_SUB(1, WASM_RTT_CANON(0))), kExprEnd)}; ModuleResult result = DecodeModule(data, data + sizeof(data)); - WasmInitExpr expected = WasmInitExpr::RttFreshSub( - 1, WasmInitExpr::RttSub(1, WasmInitExpr::RttCanon(0))); EXPECT_OK(result); - EXPECT_EQ(result.value()->globals.front().init, expected); } TEST_F(WasmModuleVerifyTest, GlobalRttFreshSubOfFreshSubOfCanon) { @@ -920,10 +895,7 @@ TEST_F(WasmModuleVerifyTest, GlobalRttFreshSubOfFreshSubOfCanon) { WASM_RTT_FRESH_SUB(1, WASM_RTT_FRESH_SUB(1, WASM_RTT_CANON(0))), kExprEnd)}; ModuleResult result = DecodeModule(data, data + sizeof(data)); - WasmInitExpr expected = WasmInitExpr::RttFreshSub( - 1, WasmInitExpr::RttFreshSub(1, WasmInitExpr::RttCanon(0))); EXPECT_OK(result); - EXPECT_EQ(result.value()->globals.front().init, expected); } TEST_F(WasmModuleVerifyTest, GlobalRttSubOfGlobal) { @@ -945,9 +917,7 @@ TEST_F(WasmModuleVerifyTest, GlobalRttSubOfGlobal) { SECTION(Global, ENTRY_COUNT(1), WASM_RTT_WITH_DEPTH(1, 1), 1, WASM_RTT_SUB(1, WASM_GLOBAL_GET(0)), kExprEnd)}; ModuleResult result = DecodeModule(data, data + sizeof(data)); - WasmInitExpr expected = WasmInitExpr::RttSub(1, WasmInitExpr::GlobalGet(0)); EXPECT_OK(result); - EXPECT_EQ(result.value()->globals[1].init, expected); } TEST_F(WasmModuleVerifyTest, GlobalRttFreshSubOfGlobal) { @@ -971,10 +941,7 @@ TEST_F(WasmModuleVerifyTest, GlobalRttFreshSubOfGlobal) { SECTION(Global, ENTRY_COUNT(1), WASM_RTT_WITH_DEPTH(1, 1), 1, WASM_RTT_FRESH_SUB(1, WASM_GLOBAL_GET(0)), kExprEnd)}; ModuleResult result = DecodeModule(data, data + sizeof(data)); - WasmInitExpr expected = - WasmInitExpr::RttFreshSub(1, WasmInitExpr::GlobalGet(0)); EXPECT_OK(result); - EXPECT_EQ(result.value()->globals[1].init, expected); } TEST_F(WasmModuleVerifyTest, GlobalRttSubOfGlobalTypeError) { @@ -994,7 +961,9 @@ TEST_F(WasmModuleVerifyTest, GlobalRttSubOfGlobalTypeError) { SECTION(Global, ENTRY_COUNT(1), WASM_RTT_WITH_DEPTH(1, 0), 1, WASM_RTT_SUB(0, WASM_GLOBAL_GET(0)), kExprEnd)}; ModuleResult result = DecodeModule(data, data + sizeof(data)); - EXPECT_NOT_OK(result, "rtt.sub requires a supertype rtt on stack"); + EXPECT_NOT_OK(result, + "rtt.sub[0] expected rtt for a supertype of type 0, found " + "global.get of type i32"); } TEST_F(WasmModuleVerifyTest, GlobalRttFreshSubOfGlobalTypeError) { @@ -1016,7 +985,9 @@ TEST_F(WasmModuleVerifyTest, GlobalRttFreshSubOfGlobalTypeError) { SECTION(Global, ENTRY_COUNT(1), WASM_RTT_WITH_DEPTH(1, 0), 1, WASM_RTT_FRESH_SUB(0, WASM_GLOBAL_GET(0)), kExprEnd)}; ModuleResult result = DecodeModule(data, data + sizeof(data)); - EXPECT_NOT_OK(result, "rtt.fresh_sub requires a supertype rtt on stack"); + EXPECT_NOT_OK(result, + "rtt.fresh_sub[0] expected rtt for a supertype of type 0, " + "found global.get of type i32"); } #if !V8_OS_FUCHSIA @@ -1031,7 +1002,9 @@ TEST_F(WasmModuleVerifyTest, GlobalRttSubIllegalParent) { SECTION(Global, ENTRY_COUNT(1), WASM_RTT_WITH_DEPTH(1, 1), 1, WASM_RTT_SUB(1, WASM_RTT_CANON(0)), kExprEnd)}; ModuleResult result = DecodeModule(data, data + sizeof(data)); - EXPECT_NOT_OK(result, "rtt.sub requires a supertype rtt on stack"); + EXPECT_NOT_OK(result, + "rtt.sub[0] expected rtt for a supertype of type 1, found " + "rtt.canon of type (rtt 0 0)"); } TEST_F(WasmModuleVerifyTest, GlobalRttFreshSubIllegalParent) { @@ -1047,7 +1020,9 @@ TEST_F(WasmModuleVerifyTest, GlobalRttFreshSubIllegalParent) { SECTION(Global, ENTRY_COUNT(1), WASM_RTT_WITH_DEPTH(1, 1), 1, WASM_RTT_FRESH_SUB(1, WASM_RTT_CANON(0)), kExprEnd)}; ModuleResult result = DecodeModule(data, data + sizeof(data)); - EXPECT_NOT_OK(result, "rtt.fresh_sub requires a supertype rtt on stack"); + EXPECT_NOT_OK(result, + "rtt.fresh_sub[0] expected rtt for a supertype of type 1, " + "found rtt.canon of type (rtt 0 0)"); } #endif // !V8_OS_FUCHSIA @@ -1061,9 +1036,9 @@ TEST_F(WasmModuleVerifyTest, RttSubGlobalTypeError) { SECTION(Global, ENTRY_COUNT(1), WASM_RTT_WITH_DEPTH(0, 0), 1, WASM_RTT_SUB(0, WASM_RTT_CANON(0)), kExprEnd)}; ModuleResult result = DecodeModule(data, data + sizeof(data)); - EXPECT_NOT_OK(result, - "type error in init expression, expected (rtt 0 0), got " - "(rtt 1 0)"); + EXPECT_NOT_OK( + result, + "type error in init. expression[0] (expected (rtt 0 0), got (rtt 1 0))"); } TEST_F(WasmModuleVerifyTest, RttFreshSubGlobalTypeError) { @@ -1078,9 +1053,9 @@ TEST_F(WasmModuleVerifyTest, RttFreshSubGlobalTypeError) { SECTION(Global, ENTRY_COUNT(1), WASM_RTT_WITH_DEPTH(0, 0), 1, WASM_RTT_FRESH_SUB(0, WASM_RTT_CANON(0)), kExprEnd)}; ModuleResult result = DecodeModule(data, data + sizeof(data)); - EXPECT_NOT_OK(result, - "type error in init expression, expected (rtt 0 0), got " - "(rtt 1 0)"); + EXPECT_NOT_OK( + result, + "type error in init. expression[0] (expected (rtt 0 0), got (rtt 1 0))"); } TEST_F(WasmModuleVerifyTest, StructNewInitExpr) { @@ -1103,7 +1078,7 @@ TEST_F(WasmModuleVerifyTest, StructNewInitExpr) { SECTION(Global, ENTRY_COUNT(3), // -- kI32Code, 0, // type, mutability WASM_INIT_EXPR_I32V_1(10), // -- - kRttCode, 0, 0, // type, mutability + kRttWithDepthCode, 1, 0, 0, // type, mutability WASM_RTT_SUB(0, WASM_RTT_CANON(0)), kExprEnd, // -- kRefCode, 0, 0, // type, mutability WASM_INIT_EXPR_STRUCT_NEW(0, WASM_GLOBAL_GET(0), @@ -1119,7 +1094,7 @@ TEST_F(WasmModuleVerifyTest, StructNewInitExpr) { WASM_INIT_EXPR_STRUCT_NEW(0, WASM_I32V(42), WASM_RTT_CANON(0)))}; EXPECT_FAILURE_WITH_MSG( type_error, - "type error in init expression, expected (ref 1), got (ref 0)"); + "type error in init. expression[0] (expected (ref 1), got (ref 0))"); static const byte subexpr_type_error[] = { SECTION(Type, ENTRY_COUNT(2), // -- @@ -1128,9 +1103,9 @@ TEST_F(WasmModuleVerifyTest, StructNewInitExpr) { SECTION(Global, ENTRY_COUNT(1), // -- kRefCode, 0, 0, // type, mutability WASM_INIT_EXPR_STRUCT_NEW(0, WASM_I32V(42), WASM_RTT_CANON(1)))}; - EXPECT_FAILURE_WITH_MSG( - subexpr_type_error, - "struct.new[1]: expected (rtt 0), found (rtt 0 1) instead"); + EXPECT_FAILURE_WITH_MSG(subexpr_type_error, + "struct.new_with_rtt[1] expected rtt with depth for " + "type 0, found rtt.canon of type (rtt 0 1)"); } TEST_F(WasmModuleVerifyTest, ArrayInitInitExpr) { @@ -1157,7 +1132,7 @@ TEST_F(WasmModuleVerifyTest, ArrayInitInitExpr) { WASM_INIT_EXPR_ARRAY_INIT(0, 1, WASM_I32V(42), WASM_RTT_CANON(0)))}; EXPECT_FAILURE_WITH_MSG( type_error, - "type error in init expression, expected (ref 1), got (ref 0)"); + "type error in init. expression[0] (expected (ref 1), got (ref 0))"); static const byte subexpr_type_error[] = { SECTION(Type, ENTRY_COUNT(1), WASM_ARRAY_DEF(kI64Code, true)), @@ -1165,8 +1140,9 @@ TEST_F(WasmModuleVerifyTest, ArrayInitInitExpr) { kRefCode, 0, 0, // type, mutability WASM_INIT_EXPR_ARRAY_INIT(0, 2, WASM_I64V(42), WASM_I32V(142), WASM_RTT_CANON(0)))}; - EXPECT_FAILURE_WITH_MSG(subexpr_type_error, - "array.init[1]: expected i64, found i32 instead"); + EXPECT_FAILURE_WITH_MSG( + subexpr_type_error, + "array.init[1] expected type i64, found i32.const of type i32"); static const byte length_error[] = { SECTION(Type, ENTRY_COUNT(1), WASM_ARRAY_DEF(kI16Code, true)), @@ -1176,7 +1152,7 @@ TEST_F(WasmModuleVerifyTest, ArrayInitInitExpr) { WASM_I32V(30), WASM_RTT_CANON(0)))}; EXPECT_FAILURE_WITH_MSG( length_error, - "not enough arguments on the stack for array.init: expected 11, found 4"); + "not enough arguments on the stack for array.init, expected 7 more"); } TEST_F(WasmModuleVerifyTest, EmptyStruct) { @@ -1587,9 +1563,6 @@ TEST_F(WasmModuleVerifyTest, DataSegmentWithImmutableImportedGlobal) { }; ModuleResult result = DecodeModule(data, data + sizeof(data)); EXPECT_OK(result); - WasmInitExpr expr = std::move(result.value()->data_segments.back()).dest_addr; - EXPECT_EQ(WasmInitExpr::kGlobalGet, expr.kind()); - EXPECT_EQ(1u, expr.immediate().index); } TEST_F(WasmModuleVerifyTest, DataSegmentWithMutableImportedGlobal) { @@ -1646,8 +1619,6 @@ TEST_F(WasmModuleVerifyTest, OneDataSegment) { const WasmDataSegment* segment = &result.value()->data_segments.back(); - EXPECT_EQ(WasmInitExpr::kI32Const, segment->dest_addr.kind()); - EXPECT_EQ(0x9BBAA, segment->dest_addr.immediate().i32_const); EXPECT_EQ(kDataSegmentSourceOffset, segment->source.offset()); EXPECT_EQ(3u, segment->source.length()); } @@ -1683,13 +1654,9 @@ TEST_F(WasmModuleVerifyTest, TwoDataSegments) { const WasmDataSegment* s0 = &result.value()->data_segments[0]; const WasmDataSegment* s1 = &result.value()->data_segments[1]; - EXPECT_EQ(WasmInitExpr::kI32Const, s0->dest_addr.kind()); - EXPECT_EQ(0x7FFEE, s0->dest_addr.immediate().i32_const); EXPECT_EQ(kDataSegment0SourceOffset, s0->source.offset()); EXPECT_EQ(4u, s0->source.length()); - EXPECT_EQ(WasmInitExpr::kI32Const, s1->dest_addr.kind()); - EXPECT_EQ(0x6DDCC, s1->dest_addr.immediate().i32_const); EXPECT_EQ(kDataSegment1SourceOffset, s1->source.offset()); EXPECT_EQ(10u, s1->source.length()); } @@ -2362,8 +2329,7 @@ TEST_F(WasmModuleVerifyTest, NonNullableTableNoInitializer) { kRefCode, 0, // table 1: type 5, 6)}; // table 1: limits - EXPECT_FAILURE_WITH_MSG(data, - "invalid opcode 0x6b in initializer expression"); + EXPECT_FAILURE(data); } TEST_F(WasmModuleVerifyTest, TieringCompilationHints) { @@ -3193,78 +3159,9 @@ TEST_F(WasmModuleVerifyTest, EmptyCodeSectionWithoutFunctionSection) { EXPECT_VERIFIES(data); } -class WasmInitExprDecodeTest : public TestWithZone { - public: - WasmInitExprDecodeTest() = default; - - WasmFeatures enabled_features_; - - WasmInitExpr DecodeInitExpr(const byte* start, const byte* end, - ValueType expected) { - return DecodeWasmInitExprForTesting(enabled_features_, start, end, - expected); - } -}; - -#define EXPECT_INIT_EXPR(Type, type, value, ...) \ - { \ - static const byte data[] = {__VA_ARGS__, kExprEnd}; \ - WasmInitExpr expr = \ - DecodeInitExpr(data, data + sizeof(data), kWasm##Type); \ - EXPECT_EQ(WasmInitExpr::k##Type##Const, expr.kind()); \ - EXPECT_EQ(value, expr.immediate().type##_const); \ - } - -#define EXPECT_INIT_EXPR_FAIL(value_type, ...) \ - { \ - static const byte data[] = {__VA_ARGS__, kExprEnd}; \ - WasmInitExpr expr = DecodeInitExpr(data, data + sizeof(data), value_type); \ - EXPECT_EQ(WasmInitExpr::kNone, expr.kind()); \ - } - -TEST_F(WasmInitExprDecodeTest, InitExpr_i32) { - EXPECT_INIT_EXPR(I32, i32, 33, WASM_I32V_1(33)); - EXPECT_INIT_EXPR(I32, i32, -21, WASM_I32V_1(-21)); - EXPECT_INIT_EXPR(I32, i32, 437, WASM_I32V_2(437)); - EXPECT_INIT_EXPR(I32, i32, 77777, WASM_I32V_3(77777)); -} - -TEST_F(WasmInitExprDecodeTest, InitExpr_f32) { - EXPECT_INIT_EXPR(F32, f32, static_cast(13.1), WASM_F32(13.1)); - EXPECT_INIT_EXPR(F32, f32, static_cast(-21.1), WASM_F32(-21.1)); - EXPECT_INIT_EXPR(F32, f32, static_cast(437.2), WASM_F32(437.2)); - EXPECT_INIT_EXPR(F32, f32, static_cast(77777.3), WASM_F32(77777.3)); -} - -TEST_F(WasmInitExprDecodeTest, InitExpr_i64) { - EXPECT_INIT_EXPR(I64, i64, 33, WASM_I64V_1(33)); - EXPECT_INIT_EXPR(I64, i64, -21, WASM_I64V_2(-21)); - EXPECT_INIT_EXPR(I64, i64, 437, WASM_I64V_5(437)); - EXPECT_INIT_EXPR(I64, i64, 77777, WASM_I64V_7(77777)); -} - -TEST_F(WasmInitExprDecodeTest, InitExpr_f64) { - EXPECT_INIT_EXPR(F64, f64, 83.22, WASM_F64(83.22)); - EXPECT_INIT_EXPR(F64, f64, -771.3, WASM_F64(-771.3)); - EXPECT_INIT_EXPR(F64, f64, 43703.0, WASM_F64(43703.0)); - EXPECT_INIT_EXPR(F64, f64, 77999.1, WASM_F64(77999.1)); -} - -TEST_F(WasmInitExprDecodeTest, InitExpr_ExternRef) { - WASM_FEATURE_SCOPE(reftypes); - static const byte data[] = {kExprRefNull, kExternRefCode, kExprEnd}; - WasmInitExpr expr = DecodeInitExpr(data, data + sizeof(data), kWasmExternRef); - EXPECT_EQ(WasmInitExpr::kRefNullConst, expr.kind()); -} - -TEST_F(WasmInitExprDecodeTest, InitExpr_illegal) { - EXPECT_INIT_EXPR_FAIL(kWasmI32, WASM_I32V_1(0), WASM_I32V_1(0)); - EXPECT_INIT_EXPR_FAIL(kWasmI32, WASM_LOCAL_GET(0)); - EXPECT_INIT_EXPR_FAIL(kWasmVoid, WASM_LOCAL_SET(0, WASM_I32V_1(0))); - EXPECT_INIT_EXPR_FAIL(kWasmI32, WASM_I32_ADD(WASM_I32V_1(0), WASM_I32V_1(0))); - EXPECT_INIT_EXPR_FAIL(kWasmI32, - WASM_IF_ELSE(WASM_ZERO, WASM_ZERO, WASM_ZERO)); -} +// TODO(manoskouk): Reintroduce tests deleted in +// https://chromium-review.googlesource.com/c/v8/v8/+/2972910 in some other +// form. TEST_F(WasmModuleVerifyTest, Multiple_Named_Sections) { static const byte data[] = {