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

Adds new `br_on_cast null <branch depth> <heap type>` instruction
with opcode 0xfb4a.
The instruction branches on null.
The heap type may be any concreate heap type index or an abstract
type like `(ref null eq)`.

Bug: v8:7748
Change-Id: I0f1debacc80a304f7cfc262fd2cde7f43fc804d3
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/4075086
Commit-Queue: Matthias Liedtke <mliedtke@chromium.org>
Reviewed-by: Manos Koukoutos <manoskouk@chromium.org>
Cr-Commit-Position: refs/heads/main@{#84703}
This commit is contained in:
Matthias Liedtke 2022-12-07 10:34:23 +01:00 committed by V8 LUCI CQ
parent 90722a662c
commit ac4c5c468d
10 changed files with 440 additions and 149 deletions

View File

@ -5703,7 +5703,13 @@ void WasmGraphBuilder::BrOnEq(Node* object, Node* /*rtt*/,
BrOnCastAbs(match_control, match_effect, no_match_control, no_match_effect,
[=](Callbacks callbacks) -> void {
if (config.from.is_nullable()) {
callbacks.fail_if(gasm_->IsNull(object), BranchHint::kFalse);
if (config.to.is_nullable()) {
callbacks.succeed_if(gasm_->IsNull(object),
BranchHint::kFalse);
} else {
callbacks.fail_if(gasm_->IsNull(object),
BranchHint::kFalse);
}
}
callbacks.succeed_if(gasm_->IsI31(object), BranchHint::kFalse);
Node* map = gasm_->LoadMap(object);
@ -5747,7 +5753,7 @@ void WasmGraphBuilder::BrOnStruct(Node* object, Node* /*rtt*/,
Node** match_control, Node** match_effect,
Node** no_match_control,
Node** no_match_effect) {
bool null_succeeds = false;
bool null_succeeds = config.to.is_nullable();
BrOnCastAbs(match_control, match_effect, no_match_control, no_match_effect,
[=](Callbacks callbacks) -> void {
if (!v8_flags.wasm_gc_structref_as_dataref) {
@ -5787,7 +5793,7 @@ void WasmGraphBuilder::BrOnArray(Node* object, Node* /*rtt*/,
Node** match_control, Node** match_effect,
Node** no_match_control,
Node** no_match_effect) {
bool null_succeeds = false;
bool null_succeeds = config.to.is_nullable();
BrOnCastAbs(match_control, match_effect, no_match_control, no_match_effect,
[=](Callbacks callbacks) -> void {
return ManagedObjectInstanceCheck(
@ -5823,15 +5829,21 @@ Node* WasmGraphBuilder::RefAsI31(Node* object, wasm::WasmCodePosition position,
}
void WasmGraphBuilder::BrOnI31(Node* object, Node* /* rtt */,
WasmTypeCheckConfig /* config */,
Node** match_control, Node** match_effect,
Node** no_match_control,
WasmTypeCheckConfig config, Node** match_control,
Node** match_effect, Node** no_match_control,
Node** no_match_effect) {
gasm_->Branch(gasm_->IsI31(object), match_control, no_match_control,
BranchHint::kTrue);
SetControl(*no_match_control);
*match_effect = effect();
*no_match_effect = effect();
BrOnCastAbs(
match_control, match_effect, no_match_control, no_match_effect,
[=](Callbacks callbacks) -> void {
if (config.from.is_nullable()) {
if (config.to.is_nullable()) {
callbacks.succeed_if(gasm_->IsNull(object), BranchHint::kFalse);
} else {
callbacks.fail_if(gasm_->IsNull(object), BranchHint::kFalse);
}
}
callbacks.fail_if_not(gasm_->IsI31(object), BranchHint::kTrue);
});
}
Node* WasmGraphBuilder::TypeGuard(Node* value, wasm::ValueType type) {

View File

@ -6079,7 +6079,8 @@ class LiftoffCompiler {
}
void BrOnCast(FullDecoder* decoder, const Value& obj, const Value& rtt,
Value* /* result_on_branch */, uint32_t depth) {
Value* /* result_on_branch */, uint32_t depth,
bool null_succeeds) {
// Avoid having sequences of branches do duplicate work.
if (depth != decoder->control_depth() - 1) {
__ PrepareForBranch(decoder->control_at(depth)->br_merge()->arity, {});
@ -6095,8 +6096,9 @@ class LiftoffCompiler {
if (obj.type.is_nullable()) LoadNullValue(scratch_null, pinned);
FREEZE_STATE(frozen);
NullSucceeds null_handling = null_succeeds ? kNullSucceeds : kNullFails;
SubtypeCheck(decoder->module_, obj_reg.gp(), obj.type, rtt_reg.gp(),
rtt.type, scratch_null, scratch2, &cont_false, kNullFails,
rtt.type, scratch_null, scratch2, &cont_false, null_handling,
frozen);
BrOrRetImpl(decoder, depth, scratch_null, scratch2);
@ -6133,21 +6135,22 @@ class LiftoffCompiler {
}
void BrOnCastAbstract(FullDecoder* decoder, const Value& obj, HeapType type,
Value* result_on_branch, uint32_t depth) {
Value* result_on_branch, uint32_t depth,
bool null_succeeds) {
switch (type.representation()) {
case HeapType::kEq:
return BrOnEq(decoder, obj, result_on_branch, depth);
return BrOnEq(decoder, obj, result_on_branch, depth, null_succeeds);
case HeapType::kI31:
return BrOnI31(decoder, obj, result_on_branch, depth);
return BrOnI31(decoder, obj, result_on_branch, depth, null_succeeds);
case HeapType::kStruct:
return BrOnStruct(decoder, obj, result_on_branch, depth);
return BrOnStruct(decoder, obj, result_on_branch, depth, null_succeeds);
case HeapType::kArray:
return BrOnArray(decoder, obj, result_on_branch, depth);
return BrOnArray(decoder, obj, result_on_branch, depth, null_succeeds);
case HeapType::kNone:
case HeapType::kNoExtern:
case HeapType::kNoFunc:
// TODO(mliedtke): This becomes reachable for `br_on_cast null`.
UNREACHABLE();
DCHECK(null_succeeds);
return BrOnNull(decoder, obj, depth, true, nullptr);
case HeapType::kAny:
// Any may never need a cast as it is either implicitly convertible or
// never convertible for any given type.
@ -6341,19 +6344,24 @@ class LiftoffCompiler {
template <TypeChecker type_checker>
void BrOnAbstractType(const Value& object, FullDecoder* decoder,
uint32_t br_depth) {
bool null_succeeds = false; // TODO(mliedtke): Use parameter.
uint32_t br_depth, bool null_succeeds) {
// Avoid having sequences of branches do duplicate work.
if (br_depth != decoder->control_depth() - 1) {
__ PrepareForBranch(decoder->control_at(br_depth)->br_merge()->arity, {});
}
Label no_match;
Label no_match, match;
TypeCheck check(object.type, &no_match, null_succeeds);
Initialize(check, kPeek);
FREEZE_STATE(frozen);
if (null_succeeds && check.obj_type.is_nullable()) {
__ emit_cond_jump(kEqual, &match, kRefNull, check.obj_reg,
check.null_reg(), frozen);
}
(this->*type_checker)(check, frozen);
__ bind(&match);
BrOrRetImpl(decoder, br_depth, check.tmp1, check.tmp2);
__ bind(&no_match);
@ -6383,23 +6391,31 @@ class LiftoffCompiler {
}
void BrOnEq(FullDecoder* decoder, const Value& object,
Value* /* value_on_branch */, uint32_t br_depth) {
BrOnAbstractType<&LiftoffCompiler::EqCheck>(object, decoder, br_depth);
Value* /* value_on_branch */, uint32_t br_depth,
bool null_succeeds) {
BrOnAbstractType<&LiftoffCompiler::EqCheck>(object, decoder, br_depth,
null_succeeds);
}
void BrOnStruct(FullDecoder* decoder, const Value& object,
Value* /* value_on_branch */, uint32_t br_depth) {
BrOnAbstractType<&LiftoffCompiler::StructCheck>(object, decoder, br_depth);
Value* /* value_on_branch */, uint32_t br_depth,
bool null_succeeds) {
BrOnAbstractType<&LiftoffCompiler::StructCheck>(object, decoder, br_depth,
null_succeeds);
}
void BrOnI31(FullDecoder* decoder, const Value& object,
Value* /* value_on_branch */, uint32_t br_depth) {
BrOnAbstractType<&LiftoffCompiler::I31Check>(object, decoder, br_depth);
Value* /* value_on_branch */, uint32_t br_depth,
bool null_succeeds) {
BrOnAbstractType<&LiftoffCompiler::I31Check>(object, decoder, br_depth,
null_succeeds);
}
void BrOnArray(FullDecoder* decoder, const Value& object,
Value* /* value_on_branch */, uint32_t br_depth) {
BrOnAbstractType<&LiftoffCompiler::ArrayCheck>(object, decoder, br_depth);
Value* /* value_on_branch */, uint32_t br_depth,
bool null_succeeds) {
BrOnAbstractType<&LiftoffCompiler::ArrayCheck>(object, decoder, br_depth,
null_succeeds);
}
void BrOnNonStruct(FullDecoder* decoder, const Value& object,

View File

@ -1096,11 +1096,11 @@ struct ControlBase : public PcForErrors<ValidationTag::full_validation> {
F(AssertNullTypecheck, const Value& obj, Value* result) \
F(AssertNotNullTypecheck, const Value& obj, Value* result) \
F(BrOnCast, const Value& obj, const Value& rtt, Value* result_on_branch, \
uint32_t depth) \
uint32_t depth, bool null_succeeds) \
F(BrOnCastFail, const Value& obj, const Value& rtt, \
Value* result_on_fallthrough, uint32_t depth) \
F(BrOnCastAbstract, const Value& obj, HeapType type, \
Value* result_on_branch, uint32_t depth) \
Value* result_on_branch, uint32_t depth, bool null_succeeds) \
F(RefIsStruct, const Value& object, Value* result) \
F(RefIsEq, const Value& object, Value* result) \
F(RefIsI31, const Value& object, Value* result) \
@ -1109,9 +1109,11 @@ struct ControlBase : public PcForErrors<ValidationTag::full_validation> {
F(RefAsI31, const Value& object, Value* result) \
F(RefAsArray, const Value& object, Value* result) \
F(BrOnStruct, const Value& object, Value* value_on_branch, \
uint32_t br_depth) \
F(BrOnI31, const Value& object, Value* value_on_branch, uint32_t br_depth) \
F(BrOnArray, const Value& object, Value* value_on_branch, uint32_t br_depth) \
uint32_t br_depth, bool null_succeeds) \
F(BrOnI31, const Value& object, Value* value_on_branch, uint32_t br_depth, \
bool null_succeeds) \
F(BrOnArray, const Value& object, Value* value_on_branch, uint32_t br_depth, \
bool null_succeeds) \
F(BrOnNonStruct, const Value& object, Value* value_on_fallthrough, \
uint32_t br_depth) \
F(BrOnNonI31, const Value& object, Value* value_on_fallthrough, \
@ -2196,6 +2198,7 @@ class WasmDecoder : public Decoder {
return length + imm.length;
}
case kExprBrOnCast:
case kExprBrOnCastNull:
case kExprBrOnCastFail: {
BranchDepthImmediate branch(decoder, pc + length, validate);
HeapTypeImmediate imm(WasmFeatures::All(), decoder,
@ -2432,6 +2435,7 @@ class WasmDecoder : public Decoder {
case kExprRefCastDeprecated:
case kExprRefCastNop:
case kExprBrOnCast:
case kExprBrOnCastNull:
case kExprBrOnCastFail:
case kExprBrOnCastDeprecated:
return {1, 1};
@ -5142,7 +5146,8 @@ class WasmFullDecoder : public WasmDecoder<ValidationTag, decoding_mode> {
Push(value);
return opcode_length;
}
case kExprBrOnCast: {
case kExprBrOnCast:
case kExprBrOnCastNull: {
NON_CONST_ONLY
BranchDepthImmediate branch_depth(this, this->pc_ + opcode_length,
validate);
@ -5194,9 +5199,11 @@ class WasmFullDecoder : public WasmDecoder<ValidationTag, decoding_mode> {
// will be on the stack when the branch is taken.
// TODO(jkummerow): Reconsider this choice.
Drop(obj);
// TODO(mliedtke): Use RefNull for br_on_cast_null.
bool null_succeeds = false;
Push(CreateValue(ValueType::Ref(target_type)));
bool null_succeeds = opcode == kExprBrOnCastNull;
Push(CreateValue(ValueType::RefMaybeNull(
imm.type, (obj.type.is_bottom() || !null_succeeds)
? kNonNullable
: obj.type.nullability())));
// The {value_on_branch} parameter we pass to the interface must
// be pointer-identical to the object on the stack.
Value* value_on_branch = stack_value(1);
@ -5208,8 +5215,9 @@ class WasmFullDecoder : public WasmDecoder<ValidationTag, decoding_mode> {
if (rtt.has_value()) {
CALL_INTERFACE(Drop); // rtt
}
// The branch will still not be taken on null.
if (obj.type.is_nullable()) {
// The branch will still not be taken on null if not
// {null_succeeds}.
if (obj.type.is_nullable() && !null_succeeds) {
CALL_INTERFACE(BrOnNonNull, obj, value_on_branch,
branch_depth.depth, false);
} else {
@ -5224,10 +5232,11 @@ class WasmFullDecoder : public WasmDecoder<ValidationTag, decoding_mode> {
null_succeeds))) {
if (rtt.has_value()) {
CALL_INTERFACE(BrOnCast, obj, rtt.value(), value_on_branch,
branch_depth.depth);
branch_depth.depth, null_succeeds);
} else {
CALL_INTERFACE(BrOnCastAbstract, obj, target_type,
value_on_branch, branch_depth.depth);
value_on_branch, branch_depth.depth,
null_succeeds);
}
c->br_merge()->reached = true;
}
@ -5299,8 +5308,9 @@ class WasmFullDecoder : public WasmDecoder<ValidationTag, decoding_mode> {
}
c->br_merge()->reached = true;
} else if (V8_LIKELY(!TypeCheckAlwaysFails(obj, rtt))) {
bool null_succeeds = false;
CALL_INTERFACE(BrOnCast, obj, rtt, value_on_branch,
branch_depth.depth);
branch_depth.depth, null_succeeds);
c->br_merge()->reached = true;
}
// Otherwise the types are unrelated. Do not branch.
@ -5487,13 +5497,16 @@ class WasmFullDecoder : public WasmDecoder<ValidationTag, decoding_mode> {
// {result_on_branch} which was passed-by-value to {Push}.
Value* value_on_branch = stack_value(1);
if (V8_LIKELY(current_code_reachable_and_ok_)) {
bool null_succeeds = false;
if (opcode == kExprBrOnStruct) {
CALL_INTERFACE(BrOnStruct, obj, value_on_branch,
branch_depth.depth);
CALL_INTERFACE(BrOnStruct, obj, value_on_branch, branch_depth.depth,
null_succeeds);
} else if (opcode == kExprBrOnArray) {
CALL_INTERFACE(BrOnArray, obj, value_on_branch, branch_depth.depth);
CALL_INTERFACE(BrOnArray, obj, value_on_branch, branch_depth.depth,
null_succeeds);
} else {
CALL_INTERFACE(BrOnI31, obj, value_on_branch, branch_depth.depth);
CALL_INTERFACE(BrOnI31, obj, value_on_branch, branch_depth.depth,
null_succeeds);
}
c->br_merge()->reached = true;
}

View File

@ -1261,12 +1261,14 @@ class WasmGraphBuildingInterface {
TFNode**)>
void BrOnCastAbs(FullDecoder* decoder, const Value& object, const Value& rtt,
Value* forwarding_value, uint32_t br_depth,
bool branch_on_match) {
// TODO(mliedtke): Add generic br_on_cast instructions where null succeeds.
WasmTypeCheckConfig config = {object.type,
!rtt.type.is_bottom()
? ValueType::Ref(rtt.type.ref_index())
: kWasmBottom};
bool branch_on_match, bool null_succeeds) {
// If the type is bottom (used for abstract types), set HeapType to None.
// The heap type is not read but the null information is needed for the
// cast.
ValueType to_type = ValueType::RefMaybeNull(
!rtt.type.is_bottom() ? rtt.type.ref_index() : HeapType::kNone,
null_succeeds ? kNullable : kNonNullable);
WasmTypeCheckConfig config = {object.type, to_type};
SsaEnv* branch_env = Split(decoder->zone(), ssa_env_);
SsaEnv* no_branch_env = Steal(decoder->zone(), ssa_env_);
no_branch_env->SetNotMerged();
@ -1286,34 +1288,46 @@ class WasmGraphBuildingInterface {
}
void BrOnCast(FullDecoder* decoder, const Value& object, const Value& rtt,
Value* value_on_branch, uint32_t br_depth) {
Value* value_on_branch, uint32_t br_depth, bool null_succeeds) {
BrOnCastAbs<&compiler::WasmGraphBuilder::BrOnCast>(
decoder, object, rtt, value_on_branch, br_depth, true);
decoder, object, rtt, value_on_branch, br_depth, true, null_succeeds);
}
void BrOnCastFail(FullDecoder* decoder, const Value& object, const Value& rtt,
Value* value_on_fallthrough, uint32_t br_depth) {
bool null_succeeds = false;
BrOnCastAbs<&compiler::WasmGraphBuilder::BrOnCast>(
decoder, object, rtt, value_on_fallthrough, br_depth, false);
decoder, object, rtt, value_on_fallthrough, br_depth, false,
null_succeeds);
}
void BrOnCastAbstract(FullDecoder* decoder, const Value& object,
HeapType type, Value* value_on_branch,
uint32_t br_depth) {
uint32_t br_depth, bool null_succeeds) {
switch (type.representation()) {
case HeapType::kEq:
return BrOnEq(decoder, object, value_on_branch, br_depth);
return BrOnEq(decoder, object, value_on_branch, br_depth,
null_succeeds);
case HeapType::kI31:
return BrOnI31(decoder, object, value_on_branch, br_depth);
return BrOnI31(decoder, object, value_on_branch, br_depth,
null_succeeds);
case HeapType::kStruct:
return BrOnStruct(decoder, object, value_on_branch, br_depth);
return BrOnStruct(decoder, object, value_on_branch, br_depth,
null_succeeds);
case HeapType::kArray:
return BrOnArray(decoder, object, value_on_branch, br_depth);
return BrOnArray(decoder, object, value_on_branch, br_depth,
null_succeeds);
case HeapType::kNone:
case HeapType::kNoExtern:
case HeapType::kNoFunc:
// TODO(mliedtke): This becomes reachable for `br_on_cast null`.
UNREACHABLE();
DCHECK(null_succeeds);
// This is needed for BrOnNull. {value_on_branch} is on the value stack
// and BrOnNull interacts with the values on the stack.
// TODO(7748): The compiler shouldn't have to access the stack used by
// the decoder ideally.
SetAndTypeNode(value_on_branch,
builder_->TypeGuard(object.node, value_on_branch->type));
return BrOnNull(decoder, object, br_depth, true, value_on_branch);
case HeapType::kAny:
// Any may never need a cast as it is either implicitly convertible or
// never convertible for any given type.
@ -1330,10 +1344,10 @@ class WasmGraphBuildingInterface {
}
void BrOnEq(FullDecoder* decoder, const Value& object, Value* value_on_branch,
uint32_t br_depth) {
uint32_t br_depth, bool null_succeeds) {
BrOnCastAbs<&compiler::WasmGraphBuilder::BrOnEq>(
decoder, object, Value{nullptr, kWasmBottom}, value_on_branch, br_depth,
true);
true, null_succeeds);
}
void RefIsStruct(FullDecoder* decoder, const Value& object, Value* result) {
@ -1353,17 +1367,19 @@ class WasmGraphBuildingInterface {
}
void BrOnStruct(FullDecoder* decoder, const Value& object,
Value* value_on_branch, uint32_t br_depth) {
Value* value_on_branch, uint32_t br_depth,
bool null_succeeds) {
BrOnCastAbs<&compiler::WasmGraphBuilder::BrOnStruct>(
decoder, object, Value{nullptr, kWasmBottom}, value_on_branch, br_depth,
true);
true, null_succeeds);
}
void BrOnNonStruct(FullDecoder* decoder, const Value& object,
Value* value_on_fallthrough, uint32_t br_depth) {
bool null_succeeds = false;
BrOnCastAbs<&compiler::WasmGraphBuilder::BrOnStruct>(
decoder, object, Value{nullptr, kWasmBottom}, value_on_fallthrough,
br_depth, false);
br_depth, false, null_succeeds);
}
void RefIsArray(FullDecoder* decoder, const Value& object, Value* result) {
@ -1383,17 +1399,19 @@ class WasmGraphBuildingInterface {
}
void BrOnArray(FullDecoder* decoder, const Value& object,
Value* value_on_branch, uint32_t br_depth) {
Value* value_on_branch, uint32_t br_depth,
bool null_succeeds) {
BrOnCastAbs<&compiler::WasmGraphBuilder::BrOnArray>(
decoder, object, Value{nullptr, kWasmBottom}, value_on_branch, br_depth,
true);
true, null_succeeds);
}
void BrOnNonArray(FullDecoder* decoder, const Value& object,
Value* value_on_fallthrough, uint32_t br_depth) {
bool null_succeeds = false;
BrOnCastAbs<&compiler::WasmGraphBuilder::BrOnArray>(
decoder, object, Value{nullptr, kWasmBottom}, value_on_fallthrough,
br_depth, false);
br_depth, false, null_succeeds);
}
void RefIsI31(FullDecoder* decoder, const Value& object, Value* result) {
@ -1410,17 +1428,18 @@ class WasmGraphBuildingInterface {
}
void BrOnI31(FullDecoder* decoder, const Value& object,
Value* value_on_branch, uint32_t br_depth) {
Value* value_on_branch, uint32_t br_depth, bool null_succeeds) {
BrOnCastAbs<&compiler::WasmGraphBuilder::BrOnI31>(
decoder, object, Value{nullptr, kWasmBottom}, value_on_branch, br_depth,
true);
true, null_succeeds);
}
void BrOnNonI31(FullDecoder* decoder, const Value& object,
Value* value_on_fallthrough, uint32_t br_depth) {
bool null_succeeds = false;
BrOnCastAbs<&compiler::WasmGraphBuilder::BrOnI31>(
decoder, object, Value{nullptr, kWasmBottom}, value_on_fallthrough,
br_depth, false);
br_depth, false, null_succeeds);
}
void StringNewWtf8(FullDecoder* decoder, const MemoryIndexImmediate& memory,

View File

@ -714,6 +714,7 @@ bool V8_EXPORT_PRIVATE IsJSCompatibleSignature(const FunctionSig* sig,
V(RefCastNull, 0xfb49, _, "ref.cast null") \
V(RefCastDeprecated, 0xfb45, _, "ref.cast") \
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(RefCastNop, 0xfb4c, _, "ref.cast_nop") \

View File

@ -592,6 +592,29 @@ WASM_COMPILED_EXEC_TEST(BrOnCast) {
WASM_GC_OP(kExprStructGet), type_index, 0, WASM_LOCAL_GET(0),
kExprI32Add, kExprEnd});
const byte kTestStructStaticNull = tester.DefineFunction(
tester.sigs.i_v(), {kWasmI32, kWasmStructRef},
{WASM_BLOCK_R(
ValueType::RefNull(type_index), WASM_LOCAL_SET(0, WASM_I32V(111)),
// Pipe a struct through a local so it's statically typed as
// structref.
WASM_LOCAL_SET(1, WASM_STRUCT_NEW(other_type_index, WASM_F32(1.0))),
WASM_LOCAL_GET(1),
// The type check fails, so this branch isn't taken.
WASM_BR_ON_CAST(0, type_index), WASM_DROP,
WASM_LOCAL_SET(0, WASM_I32V(221)), // (Final result) - 1
WASM_LOCAL_SET(1, WASM_STRUCT_NEW(type_index, WASM_I32V(1))),
WASM_LOCAL_GET(1),
// This branch is taken.
WASM_BR_ON_CAST_NULL(0, type_index), WASM_GC_OP(kExprRefCast),
type_index,
// Not executed due to the branch.
WASM_LOCAL_SET(0, WASM_I32V(333))),
WASM_GC_OP(kExprStructGet), type_index, 0, WASM_LOCAL_GET(0),
kExprI32Add, kExprEnd});
const byte kTestNullDeprecated = tester.DefineFunction(
tester.sigs.i_v(), {kWasmI32, kWasmStructRef},
{WASM_BLOCK_R(ValueType::RefNull(type_index),
@ -614,6 +637,17 @@ WASM_COMPILED_EXEC_TEST(BrOnCast) {
type_index), // Traps
WASM_DROP, WASM_LOCAL_GET(0), kExprEnd});
// "br_on_cast null" also branches on null, treating it as a successful cast.
const byte kTestNullNull = tester.DefineFunction(
tester.sigs.i_v(), {kWasmI32, kWasmStructRef},
{WASM_BLOCK_R(ValueType::RefNull(type_index),
WASM_LOCAL_SET(0, WASM_I32V(111)),
WASM_LOCAL_GET(1), // Put a nullref onto the value stack.
// Taken for nullref with br_on_cast null.
WASM_BR_ON_CAST_NULL(0, type_index),
WASM_GC_OP(kExprRefCast), type_index),
WASM_DROP, WASM_LOCAL_GET(0), kExprEnd});
const byte kTypedAfterBranch = tester.DefineFunction(
tester.sigs.i_v(), {kWasmI32, kWasmStructRef},
{WASM_LOCAL_SET(1, WASM_STRUCT_NEW(type_index, WASM_I32V(42))),
@ -631,8 +665,10 @@ WASM_COMPILED_EXEC_TEST(BrOnCast) {
tester.CompileModule();
tester.CheckResult(kTestStructStatic, 222);
tester.CheckResult(kTestStructStaticNull, 222);
tester.CheckResult(kTestNullDeprecated, 222);
tester.CheckHasThrown(kTestNull);
tester.CheckResult(kTestNullNull, 111);
tester.CheckResult(kTypedAfterBranch, 42);
}

View File

@ -534,6 +534,9 @@ inline uint16_t ExtractPrefixedOpcodeBytes(WasmOpcode opcode) {
#define WASM_BR_ON_CAST(depth, typeidx) \
WASM_GC_OP(kExprBrOnCast), static_cast<byte>(depth), \
static_cast<byte>(typeidx)
#define WASM_BR_ON_CAST_NULL(depth, typeidx) \
WASM_GC_OP(kExprBrOnCastNull), static_cast<byte>(depth), \
static_cast<byte>(typeidx)
#define WASM_BR_ON_CAST_DEPRECATED(depth, typeidx) \
WASM_GC_OP(kExprBrOnCastDeprecated), static_cast<byte>(depth), \
static_cast<byte>(typeidx)

View File

@ -80,7 +80,7 @@ d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');
kGCPrefix, kExprExternExternalize,
]).exportFunc();
builder.addFunction(`brOn${typeName}`,
builder.addFunction(`brOnCast${typeName}`,
makeSig([kWasmExternRef], [kWasmI32]))
.addBody([
kExprBlock, kWasmRef, typeCode,
@ -94,6 +94,20 @@ d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');
kExprI32Const, 1,
kExprReturn,
]).exportFunc();
builder.addFunction(`brOnCastNull${typeName}`,
makeSig([kWasmExternRef], [kWasmI32]))
.addBody([
kExprBlock, kWasmRefNull, typeCode,
kExprLocalGet, 0,
kGCPrefix, kExprExternInternalize,
kGCPrefix, kExprBrOnCastNull, 0, typeCode,
kExprI32Const, 0,
kExprReturn,
kExprEnd,
kExprDrop,
kExprI32Const, 1,
kExprReturn,
]).exportFunc();
});
var instance = builder.instantiate();
@ -351,84 +365,166 @@ d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');
assertTraps(kTrapIllegalCast, () => wasm.refCastNullNone(jsObj));
// br_on_cast
assertEquals(0, wasm.brOnStructSuper(null));
assertEquals(0, wasm.brOnStructSuper(undefined));
assertEquals(1, wasm.brOnStructSuper(structSuperObj));
assertEquals(1, wasm.brOnStructSuper(structSubObj));
assertEquals(0, wasm.brOnStructSuper(arrayObj));
assertEquals(0, wasm.brOnStructSuper(funcObj));
assertEquals(0, wasm.brOnStructSuper(1));
assertEquals(0, wasm.brOnStructSuper(jsObj));
assertEquals(0, wasm.brOnCastStructSuper(null));
assertEquals(0, wasm.brOnCastStructSuper(undefined));
assertEquals(1, wasm.brOnCastStructSuper(structSuperObj));
assertEquals(1, wasm.brOnCastStructSuper(structSubObj));
assertEquals(0, wasm.brOnCastStructSuper(arrayObj));
assertEquals(0, wasm.brOnCastStructSuper(funcObj));
assertEquals(0, wasm.brOnCastStructSuper(1));
assertEquals(0, wasm.brOnCastStructSuper(jsObj));
assertEquals(0, wasm.brOnStructSub(null));
assertEquals(0, wasm.brOnStructSub(undefined));
assertEquals(0, wasm.brOnStructSub(structSuperObj));
assertEquals(1, wasm.brOnStructSub(structSubObj));
assertEquals(0, wasm.brOnStructSub(arrayObj));
assertEquals(0, wasm.brOnStructSub(funcObj));
assertEquals(0, wasm.brOnStructSub(1));
assertEquals(0, wasm.brOnStructSub(jsObj));
assertEquals(0, wasm.brOnCastStructSub(null));
assertEquals(0, wasm.brOnCastStructSub(undefined));
assertEquals(0, wasm.brOnCastStructSub(structSuperObj));
assertEquals(1, wasm.brOnCastStructSub(structSubObj));
assertEquals(0, wasm.brOnCastStructSub(arrayObj));
assertEquals(0, wasm.brOnCastStructSub(funcObj));
assertEquals(0, wasm.brOnCastStructSub(1));
assertEquals(0, wasm.brOnCastStructSub(jsObj));
assertEquals(0, wasm.brOnArray(null));
assertEquals(0, wasm.brOnArray(undefined));
assertEquals(0, wasm.brOnArray(structSuperObj));
assertEquals(0, wasm.brOnArray(structSubObj));
assertEquals(1, wasm.brOnArray(arrayObj));
assertEquals(0, wasm.brOnArray(funcObj));
assertEquals(0, wasm.brOnArray(1));
assertEquals(0, wasm.brOnArray(jsObj));
assertEquals(0, wasm.brOnCastArray(null));
assertEquals(0, wasm.brOnCastArray(undefined));
assertEquals(0, wasm.brOnCastArray(structSuperObj));
assertEquals(0, wasm.brOnCastArray(structSubObj));
assertEquals(1, wasm.brOnCastArray(arrayObj));
assertEquals(0, wasm.brOnCastArray(funcObj));
assertEquals(0, wasm.brOnCastArray(1));
assertEquals(0, wasm.brOnCastArray(jsObj));
assertEquals(0, wasm.brOnI31(null));
assertEquals(0, wasm.brOnI31(undefined));
assertEquals(0, wasm.brOnI31(structSuperObj));
assertEquals(0, wasm.brOnI31(structSubObj));
assertEquals(0, wasm.brOnI31(arrayObj));
assertEquals(0, wasm.brOnI31(funcObj));
assertEquals(1, wasm.brOnI31(1));
assertEquals(0, wasm.brOnI31(jsObj));
assertEquals(0, wasm.brOnCastI31(null));
assertEquals(0, wasm.brOnCastI31(undefined));
assertEquals(0, wasm.brOnCastI31(structSuperObj));
assertEquals(0, wasm.brOnCastI31(structSubObj));
assertEquals(0, wasm.brOnCastI31(arrayObj));
assertEquals(0, wasm.brOnCastI31(funcObj));
assertEquals(1, wasm.brOnCastI31(1));
assertEquals(0, wasm.brOnCastI31(jsObj));
assertEquals(0, wasm.brOnAnyArray(null));
assertEquals(0, wasm.brOnAnyArray(undefined));
assertEquals(0, wasm.brOnAnyArray(structSuperObj));
assertEquals(0, wasm.brOnAnyArray(structSubObj));
assertEquals(1, wasm.brOnAnyArray(arrayObj));
assertEquals(0, wasm.brOnAnyArray(funcObj));
assertEquals(0, wasm.brOnAnyArray(1));
assertEquals(0, wasm.brOnAnyArray(jsObj));
assertEquals(0, wasm.brOnCastAnyArray(null));
assertEquals(0, wasm.brOnCastAnyArray(undefined));
assertEquals(0, wasm.brOnCastAnyArray(structSuperObj));
assertEquals(0, wasm.brOnCastAnyArray(structSubObj));
assertEquals(1, wasm.brOnCastAnyArray(arrayObj));
assertEquals(0, wasm.brOnCastAnyArray(funcObj));
assertEquals(0, wasm.brOnCastAnyArray(1));
assertEquals(0, wasm.brOnCastAnyArray(jsObj));
assertEquals(0, wasm.brOnStruct(null));
assertEquals(0, wasm.brOnStruct(undefined));
assertEquals(1, wasm.brOnStruct(structSuperObj));
assertEquals(1, wasm.brOnStruct(structSubObj));
assertEquals(0, wasm.brOnStruct(arrayObj));
assertEquals(0, wasm.brOnStruct(funcObj));
assertEquals(0, wasm.brOnStruct(1));
assertEquals(0, wasm.brOnStruct(jsObj));
assertEquals(0, wasm.brOnCastStruct(null));
assertEquals(0, wasm.brOnCastStruct(undefined));
assertEquals(1, wasm.brOnCastStruct(structSuperObj));
assertEquals(1, wasm.brOnCastStruct(structSubObj));
assertEquals(0, wasm.brOnCastStruct(arrayObj));
assertEquals(0, wasm.brOnCastStruct(funcObj));
assertEquals(0, wasm.brOnCastStruct(1));
assertEquals(0, wasm.brOnCastStruct(jsObj));
assertEquals(0, wasm.brOnEq(null));
assertEquals(0, wasm.brOnEq(undefined));
assertEquals(1, wasm.brOnEq(structSuperObj));
assertEquals(1, wasm.brOnEq(structSubObj));
assertEquals(1, wasm.brOnEq(arrayObj));
assertEquals(0, wasm.brOnEq(funcObj));
assertEquals(1, wasm.brOnEq(1));
assertEquals(0, wasm.brOnEq(jsObj));
assertEquals(0, wasm.brOnCastEq(null));
assertEquals(0, wasm.brOnCastEq(undefined));
assertEquals(1, wasm.brOnCastEq(structSuperObj));
assertEquals(1, wasm.brOnCastEq(structSubObj));
assertEquals(1, wasm.brOnCastEq(arrayObj));
assertEquals(0, wasm.brOnCastEq(funcObj));
assertEquals(1, wasm.brOnCastEq(1));
assertEquals(0, wasm.brOnCastEq(jsObj));
assertEquals(0, wasm.brOnAny(null));
assertEquals(1, wasm.brOnAny(undefined));
assertEquals(1, wasm.brOnAny(structSuperObj));
assertEquals(1, wasm.brOnAny(structSubObj));
assertEquals(1, wasm.brOnAny(arrayObj));
assertEquals(1, wasm.brOnAny(funcObj));
assertEquals(1, wasm.brOnAny(1));
assertEquals(1, wasm.brOnAny(jsObj));
assertEquals(0, wasm.brOnCastAny(null));
assertEquals(1, wasm.brOnCastAny(undefined));
assertEquals(1, wasm.brOnCastAny(structSuperObj));
assertEquals(1, wasm.brOnCastAny(structSubObj));
assertEquals(1, wasm.brOnCastAny(arrayObj));
assertEquals(1, wasm.brOnCastAny(funcObj));
assertEquals(1, wasm.brOnCastAny(1));
assertEquals(1, wasm.brOnCastAny(jsObj));
assertEquals(0, wasm.brOnNone(null));
assertEquals(0, wasm.brOnNone(undefined));
assertEquals(0, wasm.brOnNone(structSuperObj));
assertEquals(0, wasm.brOnNone(structSubObj));
assertEquals(0, wasm.brOnNone(arrayObj));
assertEquals(0, wasm.brOnNone(funcObj));
assertEquals(0, wasm.brOnNone(1));
assertEquals(0, wasm.brOnNone(jsObj));
assertEquals(0, wasm.brOnCastNone(null));
assertEquals(0, wasm.brOnCastNone(undefined));
assertEquals(0, wasm.brOnCastNone(structSuperObj));
assertEquals(0, wasm.brOnCastNone(structSubObj));
assertEquals(0, wasm.brOnCastNone(arrayObj));
assertEquals(0, wasm.brOnCastNone(funcObj));
assertEquals(0, wasm.brOnCastNone(1));
assertEquals(0, wasm.brOnCastNone(jsObj));
// br_on_cast null
assertEquals(1, wasm.brOnCastNullStructSuper(null));
assertEquals(0, wasm.brOnCastNullStructSuper(undefined));
assertEquals(1, wasm.brOnCastNullStructSuper(structSuperObj));
assertEquals(1, wasm.brOnCastNullStructSuper(structSubObj));
assertEquals(0, wasm.brOnCastNullStructSuper(arrayObj));
assertEquals(0, wasm.brOnCastNullStructSuper(funcObj));
assertEquals(0, wasm.brOnCastNullStructSuper(1));
assertEquals(0, wasm.brOnCastNullStructSuper(jsObj));
assertEquals(1, wasm.brOnCastNullStructSub(null));
assertEquals(0, wasm.brOnCastNullStructSub(undefined));
assertEquals(0, wasm.brOnCastNullStructSub(structSuperObj));
assertEquals(1, wasm.brOnCastNullStructSub(structSubObj));
assertEquals(0, wasm.brOnCastNullStructSub(arrayObj));
assertEquals(0, wasm.brOnCastNullStructSub(funcObj));
assertEquals(0, wasm.brOnCastNullStructSub(1));
assertEquals(0, wasm.brOnCastNullStructSub(jsObj));
assertEquals(1, wasm.brOnCastNullArray(null));
assertEquals(0, wasm.brOnCastNullArray(undefined));
assertEquals(0, wasm.brOnCastNullArray(structSuperObj));
assertEquals(0, wasm.brOnCastNullArray(structSubObj));
assertEquals(1, wasm.brOnCastNullArray(arrayObj));
assertEquals(0, wasm.brOnCastNullArray(funcObj));
assertEquals(0, wasm.brOnCastNullArray(1));
assertEquals(0, wasm.brOnCastNullArray(jsObj));
assertEquals(1, wasm.brOnCastNullI31(null));
assertEquals(0, wasm.brOnCastNullI31(undefined));
assertEquals(0, wasm.brOnCastNullI31(structSuperObj));
assertEquals(0, wasm.brOnCastNullI31(structSubObj));
assertEquals(0, wasm.brOnCastNullI31(arrayObj));
assertEquals(0, wasm.brOnCastNullI31(funcObj));
assertEquals(1, wasm.brOnCastNullI31(1));
assertEquals(0, wasm.brOnCastNullI31(jsObj));
assertEquals(1, wasm.brOnCastNullAnyArray(null));
assertEquals(0, wasm.brOnCastNullAnyArray(undefined));
assertEquals(0, wasm.brOnCastNullAnyArray(structSuperObj));
assertEquals(0, wasm.brOnCastNullAnyArray(structSubObj));
assertEquals(1, wasm.brOnCastNullAnyArray(arrayObj));
assertEquals(0, wasm.brOnCastNullAnyArray(funcObj));
assertEquals(0, wasm.brOnCastNullAnyArray(1));
assertEquals(0, wasm.brOnCastNullAnyArray(jsObj));
assertEquals(1, wasm.brOnCastNullStruct(null));
assertEquals(0, wasm.brOnCastNullStruct(undefined));
assertEquals(1, wasm.brOnCastNullStruct(structSuperObj));
assertEquals(1, wasm.brOnCastNullStruct(structSubObj));
assertEquals(0, wasm.brOnCastNullStruct(arrayObj));
assertEquals(0, wasm.brOnCastNullStruct(funcObj));
assertEquals(0, wasm.brOnCastNullStruct(1));
assertEquals(0, wasm.brOnCastNullStruct(jsObj));
assertEquals(1, wasm.brOnCastNullEq(null));
assertEquals(0, wasm.brOnCastNullEq(undefined));
assertEquals(1, wasm.brOnCastNullEq(structSuperObj));
assertEquals(1, wasm.brOnCastNullEq(structSubObj));
assertEquals(1, wasm.brOnCastNullEq(arrayObj));
assertEquals(0, wasm.brOnCastNullEq(funcObj));
assertEquals(1, wasm.brOnCastNullEq(1));
assertEquals(0, wasm.brOnCastNullEq(jsObj));
assertEquals(1, wasm.brOnCastNullAny(null));
assertEquals(1, wasm.brOnCastNullAny(undefined));
assertEquals(1, wasm.brOnCastNullAny(structSuperObj));
assertEquals(1, wasm.brOnCastNullAny(structSubObj));
assertEquals(1, wasm.brOnCastNullAny(arrayObj));
assertEquals(1, wasm.brOnCastNullAny(funcObj));
assertEquals(1, wasm.brOnCastNullAny(1));
assertEquals(1, wasm.brOnCastNullAny(jsObj));
assertEquals(1, wasm.brOnCastNullNone(null));
assertEquals(0, wasm.brOnCastNullNone(undefined));
assertEquals(0, wasm.brOnCastNullNone(structSuperObj));
assertEquals(0, wasm.brOnCastNullNone(structSubObj));
assertEquals(0, wasm.brOnCastNullNone(arrayObj));
assertEquals(0, wasm.brOnCastNullNone(funcObj));
assertEquals(0, wasm.brOnCastNullNone(1));
assertEquals(0, wasm.brOnCastNullNone(jsObj));
})();

View File

@ -231,6 +231,20 @@ d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');
kExprI32Const, 1,
kExprReturn,
]).exportFunc();
builder.addFunction(`brOnCastNull_${name}`,
makeSig([kWasmFuncRef], [kWasmI32]))
.addBody([
kExprBlock, kWasmRefNull, typeCode,
kExprLocalGet, 0,
kGCPrefix, kExprBrOnCastNull, 0, typeCode,
kExprI32Const, 0,
kExprReturn,
kExprEnd,
kExprDrop,
kExprI32Const, 1,
kExprReturn,
]).exportFunc();
}
let instance = builder.instantiate();
@ -257,6 +271,26 @@ d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');
assertEquals(0, wasm.brOnCast_nullfuncref(wasm.fctSub));
assertEquals(1, wasm.brOnCast_super(wasm.fctSub));
assertEquals(1, wasm.brOnCast_sub(wasm.fctSub));
assertEquals(1, wasm.brOnCastNull_funcref(null));
assertEquals(1, wasm.brOnCastNull_nullfuncref(null));
assertEquals(1, wasm.brOnCastNull_super(null));
assertEquals(1, wasm.brOnCastNull_sub(null));
assertEquals(1, wasm.brOnCastNull_funcref(jsFct));
assertEquals(0, wasm.brOnCastNull_nullfuncref(jsFct));
assertEquals(0, wasm.brOnCastNull_super(jsFct));
assertEquals(0, wasm.brOnCastNull_sub(jsFct));
assertEquals(1, wasm.brOnCastNull_funcref(wasm.fctSuper));
assertEquals(0, wasm.brOnCastNull_nullfuncref(wasm.fctSuper));
assertEquals(1, wasm.brOnCastNull_super(wasm.fctSuper));
assertEquals(0, wasm.brOnCastNull_sub(wasm.fctSuper));
assertEquals(1, wasm.brOnCastNull_funcref(wasm.fctSub));
assertEquals(0, wasm.brOnCastNull_nullfuncref(wasm.fctSub));
assertEquals(1, wasm.brOnCastNull_super(wasm.fctSub));
assertEquals(1, wasm.brOnCastNull_sub(wasm.fctSub));
})();
(function RefTestExternRef() {
@ -378,6 +412,35 @@ d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');
])
.exportFunc();
builder.addFunction('castNullToExternRef',
makeSig([kWasmExternRef], [kWasmI32]))
.addBody([
kExprBlock, kWasmRefNull, kExternRefCode,
kExprLocalGet, 0,
kGCPrefix, kExprBrOnCastNull, 0, kExternRefCode,
kExprI32Const, 0,
kExprReturn,
kExprEnd,
kExprDrop,
kExprI32Const, 1,
kExprReturn,
])
.exportFunc();
builder.addFunction('castNullToNullExternRef',
makeSig([kWasmExternRef], [kWasmI32]))
.addBody([
kExprBlock, kWasmRefNull, kNullExternRefCode,
kExprLocalGet, 0,
kGCPrefix, kExprBrOnCastNull, 0, kNullExternRefCode,
kExprI32Const, 0,
kExprReturn,
kExprEnd,
kExprDrop,
kExprI32Const, 1,
kExprReturn,
])
.exportFunc();
let instance = builder.instantiate();
let wasm = instance.exports;
let obj = {};
@ -394,6 +457,17 @@ d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');
assertEquals(0, wasm.castToNullExternRef(obj));
assertEquals(0, wasm.castToNullExternRef(wasm.castToExternRef));
assertEquals(1, wasm.castNullToExternRef(null));
assertEquals(1, wasm.castNullToExternRef(undefined));
assertEquals(1, wasm.castNullToExternRef(1));
assertEquals(1, wasm.castNullToExternRef(obj));
assertEquals(1, wasm.castNullToExternRef(wasm.castToExternRef));
assertEquals(1, wasm.castNullToNullExternRef(null));
assertEquals(0, wasm.castNullToNullExternRef(undefined));
assertEquals(0, wasm.castNullToNullExternRef(1));
assertEquals(0, wasm.castNullToNullExternRef(obj));
assertEquals(0, wasm.castNullToNullExternRef(wasm.castToExternRef));
})();
@ -563,6 +637,22 @@ d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');
kExprReturn,
])
.exportFunc();
builder.addFunction(`brOnCastNull_${test.source}_to_${target}`,
makeSig([wasmRefType(creatorType)], [kWasmI32]))
.addBody([
kExprBlock, kWasmRefNull, heapType,
kExprLocalGet, 0,
kExprCallRef, ...wasmUnsignedLeb(creatorType),
kGCPrefix, kExprBrOnCastNull, 0, heapType,
kExprI32Const, 0,
kExprReturn,
kExprEnd,
kExprDrop,
kExprI32Const, 1,
kExprReturn,
])
.exportFunc();
}
}
@ -599,6 +689,10 @@ d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');
print(`Test br_on_cast: ${test.source}(${value}) -> ${target}`);
res = wasm[`brOnCast_${test.source}_to_${target}`](create_value);
assertEquals(validValues.includes(value) ? 1 : 0, res);
print(`Test br_on_cast null: ${test.source}(${value}) -> ${target}`);
res = wasm[`brOnCastNull_${test.source}_to_${target}`](create_value);
assertEquals(
validValues.includes(value) || value == "nullref" ? 1 : 0, res);
}
}
}

View File

@ -516,6 +516,7 @@ let kExprRefCast = 0x41;
let kExprRefCastNull = 0x49;
let kExprRefCastDeprecated = 0x45;
let kExprBrOnCast = 0x42;
let kExprBrOnCastNull = 0x4a;
let kExprBrOnCastDeprecated = 0x46;
let kExprBrOnCastFail = 0x47;
let kExprRefCastNop = 0x4c;