Commit ae7b6fcd authored by Jakob Kummerow's avatar Jakob Kummerow Committed by Commit Bot

[wasm-gc] Implement br_on_cast

Bug: v8:7748
Change-Id: I6e226888d84a790efc36ac6e7c2a32bc3426bd84
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2308341
Commit-Queue: Jakob Kummerow <jkummerow@chromium.org>
Reviewed-by: 's avatarClemens Backes <clemensb@chromium.org>
Cr-Commit-Position: refs/heads/master@{#69002}
parent 51b53dd3
......@@ -5460,6 +5460,79 @@ Node* WasmGraphBuilder::RefCast(Node* object, Node* rtt,
return object;
}
Node* WasmGraphBuilder::BrOnCast(Node* object, Node* rtt,
CheckForNull null_check, CheckForI31 i31_check,
RttIsI31 rtt_is_i31, Node** match_control,
Node** match_effect, Node** no_match_control,
Node** no_match_effect) {
// We have up to 3 control nodes to merge; the EffectPhi needs an additional
// input.
base::SmallVector<Node*, 3> merge_controls;
base::SmallVector<Node*, 4> merge_effects;
Node* is_i31 = IsI31(gasm_.get(), object);
if (i31_check == kWithI31Check) {
if (rtt_is_i31 == kRttIsI31) {
BranchExpectFalse(is_i31, match_control, no_match_control);
return nullptr;
} else {
Node* i31_branch = graph()->NewNode(
mcgraph()->common()->Branch(BranchHint::kFalse), is_i31, control());
SetControl(graph()->NewNode(mcgraph()->common()->IfFalse(), i31_branch));
merge_controls.emplace_back(
graph()->NewNode(mcgraph()->common()->IfTrue(), i31_branch));
merge_effects.emplace_back(effect());
}
} else {
AssertFalse(gasm_.get(), is_i31);
}
if (null_check == kWithNullCheck) {
Node* null_branch =
graph()->NewNode(mcgraph()->common()->Branch(BranchHint::kFalse),
gasm_->WordEqual(object, RefNull()), control());
SetControl(graph()->NewNode(mcgraph()->common()->IfFalse(), null_branch));
merge_controls.emplace_back(
graph()->NewNode(mcgraph()->common()->IfTrue(), null_branch));
merge_effects.emplace_back(effect());
}
// At this point, {object} is neither null nor an i31ref/Smi.
Node* map = gasm_->Load(MachineType::TaggedPointer(), object,
HeapObject::kMapOffset - kHeapObjectTag);
// TODO(7748): Add a fast path for map == rtt.
Node* subtype_check = BuildChangeSmiToInt32(CALL_BUILTIN(
WasmIsRttSubtype, map, rtt,
LOAD_INSTANCE_FIELD(NativeContext, MachineType::TaggedPointer())));
Node* cast_branch =
graph()->NewNode(mcgraph()->common()->Branch(BranchHint::kFalse),
subtype_check, control());
*match_control = graph()->NewNode(mcgraph()->common()->IfTrue(), cast_branch);
*match_effect = effect();
Node* not_subtype =
graph()->NewNode(mcgraph()->common()->IfFalse(), cast_branch);
// Wire up the "cast attempt was unsuccessful" control nodes: merge them if
// there is more than one.
if (merge_controls.size() > 0) {
merge_controls.emplace_back(not_subtype);
merge_effects.emplace_back(effect());
// Range is 1..3, so casting to int is safe.
DCHECK_EQ(merge_controls.size(), merge_effects.size());
unsigned count = static_cast<unsigned>(merge_controls.size());
*no_match_control = Merge(count, merge_controls.data());
// EffectPhis need their control dependency as an additional input.
merge_effects.emplace_back(*no_match_control);
*no_match_effect = EffectPhi(count, merge_effects.data());
} else {
*no_match_control = not_subtype;
*no_match_effect = effect();
}
// Return value is not used, but we need it for compatibility
// with graph-builder-interface.
return nullptr;
}
Node* WasmGraphBuilder::StructGet(Node* struct_object,
const wasm::StructType* struct_type,
uint32_t field_index, CheckForNull null_check,
......
......@@ -418,6 +418,10 @@ class WasmGraphBuilder {
Node* RefCast(Node* object, Node* rtt, CheckForNull null_check,
CheckForI31 i31_check, RttIsI31 rtt_is_i31,
wasm::WasmCodePosition position);
Node* BrOnCast(Node* object, Node* rtt, CheckForNull null_check,
CheckForI31 i31_check, RttIsI31 rtt_is_i31,
Node** match_control, Node** match_effect,
Node** no_match_control, Node** no_match_effect);
bool has_simd() const { return has_simd_; }
......
......@@ -3532,6 +3532,11 @@ class LiftoffCompiler {
// TODO(7748): Implement.
unsupported(decoder, kGC, "ref.cast");
}
void BrOnCast(FullDecoder* decoder, const Value& obj, const Value& rtt,
Value* result_on_branch, uint32_t depth) {
// TODO(7748): Implement.
unsupported(decoder, kGC, "br_on_cast");
}
void PassThrough(FullDecoder* decoder, const Value& from, Value* to) {
// TODO(7748): Implement.
......
......@@ -968,6 +968,8 @@ struct ControlBase {
Value* result) \
F(RefTest, const Value& obj, const Value& rtt, Value* result) \
F(RefCast, const Value& obj, const Value& rtt, Value* result) \
F(BrOnCast, const Value& obj, const Value& rtt, Value* result_on_branch, \
uint32_t depth) \
F(PassThrough, const Value& from, Value* to)
// Generic Wasm bytecode decoder with utilities for decoding immediates,
......@@ -2310,9 +2312,11 @@ class WasmFullDecoder : public WasmDecoder<validate> {
break;
}
default:
this->error(this->pc_, "invalid agrument type to ref.as_non_null");
this->error(this->pc_, "invalid argument type to br_on_null");
return 0;
}
} else if (check_result == kInvalidStack) {
return 0;
}
return 1 + imm.length;
}
......@@ -3657,6 +3661,49 @@ class WasmFullDecoder : public WasmDecoder<validate> {
CALL_INTERFACE_IF_REACHABLE(RefCast, obj, rtt, value);
return len;
}
case kExprBrOnCast: {
BranchDepthImmediate<validate> branch_depth(this, this->pc_ + 2);
if (!this->Validate(this->pc_ + 2, branch_depth, control_.size())) {
return 0;
}
// TODO(7748): If the heap type immediates remain in the spec, read
// them here.
Value rtt = Pop(1);
if (!VALIDATE(rtt.type.kind() == ValueType::kRtt)) {
this->error(this->pc_, "br_on_cast[1]: expected rtt on stack");
return 0;
}
Value obj = Pop(0);
if (!VALIDATE(obj.type.kind() == ValueType::kRef ||
obj.type.kind() == ValueType::kOptRef)) {
this->error(this->pc_, "br_on_cast[0]: expected reference on stack");
return 0;
}
// The static type of {obj} must be a supertype of {rtt}'s type.
if (!VALIDATE(
IsSubtypeOf(ValueType::Ref(rtt.type.heap_type(), kNonNullable),
ValueType::Ref(obj.type.heap_type(), kNonNullable),
this->module_))) {
this->error(this->pc_,
"br_on_cast: rtt type must be a subtype of object type");
return 0;
}
Control* c = control_at(branch_depth.depth);
Value* result_on_branch =
Push(ValueType::Ref(rtt.type.heap_type(), kNonNullable));
TypeCheckBranchResult check_result = TypeCheckBranch(c, true);
if (V8_LIKELY(check_result == kReachableBranch)) {
CALL_INTERFACE(BrOnCast, obj, rtt, result_on_branch,
branch_depth.depth);
c->br_merge()->reached = true;
} else if (check_result == kInvalidStack) {
return 0;
}
Pop(0); // Drop {result_on_branch}, restore original value.
Value* result_on_fallthrough = Push(obj.type);
*result_on_fallthrough = obj;
return 2 + branch_depth.length;
}
default:
this->error("invalid gc opcode");
return 0;
......
......@@ -775,6 +775,34 @@ class WasmGraphBuildingInterface {
rtt_is_i31, decoder->position());
}
void BrOnCast(FullDecoder* decoder, const Value& object, const Value& rtt,
Value* value_on_branch, uint32_t depth) {
using CheckForNull = compiler::WasmGraphBuilder::CheckForNull;
using CheckForI31 = compiler::WasmGraphBuilder::CheckForI31;
using RttIsI31 = compiler::WasmGraphBuilder::RttIsI31;
CheckForNull null_check = object.type.is_nullable()
? CheckForNull::kWithNullCheck
: CheckForNull::kWithoutNullCheck;
CheckForI31 i31_check =
IsSubtypeOf(kWasmI31Ref, object.type, decoder->module_)
? CheckForI31::kWithI31Check
: CheckForI31::kNoI31Check;
RttIsI31 rtt_is_i31 = rtt.type.heap_representation() == HeapType::kI31
? RttIsI31::kRttIsI31
: RttIsI31::kRttIsNotI31;
SsaEnv* match_env = Split(decoder->zone(), ssa_env_);
SsaEnv* no_match_env = Steal(decoder->zone(), ssa_env_);
no_match_env->SetNotMerged();
BUILD(BrOnCast, object.node, rtt.node, null_check, i31_check, rtt_is_i31,
&match_env->control, &match_env->effect, &no_match_env->control,
&no_match_env->effect);
builder_->SetControl(no_match_env->control);
SetEnv(match_env);
value_on_branch->node = object.node;
BrOrRet(decoder, depth);
SetEnv(no_match_env);
}
void PassThrough(FullDecoder* decoder, const Value& from, Value* to) {
to->node = from.node;
}
......
......@@ -284,6 +284,84 @@ TEST(WasmBrOnNull) {
tester.CheckResult(kNotTaken, 52);
}
TEST(BrOnCast) {
WasmGCTester tester;
uint32_t type_index = tester.DefineStruct({F(kWasmI32, true)});
uint32_t rtt_index =
tester.AddGlobal(ValueType::Rtt(type_index, 1), false,
WasmInitExpr::RttCanon(
static_cast<HeapType::Representation>(type_index)));
const uint32_t kTestStruct = tester.DefineFunction(
tester.sigs.i_v(), {kWasmI32, kWasmEqRef},
{WASM_BLOCK(WASM_SET_LOCAL(0, WASM_I32V(111)),
// Pipe a struct through a local so it's statically typed
// as eqref.
WASM_SET_LOCAL(
1, WASM_STRUCT_NEW_WITH_RTT(type_index, WASM_I32V(1),
WASM_GET_GLOBAL(rtt_index))),
WASM_GET_LOCAL(1),
// The struct is not an i31, so this branch isn't taken.
WASM_BR_ON_CAST(0, WASM_RTT_CANON(kLocalI31Ref)),
WASM_SET_LOCAL(0, WASM_I32V(222)), // Final result.
// This branch is taken.
WASM_BR_ON_CAST(0, WASM_GET_GLOBAL(rtt_index)),
// Not executed due to the branch.
WASM_DROP, WASM_SET_LOCAL(0, WASM_I32V(333))),
WASM_GET_LOCAL(0), kExprEnd});
const uint32_t kTestI31 = tester.DefineFunction(
tester.sigs.i_v(), {kWasmI32, kWasmEqRef},
{WASM_BLOCK(WASM_SET_LOCAL(0, WASM_I32V(111)),
// Pipe an i31ref through a local so it's statically typed
// as eqref.
WASM_SET_LOCAL(1, WASM_I31_NEW(WASM_I32V(42))),
WASM_GET_LOCAL(1),
// The i31 is not a struct, so this branch isn't taken.
WASM_BR_ON_CAST(0, WASM_GET_GLOBAL(rtt_index)),
WASM_SET_LOCAL(0, WASM_I32V(222)), // Final result.
// This branch is taken.
WASM_BR_ON_CAST(0, WASM_RTT_CANON(kLocalI31Ref)),
// Not executed due to the branch.
WASM_DROP, WASM_SET_LOCAL(0, WASM_I32V(333))),
WASM_GET_LOCAL(0), kExprEnd});
const uint32_t kTestNull = tester.DefineFunction(
tester.sigs.i_v(), {kWasmI32, kWasmEqRef},
{WASM_BLOCK(WASM_SET_LOCAL(0, WASM_I32V(111)),
WASM_GET_LOCAL(1), // Put a nullref onto the value stack.
// Neither of these branches is taken for nullref.
WASM_BR_ON_CAST(0, WASM_RTT_CANON(kLocalI31Ref)),
WASM_SET_LOCAL(0, WASM_I32V(222)),
WASM_BR_ON_CAST(0, WASM_GET_GLOBAL(rtt_index)), WASM_DROP,
WASM_SET_LOCAL(0, WASM_I32V(333))), // Final result.
WASM_GET_LOCAL(0), kExprEnd});
const uint32_t kTypedAfterBranch = tester.DefineFunction(
tester.sigs.i_v(), {kWasmI32, kWasmEqRef},
{WASM_SET_LOCAL(1, WASM_STRUCT_NEW_WITH_RTT(type_index, WASM_I32V(42),
WASM_GET_GLOBAL(rtt_index))),
WASM_BLOCK(WASM_SET_LOCAL(
// The outer block catches the struct left behind by the inner block
// and reads its field.
0,
WASM_STRUCT_GET(
type_index, 0,
// The inner block should take the early branch with a struct
// on the stack.
WASM_BLOCK_R(ValueType::Ref(type_index, kNonNullable),
WASM_GET_LOCAL(1),
WASM_BR_ON_CAST(0, WASM_GET_GLOBAL(rtt_index)),
// Returning 123 is the unreachable failure case.
WASM_SET_LOCAL(0, WASM_I32V(123)), WASM_BR(1))))),
WASM_GET_LOCAL(0), kExprEnd});
tester.CompileModule();
tester.CheckResult(kTestStruct, 222);
tester.CheckResult(kTestI31, 222);
tester.CheckResult(kTestNull, 333);
tester.CheckResult(kTypedAfterBranch, 42);
}
TEST(WasmRefEq) {
WasmGCTester tester;
byte type_index = static_cast<byte>(
......
......@@ -453,6 +453,10 @@ inline WasmOpcode LoadStoreOpcodeOf(MachineType type, bool store) {
#define WASM_REF_CAST(obj_type, rtt_type, ref, rtt) \
ref, rtt, WASM_GC_OP(kExprRefCast), static_cast<byte>(obj_type), \
static_cast<byte>(rtt_type)
// Takes a reference value from the value stack to allow sequences of
// conditional branches.
#define WASM_BR_ON_CAST(depth, rtt) \
rtt, WASM_GC_OP(kExprBrOnCast), static_cast<byte>(depth)
#define WASM_ARRAY_NEW_WITH_RTT(index, default_value, length, rtt) \
default_value, length, rtt, WASM_GC_OP(kExprArrayNewWithRtt), \
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment