[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}
This commit is contained in:
Manos Koukoutos 2021-06-29 15:00:01 +00:00 committed by V8 LUCI CQ
parent 444fdfdef6
commit 071a1acf32
13 changed files with 588 additions and 613 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,6 +2763,7 @@ class WasmFullDecoder : public WasmDecoder<validate> {
DECODE(End) {
DCHECK(!control_.empty());
if (decoding_mode == kFunctionBody) {
Control* c = &control_.back();
if (c->is_incomplete_try()) {
// Catch-less try, fall through to the implicit catch-all.
@ -2769,8 +2792,14 @@ class WasmFullDecoder : public WasmDecoder<validate> {
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
@ -3349,6 +3383,15 @@ class WasmFullDecoder : public WasmDecoder<validate> {
// freedom to use the same implementation for different opcodes.
#define DECODE_IMPL(opcode) DECODE_IMPL2(kExpr##opcode, opcode)
#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);
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);
uint32_t offset = this->pc_offset();
decoder.DecodeFunctionBody();
this->pc_ = decoder.end();
if (decoder.failed()) {
error(decoder.error().offset(), decoder.error().message().c_str());
return {};
}
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);
if (!decoder.interface().end_found()) {
error("Initializer expression is missing 'end'");
return {};
}
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;
}
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");
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());
}
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);
@ -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<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[] = {