[wasm-gc] Add new br_on_cast_fail variant taking a heap type immediate

Adds new `br_on_cast_fail <branch depth> <heap type>` instruction
with opcode 0xfb43.
The instruction branches if the cast fails. `null` is treated as a cast
failure (meaning the branch is taken).
The heap type may be any concreate heap type index or an abstract
type like `(ref null eq)`.

Bug: v8:7748
Change-Id: I97a78d6d0872703ab825016cab4e737f8f79995f
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/4084981
Reviewed-by: Manos Koukoutos <manoskouk@chromium.org>
Commit-Queue: Matthias Liedtke <mliedtke@chromium.org>
Cr-Commit-Position: refs/heads/main@{#84733}
This commit is contained in:
Matthias Liedtke 2022-12-08 15:13:57 +01:00 committed by V8 LUCI CQ
parent bf17a2c78f
commit c2a1261355
10 changed files with 365 additions and 13 deletions

View File

@ -6159,6 +6159,31 @@ class LiftoffCompiler {
}
}
void BrOnCastFailAbstract(FullDecoder* decoder, const Value& obj,
HeapType type, Value* result_on_fallthrough,
uint32_t depth) {
switch (type.representation()) {
case HeapType::kEq:
return BrOnNonEq(decoder, obj, result_on_fallthrough, depth);
case HeapType::kI31:
return BrOnNonI31(decoder, obj, result_on_fallthrough, depth);
case HeapType::kStruct:
return BrOnNonStruct(decoder, obj, result_on_fallthrough, depth);
case HeapType::kArray:
return BrOnNonArray(decoder, obj, result_on_fallthrough, depth);
case HeapType::kNone:
case HeapType::kNoExtern:
case HeapType::kNoFunc:
// TODO(mliedtke): This becomes reachable for `br_on_cast_fail null`.
UNREACHABLE();
case HeapType::kAny:
// Any may never need a cast as it is either implicitly convertible or
// never convertible for any given type.
default:
UNREACHABLE();
}
}
struct TypeCheck {
Register obj_reg = no_reg;
ValueType obj_type;
@ -6435,6 +6460,11 @@ class LiftoffCompiler {
br_depth);
}
void BrOnNonEq(FullDecoder* decoder, const Value& object,
Value* /* value_on_branch */, uint32_t br_depth) {
BrOnNonAbstractType<&LiftoffCompiler::EqCheck>(object, decoder, br_depth);
}
void StringNewWtf8(FullDecoder* decoder, const MemoryIndexImmediate& imm,
const unibrow::Utf8Variant variant, const Value& offset,
const Value& size, Value* result) {

View File

@ -1101,6 +1101,8 @@ struct ControlBase : public PcForErrors<ValidationTag::full_validation> {
Value* result_on_fallthrough, uint32_t depth) \
F(BrOnCastAbstract, const Value& obj, HeapType type, \
Value* result_on_branch, uint32_t depth, bool null_succeeds) \
F(BrOnCastFailAbstract, const Value& obj, HeapType type, \
Value* result_on_fallthrough, uint32_t depth) \
F(RefIsStruct, const Value& object, Value* result) \
F(RefIsEq, const Value& object, Value* result) \
F(RefIsI31, const Value& object, Value* result) \
@ -2207,6 +2209,7 @@ class WasmDecoder : public Decoder {
(ios.HeapType(imm), ...);
return length + branch.length + imm.length;
}
case kExprBrOnCastFailDeprecated:
case kExprBrOnCastDeprecated: {
BranchDepthImmediate branch(decoder, pc + length, validate);
IndexImmediate index(decoder, pc + length + branch.length,
@ -2437,6 +2440,7 @@ class WasmDecoder : public Decoder {
case kExprBrOnCast:
case kExprBrOnCastNull:
case kExprBrOnCastFail:
case kExprBrOnCastFailDeprecated:
case kExprBrOnCastDeprecated:
return {1, 1};
case kExprStructSet:
@ -5321,6 +5325,98 @@ class WasmFullDecoder : public WasmDecoder<ValidationTag, decoding_mode> {
return pc_offset;
}
case kExprBrOnCastFail: {
NON_CONST_ONLY
BranchDepthImmediate branch_depth(this, this->pc_ + opcode_length,
validate);
if (!this->Validate(this->pc_ + opcode_length, branch_depth,
control_.size())) {
return 0;
}
uint32_t pc_offset = opcode_length + branch_depth.length;
HeapTypeImmediate imm(this->enabled_, this, this->pc_ + pc_offset,
validate);
this->Validate(this->pc_ + opcode_length, imm);
if (!VALIDATE(this->ok())) return 0;
pc_offset += imm.length;
std::optional<Value> rtt;
HeapType target_type = imm.type;
if (imm.type.is_index()) {
rtt = CreateValue(ValueType::Rtt(imm.type.ref_index()));
CALL_INTERFACE_IF_OK_AND_REACHABLE(RttCanon, imm.type.ref_index(),
&rtt.value());
// Differently to other instructions we don't push the RTT yet.
}
Value obj = Peek(0);
if (!VALIDATE((obj.type.is_object_reference() &&
IsSameTypeHierarchy(obj.type.heap_type(), target_type,
this->module_)) ||
obj.type.is_bottom())) {
this->DecodeError(
obj.pc(),
"Invalid types for br_on_cast_fail: %s of type %s has to "
"be in the same reference type hierarchy as (ref %s)",
SafeOpcodeNameAt(obj.pc()), obj.type.name().c_str(),
target_type.name().c_str());
return 0;
}
Control* c = control_at(branch_depth.depth);
if (c->br_merge()->arity == 0) {
this->DecodeError(
"br_on_cast_fail must target a branch of arity at least 1");
return 0;
}
if (!VALIDATE(TypeCheckBranch<true>(c, 0))) return 0;
bool null_succeeds = false;
Value result_on_fallthrough = CreateValue(ValueType::Ref(target_type));
if (V8_LIKELY(current_code_reachable_and_ok_)) {
// This logic ensures that code generation can assume that functions
// can only be cast between compatible types.
if (V8_UNLIKELY(
TypeCheckAlwaysFails(obj, target_type, null_succeeds))) {
if (rtt.has_value()) {
CALL_INTERFACE(Drop); // rtt
}
// The types are incompatible (i.e. neither of the two types is a
// subtype of the other). Always branch.
CALL_INTERFACE(BrOrRet, branch_depth.depth, 0);
// We know that the following code is not reachable, but according
// to the spec it technically is. Set it to spec-only reachable.
SetSucceedingCodeDynamicallyUnreachable();
c->br_merge()->reached = true;
} else if (V8_UNLIKELY(TypeCheckAlwaysSucceeds(obj, target_type))) {
if (rtt.has_value()) {
CALL_INTERFACE(Drop); // rtt
}
// The branch can still be taken on null.
if (obj.type.is_nullable()) {
CALL_INTERFACE(BrOnNull, obj, branch_depth.depth, true,
&result_on_fallthrough);
c->br_merge()->reached = true;
}
// Otherwise, the type check always succeeds. Do not branch. Also,
// the object is already on the stack; do not manipulate the stack.
} else {
if (rtt.has_value()) {
CALL_INTERFACE(BrOnCastFail, obj, rtt.value(),
&result_on_fallthrough, branch_depth.depth);
} else {
CALL_INTERFACE(BrOnCastFailAbstract, obj, target_type,
&result_on_fallthrough, branch_depth.depth);
}
c->br_merge()->reached = true;
}
}
// Make sure the correct value is on the stack state on fallthrough.
Drop(obj);
Push(result_on_fallthrough);
return pc_offset;
}
case kExprBrOnCastFailDeprecated: {
NON_CONST_ONLY
BranchDepthImmediate branch_depth(this, this->pc_ + opcode_length,
validate);
@ -5350,12 +5446,7 @@ class WasmFullDecoder : public WasmDecoder<ValidationTag, decoding_mode> {
"br_on_cast_fail must target a branch of arity at least 1");
return 0;
}
// Attention: contrary to most other instructions, we modify the stack
// before calling the interface function. This makes it significantly
// more convenient to pass around the values that will be on the stack
// when the branch is taken. In this case, we leave {obj} on the stack
// to type check the branch.
// TODO(jkummerow): Reconsider this choice.
if (!VALIDATE(TypeCheckBranch<true>(c, 0))) return 0;
Value result_on_fallthrough = CreateValue(ValueType::Ref(imm.index));
if (V8_LIKELY(current_code_reachable_and_ok_)) {

View File

@ -1336,6 +1336,30 @@ class WasmGraphBuildingInterface {
}
}
void BrOnCastFailAbstract(FullDecoder* decoder, const Value& object,
HeapType type, Value* value_on_branch,
uint32_t br_depth) {
switch (type.representation()) {
case HeapType::kEq:
return BrOnNonEq(decoder, object, value_on_branch, br_depth);
case HeapType::kI31:
return BrOnNonI31(decoder, object, value_on_branch, br_depth);
case HeapType::kStruct:
return BrOnNonStruct(decoder, object, value_on_branch, br_depth);
case HeapType::kArray:
return BrOnNonArray(decoder, object, value_on_branch, br_depth);
case HeapType::kNone:
case HeapType::kNoExtern:
case HeapType::kNoFunc:
// TODO(mliedtke): Implement for br_on_cast_fail null.
case HeapType::kAny:
// Any may never need a cast as it is either implicitly convertible or
// never convertible for any given type.
default:
UNREACHABLE();
}
}
void RefIsEq(FullDecoder* decoder, const Value& object, Value* result) {
bool null_succeeds = false;
SetAndTypeNode(result,
@ -1350,6 +1374,17 @@ class WasmGraphBuildingInterface {
true, null_succeeds);
}
void BrOnNonEq(FullDecoder* decoder, const Value& object,
Value* value_on_fallthrough, uint32_t br_depth) {
bool null_succeeds = false;
// TODO(7748): Merge BrOn* and BrOnNon* instructions as their only
// difference is a boolean flag passed to BrOnCastAbs. This could also be
// leveraged to merge BrOnCastFailAbstract and BrOnCastAbstract.
BrOnCastAbs<&compiler::WasmGraphBuilder::BrOnEq>(
decoder, object, Value{nullptr, kWasmBottom}, value_on_fallthrough,
br_depth, false, null_succeeds);
}
void RefIsStruct(FullDecoder* decoder, const Value& object, Value* result) {
bool null_succeeds = false;
SetAndTypeNode(result,

View File

@ -716,7 +716,8 @@ bool V8_EXPORT_PRIVATE IsJSCompatibleSignature(const FunctionSig* sig,
V(BrOnCast, 0xfb42, _, "br_on_cast") \
V(BrOnCastNull, 0xfb4a, _, "br_on_cast null") \
V(BrOnCastDeprecated, 0xfb46, _, "br_on_cast") \
V(BrOnCastFail, 0xfb47, _, "br_on_cast_fail") \
V(BrOnCastFail, 0xfb43, _, "br_on_cast_fail") \
V(BrOnCastFailDeprecated, 0xfb47, _, "br_on_cast_fail") \
V(RefCastNop, 0xfb4c, _, "ref.cast_nop") \
V(RefIsStruct, 0xfb51, _, "ref.is_struct") \
V(RefIsI31, 0xfb52, _, "ref.is_i31") \

View File

@ -1373,14 +1373,14 @@ WASM_COMPILED_EXEC_TEST(RefTrivialCastsStatic) {
tester.sigs.i_v(), {refNull(subtype_index)},
{WASM_LOCAL_SET(0, WASM_STRUCT_NEW_DEFAULT(subtype_index)),
WASM_BLOCK_R(refNull(subtype_index), WASM_LOCAL_GET(0),
WASM_BR_ON_CAST_FAIL(0, sig_index), WASM_DROP,
WASM_BR_ON_CAST_FAIL_DEPRECATED(0, sig_index), WASM_DROP,
WASM_RETURN(WASM_I32V(0))),
WASM_DROP, WASM_I32V(1), WASM_END});
const byte kBrOnCastFailUnrelatedNull = tester.DefineFunction(
tester.sigs.i_v(), {},
{WASM_BLOCK_R(refNull(subtype_index), WASM_REF_NULL(subtype_index),
WASM_BR_ON_CAST_FAIL(0, sig_index), WASM_DROP,
WASM_BR_ON_CAST_FAIL_DEPRECATED(0, sig_index), WASM_DROP,
WASM_RETURN(WASM_I32V(0))),
WASM_DROP, WASM_I32V(1), WASM_END});
@ -1388,7 +1388,7 @@ WASM_COMPILED_EXEC_TEST(RefTrivialCastsStatic) {
tester.sigs.i_v(), {},
{WASM_BLOCK_R(refNull(subtype_index),
WASM_STRUCT_NEW_DEFAULT(subtype_index),
WASM_BR_ON_CAST_FAIL(0, sig_index), WASM_DROP,
WASM_BR_ON_CAST_FAIL_DEPRECATED(0, sig_index), WASM_DROP,
WASM_RETURN(WASM_I32V(0))),
WASM_DROP, WASM_I32V(1), WASM_END});

View File

@ -547,6 +547,9 @@ inline uint16_t ExtractPrefixedOpcodeBytes(WasmOpcode opcode) {
#define WASM_BR_ON_CAST_FAIL(depth, typeidx) \
WASM_GC_OP(kExprBrOnCastFail), static_cast<byte>(depth), \
static_cast<byte>(typeidx)
#define WASM_BR_ON_CAST_FAIL_DEPRECATED(depth, typeidx) \
WASM_GC_OP(kExprBrOnCastFailDeprecated), static_cast<byte>(depth), \
static_cast<byte>(typeidx)
#define WASM_GC_INTERNALIZE(extern) extern, WASM_GC_OP(kExprExternInternalize)
#define WASM_GC_EXTERNALIZE(ref) ref, WASM_GC_OP(kExprExternExternalize)

View File

@ -108,6 +108,20 @@ d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');
kExprI32Const, 1,
kExprReturn,
]).exportFunc();
builder.addFunction(`brOnCastFail${typeName}`,
makeSig([kWasmExternRef], [kWasmI32]))
.addBody([
kExprBlock, kAnyRefCode,
kExprLocalGet, 0,
kGCPrefix, kExprExternInternalize,
kGCPrefix, kExprBrOnCastFail, 0, typeCode,
kExprI32Const, 0,
kExprReturn,
kExprEnd,
kExprDrop,
kExprI32Const, 1,
kExprReturn,
]).exportFunc();
});
var instance = builder.instantiate();
@ -527,4 +541,86 @@ d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');
assertEquals(0, wasm.brOnCastNullNone(funcObj));
assertEquals(0, wasm.brOnCastNullNone(1));
assertEquals(0, wasm.brOnCastNullNone(jsObj));
// br_on_cast_fail
assertEquals(1, wasm.brOnCastFailStructSuper(null));
assertEquals(1, wasm.brOnCastFailStructSuper(undefined));
assertEquals(0, wasm.brOnCastFailStructSuper(structSuperObj));
assertEquals(0, wasm.brOnCastFailStructSuper(structSubObj));
assertEquals(1, wasm.brOnCastFailStructSuper(arrayObj));
assertEquals(1, wasm.brOnCastFailStructSuper(funcObj));
assertEquals(1, wasm.brOnCastFailStructSuper(1));
assertEquals(1, wasm.brOnCastFailStructSuper(jsObj));
assertEquals(1, wasm.brOnCastFailStructSub(null));
assertEquals(1, wasm.brOnCastFailStructSub(undefined));
assertEquals(1, wasm.brOnCastFailStructSub(structSuperObj));
assertEquals(0, wasm.brOnCastFailStructSub(structSubObj));
assertEquals(1, wasm.brOnCastFailStructSub(arrayObj));
assertEquals(1, wasm.brOnCastFailStructSub(funcObj));
assertEquals(1, wasm.brOnCastFailStructSub(1));
assertEquals(1, wasm.brOnCastFailStructSub(jsObj));
assertEquals(1, wasm.brOnCastFailArray(null));
assertEquals(1, wasm.brOnCastFailArray(undefined));
assertEquals(1, wasm.brOnCastFailArray(structSuperObj));
assertEquals(1, wasm.brOnCastFailArray(structSubObj));
assertEquals(0, wasm.brOnCastFailArray(arrayObj));
assertEquals(1, wasm.brOnCastFailArray(funcObj));
assertEquals(1, wasm.brOnCastFailArray(1));
assertEquals(1, wasm.brOnCastFailArray(jsObj));
assertEquals(1, wasm.brOnCastFailI31(null));
assertEquals(1, wasm.brOnCastFailI31(undefined));
assertEquals(1, wasm.brOnCastFailI31(structSuperObj));
assertEquals(1, wasm.brOnCastFailI31(structSubObj));
assertEquals(1, wasm.brOnCastFailI31(arrayObj));
assertEquals(1, wasm.brOnCastFailI31(funcObj));
assertEquals(0, wasm.brOnCastFailI31(1));
assertEquals(1, wasm.brOnCastFailI31(jsObj));
assertEquals(1, wasm.brOnCastFailAnyArray(null));
assertEquals(1, wasm.brOnCastFailAnyArray(undefined));
assertEquals(1, wasm.brOnCastFailAnyArray(structSuperObj));
assertEquals(1, wasm.brOnCastFailAnyArray(structSubObj));
assertEquals(0, wasm.brOnCastFailAnyArray(arrayObj));
assertEquals(1, wasm.brOnCastFailAnyArray(funcObj));
assertEquals(1, wasm.brOnCastFailAnyArray(1));
assertEquals(1, wasm.brOnCastFailAnyArray(jsObj));
assertEquals(1, wasm.brOnCastFailStruct(null));
assertEquals(1, wasm.brOnCastFailStruct(undefined));
assertEquals(0, wasm.brOnCastFailStruct(structSuperObj));
assertEquals(0, wasm.brOnCastFailStruct(structSubObj));
assertEquals(1, wasm.brOnCastFailStruct(arrayObj));
assertEquals(1, wasm.brOnCastFailStruct(funcObj));
assertEquals(1, wasm.brOnCastFailStruct(1));
assertEquals(1, wasm.brOnCastFailStruct(jsObj));
assertEquals(1, wasm.brOnCastFailEq(null));
assertEquals(1, wasm.brOnCastFailEq(undefined));
assertEquals(0, wasm.brOnCastFailEq(structSuperObj));
assertEquals(0, wasm.brOnCastFailEq(structSubObj));
assertEquals(0, wasm.brOnCastFailEq(arrayObj));
assertEquals(1, wasm.brOnCastFailEq(funcObj));
assertEquals(0, wasm.brOnCastFailEq(1));
assertEquals(1, wasm.brOnCastFailEq(jsObj));
assertEquals(1, wasm.brOnCastFailAny(null));
assertEquals(0, wasm.brOnCastFailAny(undefined));
assertEquals(0, wasm.brOnCastFailAny(structSuperObj));
assertEquals(0, wasm.brOnCastFailAny(structSubObj));
assertEquals(0, wasm.brOnCastFailAny(arrayObj));
assertEquals(0, wasm.brOnCastFailAny(funcObj));
assertEquals(0, wasm.brOnCastFailAny(1));
assertEquals(0, wasm.brOnCastFailAny(jsObj));
assertEquals(1, wasm.brOnCastFailNone(null));
assertEquals(1, wasm.brOnCastFailNone(undefined));
assertEquals(1, wasm.brOnCastFailNone(structSuperObj));
assertEquals(1, wasm.brOnCastFailNone(structSubObj));
assertEquals(1, wasm.brOnCastFailNone(arrayObj));
assertEquals(1, wasm.brOnCastFailNone(funcObj));
assertEquals(1, wasm.brOnCastFailNone(1));
assertEquals(1, wasm.brOnCastFailNone(jsObj));
})();

View File

@ -245,6 +245,20 @@ d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');
kExprI32Const, 1,
kExprReturn,
]).exportFunc();
builder.addFunction(`brOnCastFail_${name}`,
makeSig([kWasmFuncRef], [kWasmI32]))
.addBody([
kExprBlock, kFuncRefCode,
kExprLocalGet, 0,
kGCPrefix, kExprBrOnCastFail, 0, typeCode,
kExprI32Const, 0,
kExprReturn,
kExprEnd,
kExprDrop,
kExprI32Const, 1,
kExprReturn,
]).exportFunc();
}
let instance = builder.instantiate();
@ -291,6 +305,26 @@ d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');
assertEquals(0, wasm.brOnCastNull_nullfuncref(wasm.fctSub));
assertEquals(1, wasm.brOnCastNull_super(wasm.fctSub));
assertEquals(1, wasm.brOnCastNull_sub(wasm.fctSub));
assertEquals(1, wasm.brOnCastFail_funcref(null));
assertEquals(1, wasm.brOnCastFail_nullfuncref(null));
assertEquals(1, wasm.brOnCastFail_super(null));
assertEquals(1, wasm.brOnCastFail_sub(null));
assertEquals(0, wasm.brOnCastFail_funcref(jsFct));
assertEquals(1, wasm.brOnCastFail_nullfuncref(jsFct));
assertEquals(1, wasm.brOnCastFail_super(jsFct));
assertEquals(1, wasm.brOnCastFail_sub(jsFct));
assertEquals(0, wasm.brOnCastFail_funcref(wasm.fctSuper));
assertEquals(1, wasm.brOnCastFail_nullfuncref(wasm.fctSuper));
assertEquals(0, wasm.brOnCastFail_super(wasm.fctSuper));
assertEquals(1, wasm.brOnCastFail_sub(wasm.fctSuper));
assertEquals(0, wasm.brOnCastFail_funcref(wasm.fctSub));
assertEquals(1, wasm.brOnCastFail_nullfuncref(wasm.fctSub));
assertEquals(0, wasm.brOnCastFail_super(wasm.fctSub));
assertEquals(0, wasm.brOnCastFail_sub(wasm.fctSub));
})();
(function RefTestExternRef() {
@ -441,6 +475,35 @@ d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');
])
.exportFunc();
builder.addFunction('castFailToExternRef',
makeSig([kWasmExternRef], [kWasmI32]))
.addBody([
kExprBlock, kWasmRefNull, kExternRefCode,
kExprLocalGet, 0,
kGCPrefix, kExprBrOnCastFail, 0, kExternRefCode,
kExprI32Const, 0,
kExprReturn,
kExprEnd,
kExprDrop,
kExprI32Const, 1,
kExprReturn,
])
.exportFunc();
builder.addFunction('castFailToNullExternRef',
makeSig([kWasmExternRef], [kWasmI32]))
.addBody([
kExprBlock, kWasmRefNull, kExternRefCode,
kExprLocalGet, 0,
kGCPrefix, kExprBrOnCastFail, 0, kNullExternRefCode,
kExprI32Const, 0,
kExprReturn,
kExprEnd,
kExprDrop,
kExprI32Const, 1,
kExprReturn,
])
.exportFunc();
let instance = builder.instantiate();
let wasm = instance.exports;
let obj = {};
@ -468,6 +531,18 @@ d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');
assertEquals(0, wasm.castNullToNullExternRef(1));
assertEquals(0, wasm.castNullToNullExternRef(obj));
assertEquals(0, wasm.castNullToNullExternRef(wasm.castToExternRef));
assertEquals(1, wasm.castFailToExternRef(null));
assertEquals(0, wasm.castFailToExternRef(undefined));
assertEquals(0, wasm.castFailToExternRef(1));
assertEquals(0, wasm.castFailToExternRef(obj));
assertEquals(0, wasm.castFailToExternRef(wasm.castToExternRef));
assertEquals(1, wasm.castFailToNullExternRef(null));
assertEquals(1, wasm.castFailToNullExternRef(undefined));
assertEquals(1, wasm.castFailToNullExternRef(1));
assertEquals(1, wasm.castFailToNullExternRef(obj));
assertEquals(1, wasm.castFailToNullExternRef(wasm.castToExternRef));
})();
@ -653,6 +728,23 @@ d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');
kExprReturn,
])
.exportFunc();
let sourceHeapType = sourceType.heap_type ?? (sourceType & kLeb128Mask);
builder.addFunction(`brOnCastFail_${test.source}_to_${target}`,
makeSig([wasmRefType(creatorType)], [kWasmI32]))
.addBody([
kExprBlock, kWasmRefNull, sourceHeapType,
kExprLocalGet, 0,
kExprCallRef, ...wasmUnsignedLeb(creatorType),
kGCPrefix, kExprBrOnCastFail, 0, heapType,
kExprI32Const, 0,
kExprReturn,
kExprEnd,
kExprDrop,
kExprI32Const, 1,
kExprReturn,
])
.exportFunc();
}
}
@ -693,6 +785,10 @@ d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');
res = wasm[`brOnCastNull_${test.source}_to_${target}`](create_value);
assertEquals(
validValues.includes(value) || value == "nullref" ? 1 : 0, res);
print(`Test br_on_cast_fail: ${test.source}(${value}) -> ${target}`);
res = wasm[`brOnCastFail_${test.source}_to_${target}`](create_value);
assertEquals(!validValues.includes(value) || value == "nullref" ? 1 : 0, res);
}
}
}

View File

@ -518,7 +518,7 @@ let kExprRefCastDeprecated = 0x45;
let kExprBrOnCast = 0x42;
let kExprBrOnCastNull = 0x4a;
let kExprBrOnCastDeprecated = 0x46;
let kExprBrOnCastFail = 0x47;
let kExprBrOnCastFail = 0x43;
let kExprRefCastNop = 0x4c;
let kExprRefIsData = 0x51;
let kExprRefIsI31 = 0x52;

View File

@ -4474,8 +4474,8 @@ TEST_F(FunctionBodyDecoderTest, BrOnCastOrCastFail) {
ExpectFailure(
FunctionSig::Build(this->zone(), {supertype}, {kWasmExternRef}),
{WASM_LOCAL_GET(0), WASM_BR_ON_CAST_FAIL(0, sub_struct)}, kAppendEnd,
"br_on_cast_fail[0] expected subtype of (ref null func), (ref null "
"struct) or (ref null array), found local.get of type externref");
"Invalid types for br_on_cast_fail: local.get of type externref has to "
"be in the same reference type hierarchy as (ref 1)");
// Cast between types of different type hierarchies is invalid.
ExpectFailure(