Reland "[wasm] Refactor initializer expression handling"

This is a reland of 071a1acf32

Changes compared to original:
Expect SIMD test to fail if SIMD is not supported.

Original change's description:
> [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 <jkummerow@chromium.org>
> Commit-Queue: Manos Koukoutos <manoskouk@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#75476}

Bug: v8:11895
Change-Id: I2dface5ff28d5a2d439a65d3e5cb83135c061bb9
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2997722
Commit-Queue: Manos Koukoutos <manoskouk@chromium.org>
Reviewed-by: Jakob Kummerow <jkummerow@chromium.org>
Cr-Commit-Position: refs/heads/master@{#75492}
This commit is contained in:
Manos Koukoutos 2021-06-30 20:35:23 +00:00 committed by V8 LUCI CQ
parent a83fcd807c
commit 7981dc33d5
13 changed files with 597 additions and 618 deletions

View File

@ -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",

View File

@ -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",

View File

@ -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 <Decoder::ValidateFlag validate>
struct ImmI32Immediate {
@ -677,7 +679,8 @@ class BranchTableIterator {
const uint32_t table_count_; // the count of entries, not including default.
};
template <Decoder::ValidateFlag validate>
template <Decoder::ValidateFlag validate,
DecodingMode decoding_mode = kFunctionBody>
class WasmDecoder;
template <Decoder::ValidateFlag validate>
@ -1088,7 +1091,7 @@ struct ControlBase : public PcForErrors<validate> {
// Generic Wasm bytecode decoder with utilities for decoding immediates,
// lengths, etc.
template <Decoder::ValidateFlag validate>
template <Decoder::ValidateFlag validate, DecodingMode decoding_mode>
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<validate>& 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 <Decoder::ValidateFlag validate, typename Interface>
class WasmFullDecoder : public WasmDecoder<validate> {
template <Decoder::ValidateFlag validate, typename Interface,
DecodingMode decoding_mode = kFunctionBody>
class WasmFullDecoder : public WasmDecoder<validate, decoding_mode> {
using Value = typename Interface::Value;
using Control = typename Interface::Control;
using ArgVector = base::Vector<Value>;
@ -2122,8 +2143,9 @@ class WasmFullDecoder : public WasmDecoder<validate> {
WasmFullDecoder(Zone* zone, const WasmModule* module,
const WasmFeatures& enabled, WasmFeatures* detected,
const FunctionBody& body, InterfaceArgs&&... interface_args)
: WasmDecoder<validate>(zone, module, enabled, detected, body.sig,
body.start, body.end, body.offset),
: WasmDecoder<validate, decoding_mode>(zone, module, enabled, detected,
body.sig, body.start, body.end,
body.offset),
interface_(std::forward<InterfaceArgs>(interface_args)...),
control_(zone) {}
@ -2741,36 +2763,43 @@ class WasmFullDecoder : public WasmDecoder<validate> {
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<kStrictCounting, decoding_mode == kFunctionBody
? kFallthroughMerge
: kInitExprMerge>();
// 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<validate> {
// The result of the block is the return value.
trace_msg->Append("\n" TRACE_INST_FORMAT, startrel(this->pc_),
"(implicit) return");
DoReturn<kStrictCounting, kFallthroughMerge>();
control_.clear();
return 1;
}
@ -3339,6 +3367,12 @@ class WasmFullDecoder : public WasmDecoder<validate> {
#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<validate> {
// 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<validate> {
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<validate> {
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<validate> {
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<validate> {
}
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<validate> {
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<validate> {
return opcode_length + imm.length;
}
case kExprStructNewDefault: {
NON_CONST_ONLY
StructIndexImmediate<validate> 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<validate> {
return opcode_length + imm.length;
}
case kExprStructGet: {
NON_CONST_ONLY
FieldImmediate<validate> 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<validate> {
}
case kExprStructGetU:
case kExprStructGetS: {
NON_CONST_ONLY
FieldImmediate<validate> 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<validate> {
return opcode_length + field.length;
}
case kExprStructSet: {
NON_CONST_ONLY
FieldImmediate<validate> 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<validate> {
return opcode_length + field.length;
}
case kExprArrayNewWithRtt: {
NON_CONST_ONLY
ArrayIndexImmediate<validate> 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<validate> {
return opcode_length + imm.length;
}
case kExprArrayNewDefault: {
NON_CONST_ONLY
ArrayIndexImmediate<validate> 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<validate> {
}
case kExprArrayGetS:
case kExprArrayGetU: {
NON_CONST_ONLY
ArrayIndexImmediate<validate> 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<validate> {
return opcode_length + imm.length;
}
case kExprArrayGet: {
NON_CONST_ONLY
ArrayIndexImmediate<validate> 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<validate> {
return opcode_length + imm.length;
}
case kExprArraySet: {
NON_CONST_ONLY
ArrayIndexImmediate<validate> 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<validate> {
return opcode_length + imm.length;
}
case kExprArrayLen: {
NON_CONST_ONLY
ArrayIndexImmediate<validate> 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<validate> {
return opcode_length + imm.length;
}
case kExprArrayCopy: {
NON_CONST_ONLY
CHECK_PROTOTYPE_OPCODE(gc_experiments);
ArrayIndexImmediate<validate> 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<validate> {
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<validate> array_imm(this,
this->pc_ + opcode_length);
if (!this->Validate(this->pc_ + opcode_length, array_imm)) return 0;
IndexImmediate<validate> 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<ValueType> 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<validate> {
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<validate> {
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<validate> {
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<validate> {
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<validate> {
return opcode_length;
}
case kExprBrOnCast: {
NON_CONST_ONLY
BranchDepthImmediate<validate> branch_depth(this,
this->pc_ + opcode_length);
if (!this->Validate(this->pc_ + opcode_length, branch_depth,
@ -4358,6 +4467,7 @@ class WasmFullDecoder : public WasmDecoder<validate> {
return opcode_length + branch_depth.length;
}
case kExprBrOnCastFail: {
NON_CONST_ONLY
BranchDepthImmediate<validate> branch_depth(this,
this->pc_ + opcode_length);
if (!this->Validate(this->pc_ + opcode_length, branch_depth,
@ -4420,6 +4530,7 @@ class WasmFullDecoder : public WasmDecoder<validate> {
}
#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<validate> {
#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<validate> {
case kExprBrOnData:
case kExprBrOnFunc:
case kExprBrOnI31: {
NON_CONST_ONLY
BranchDepthImmediate<validate> branch_depth(this,
this->pc_ + opcode_length);
if (!this->Validate(this->pc_ + opcode_length, branch_depth,
@ -4502,6 +4615,7 @@ class WasmFullDecoder : public WasmDecoder<validate> {
case kExprBrOnNonData:
case kExprBrOnNonFunc:
case kExprBrOnNonI31: {
NON_CONST_ONLY
BranchDepthImmediate<validate> branch_depth(this,
this->pc_ + opcode_length);
if (!this->Validate(this->pc_ + opcode_length, branch_depth,
@ -4547,6 +4661,7 @@ class WasmFullDecoder : public WasmDecoder<validate> {
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<validate> {
}
void PushMergeValues(Control* c, Merge<Value>* 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<validate> {
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<validate> {
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<validate> {
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<validate> {
class EmptyInterface {
public:
static constexpr Decoder::ValidateFlag validate = Decoder::kFullValidation;
static constexpr DecodingMode decoding_mode = kFunctionBody;
using Value = ValueBase<validate>;
using Control = ControlBase<Value, validate>;
using FullDecoder = WasmFullDecoder<validate, EmptyInterface>;

View File

@ -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<validate>& 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<validate>& 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<validate>& imm,
const Value& rtt, const Value args[], Value* result) {
if (isolate_ == nullptr) return;
std::vector<WasmValue> 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<Map>::cast(rtt.runtime_value.to_ref())),
ValueType::Ref(HeapType(imm.index), kNonNullable));
}
void InitExprInterface::ArrayInit(FullDecoder* decoder,
const ArrayIndexImmediate<validate>& imm,
const base::Vector<Value>& elements,
const Value& rtt, Value* result) {
if (isolate_ == nullptr) return;
std::vector<WasmValue> 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<Map>::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<Object>::cast(AllocateSubRtt(
isolate_, instance_, type_index,
Handle<Map>::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<byte*>(untagged_globals_->backing_store()) +
global.offset;
}
} // namespace wasm
} // namespace internal
} // namespace v8

View File

@ -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<validate> {
WasmValue runtime_value;
template <typename... Args>
explicit Value(Args&&... args) V8_NOEXCEPT
: ValueBase(std::forward<Args>(args)...) {}
};
using Control = ControlBase<Value, validate>;
using FullDecoder =
WasmFullDecoder<validate, InitExprInterface, decoding_mode>;
InitExprInterface(const WasmModule* module, Isolate* isolate,
Handle<WasmInstanceObject> instance,
Handle<FixedArray> tagged_globals,
Handle<JSArrayBuffer> 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<WasmInstanceObject> instance_;
Handle<FixedArray> tagged_globals_;
Handle<JSArrayBuffer> untagged_globals_;
};
} // namespace wasm
} // namespace internal
} // namespace v8
#endif // V8_WASM_INIT_EXPR_INTERFACE_H_

View File

@ -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<uint32_t>(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<WasmInitExpr> stack;
while (pc() < end() && opcode != kExprEnd) {
uint32_t len = 1;
opcode = static_cast<WasmOpcode>(read_u8<validate>(pc(), "opcode"));
switch (opcode) {
case kExprGlobalGet: {
GlobalIndexImmediate<validate> 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<Decoder::kFullValidation> imm(this, pc() + 1);
stack.emplace_back(imm.value);
len = 1 + imm.length;
break;
}
case kExprF32Const: {
ImmF32Immediate<Decoder::kFullValidation> imm(this, pc() + 1);
stack.emplace_back(imm.value);
len = 1 + imm.length;
break;
}
case kExprI64Const: {
ImmI64Immediate<Decoder::kFullValidation> imm(this, pc() + 1);
stack.emplace_back(imm.value);
len = 1 + imm.length;
break;
}
case kExprF64Const: {
ImmF64Immediate<Decoder::kFullValidation> 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<Decoder::kFullValidation> 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::kFullValidation, InitExprInterface,
kInitExpression>
decoder(&init_expr_zone_, module, enabled_features_, &detected, body,
module);
IndexImmediate<Decoder::kFullValidation> 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<validate>(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<validate> 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<validate>(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<validate> 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<WasmInitExpr> 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<validate> 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<validate> 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<WasmInitExpr> 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<validate> 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<validate> 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<uint32_t>(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);

View File

@ -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);

View File

@ -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<WasmInstanceObject> instance);
WasmValue EvaluateInitExpression(const WasmInitExpr& init,
WasmValue EvaluateInitExpression(WireBytesRef init, ValueType expected,
Handle<WasmInstanceObject> instance);
// Process the exports, creating wrappers for functions, tables, memories,
@ -865,7 +867,8 @@ void InstanceBuilder::LoadDataSegments(Handle<WasmInstanceObject> 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<size_t>::max()}, which is always an
// invalid offset.
DCHECK_GT(std::numeric_limits<size_t>::max(), instance->memory_size());
@ -873,7 +876,8 @@ void InstanceBuilder::LoadDataSegments(Handle<WasmInstanceObject> instance) {
dest_offset_64, uint64_t{std::numeric_limits<size_t>::max()}));
} else {
dest_offset =
EvaluateInitExpression(segment.dest_addr, instance).to_u32();
EvaluateInitExpression(segment.dest_addr, kWasmI32, instance)
.to_u32();
}
if (!base::IsInBounds<size_t>(dest_offset, size, instance->memory_size())) {
@ -1532,78 +1536,25 @@ T* InstanceBuilder::GetRawUntaggedGlobalPtr(const WasmGlobal& global) {
}
WasmValue InstanceBuilder::EvaluateInitExpression(
const WasmInitExpr& init, Handle<WasmInstanceObject> 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<byte>(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<WasmValue> fields(type->field_count());
for (uint32_t i = 0; i < type->field_count(); i++) {
fields[i] = EvaluateInitExpression(init.operands()[i], instance);
}
auto rtt = Handle<Map>::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<WasmValue> elements(init.operands().size() - 1);
for (uint32_t i = 0; i < elements.size(); i++) {
elements[i] = EvaluateInitExpression(init.operands()[i], instance);
}
auto rtt = Handle<Map>::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<Map>::cast(parent.to_ref()),
init.kind() == WasmInitExpr::kRttSub
? WasmRttSubMode::kCanonicalize
: WasmRttSubMode::kFresh),
init.type(module_, enabled_));
}
}
WireBytesRef init, ValueType expected,
Handle<WasmInstanceObject> instance) {
AccountingAllocator allocator;
Zone zone(&allocator, "consume_init_expr");
base::Vector<const byte> 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::kFullValidation, InitExprInterface, kInitExpression>
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<WasmInstanceObject> 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<WasmExportedFunction>::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<WasmExternalFunction> 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<WasmInstanceObject> 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();

View File

@ -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<WasmInitExpr> 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) {

View File

@ -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);

View File

@ -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<WasmOpcode>(pc[0])) {
case kExprGlobalGet:
os << "GlobalGet("
<< decoder.read_u32v<Decoder::kNoValidation>(pc + 1, &length);
break;
case kExprI32Const:
os << "I32Const("
<< decoder.read_i32v<Decoder::kNoValidation>(pc + 1, &length);
break;
case kExprI64Const:
os << "I64Const("
<< decoder.read_i64v<Decoder::kNoValidation>(pc + 1, &length);
break;
case kExprF32Const: {
uint32_t result = decoder.read_u32<Decoder::kNoValidation>(pc + 1);
os << "F32Const(" << bit_cast<float>(result);
break;
}
case kExprF64Const: {
uint64_t result = decoder.read_u64<Decoder::kNoValidation>(pc + 1);
os << "F64Const(" << bit_cast<double>(result);
break;
}
case kExprRefFunc:
os << "RefFunc("
<< decoder.read_u32v<Decoder::kNoValidation>(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++) {

View File

@ -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<byte>(mod.globals.size() - 1);
}

View File

@ -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);
@ -288,13 +286,15 @@ TEST_F(WasmModuleVerifyTest, S128Global) {
0, // immutable
WASM_SIMD_CONSTANT(v.data()), kExprEnd)};
ModuleResult result = DecodeModule(data, data + sizeof(data));
EXPECT_OK(result);
const WasmGlobal* global = &result.value()->globals.back();
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);
if (!CheckHardwareSupportsSimd()) {
EXPECT_NOT_OK(result, "Wasm SIMD unsupported");
} else {
EXPECT_OK(result);
const WasmGlobal* global = &result.value()->globals.back();
EXPECT_EQ(kWasmS128, global->type);
EXPECT_EQ(0u, global->offset);
EXPECT_FALSE(global->mutability);
}
}
TEST_F(WasmModuleVerifyTest, ExternRefGlobal) {
@ -335,13 +335,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 +379,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 +431,6 @@ TEST_F(WasmModuleVerifyTest, ExternRefGlobalWithGlobalInit) {
EXPECT_EQ(kWasmExternRef, global->type);
EXPECT_FALSE(global->mutability);
EXPECT_EQ(WasmInitExpr::kGlobalGet, global->init.kind());
}
}
@ -471,7 +464,6 @@ TEST_F(WasmModuleVerifyTest, NullGlobalWithGlobalInit) {
EXPECT_EQ(kWasmExternRef, global->type);
EXPECT_FALSE(global->mutability);
EXPECT_EQ(WasmInitExpr::kGlobalGet, global->init.kind());
}
}
@ -516,9 +508,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 +531,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 +752,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 +814,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 +831,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 +848,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 +863,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 +881,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 +899,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 +921,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 +945,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 +965,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 +989,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 +1006,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 +1024,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 +1040,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 +1057,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 +1082,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 +1098,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 +1107,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 +1136,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 +1144,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 +1156,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 +1567,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 +1623,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 +1658,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 +2333,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 +3163,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<float>(13.1), WASM_F32(13.1));
EXPECT_INIT_EXPR(F32, f32, static_cast<float>(-21.1), WASM_F32(-21.1));
EXPECT_INIT_EXPR(F32, f32, static_cast<float>(437.2), WASM_F32(437.2));
EXPECT_INIT_EXPR(F32, f32, static_cast<float>(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[] = {