Commit 1186b5c0 authored by Manos Koukoutos's avatar Manos Koukoutos Committed by V8 LUCI CQ

[wasm-gc] Path-based type tracking for wasm-gc nodes

This CL adds control-path type-tracking for wasm-gc nodes in the
WasmGCOperatorReducer. Nodes now use the types assigned to their
argument nodes, as well as the additional information tracked along
control paths.

Drive-by: Add support for multiple instances of the same node to
appear in control-path-state.

Bug: v8:7748
Change-Id: I73e8f84595609b3a5fb61a2bffeb973182d17676
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3717994Reviewed-by: 's avatarMaya Lekova <mslekova@chromium.org>
Commit-Queue: Manos Koukoutos <manoskouk@chromium.org>
Cr-Commit-Position: refs/heads/main@{#81373}
parent ff1d23c7
......@@ -37,7 +37,13 @@ class ControlPathState {
// Returns the {NodeState} assigned to node, or the default value
// {NodeState()} if it is not assigned.
NodeState LookupState(Node* node) const { return states_.Get(node); }
NodeState LookupState(Node* node) const {
for (size_t depth = blocks_.Size(); depth > 0; depth--) {
NodeState state = states_.Get({node, depth});
if (state.IsSet()) return state;
}
return {};
}
// Adds a state in the current code block, or a new block if the block list is
// empty.
......@@ -60,15 +66,23 @@ class ControlPathState {
}
private:
using NodeWithPathDepth = std::pair<Node*, size_t>;
#if DEBUG
bool BlocksAndStatesInvariant() {
PersistentMap<Node*, NodeState> states_copy(states_);
PersistentMap<NodeWithPathDepth, NodeState> states_copy(states_);
size_t depth = blocks_.Size();
for (auto block : blocks_) {
std::unordered_set<Node*> seen_this_block;
for (NodeState state : block) {
// Every element of blocks_ has to be in states_.
if (states_copy.Get(state.node) != state) return false;
states_copy.Set(state.node, {});
if (seen_this_block.count(state.node) == 0) {
if (states_copy.Get({state.node, depth}) != state) return false;
states_copy.Set({state.node, depth}, {});
seen_this_block.emplace(state.node);
}
}
depth--;
}
// Every element of {states_} has to be in {blocks_}. We removed all
// elements of blocks_ from states_copy, so if it is not empty, the
......@@ -82,7 +96,7 @@ class ControlPathState {
// set of states. It should hold at any point that the contents of {blocks_}
// and {states_} is the same, which is implemented in
// {BlocksAndStatesInvariant}.
PersistentMap<Node*, NodeState> states_;
PersistentMap<NodeWithPathDepth, NodeState> states_;
};
template <typename NodeState>
......@@ -125,8 +139,6 @@ template <typename NodeState>
void ControlPathState<NodeState>::AddState(Zone* zone, Node* node,
NodeState state,
ControlPathState<NodeState> hint) {
if (LookupState(node).IsSet()) return;
FunctionalList<NodeState> prev_front = blocks_.Front();
if (hint.blocks_.Size() > 0) {
prev_front.PushFront(state, zone, hint.blocks_.Front());
......@@ -135,7 +147,7 @@ void ControlPathState<NodeState>::AddState(Zone* zone, Node* node,
}
blocks_.DropFront();
blocks_.PushFront(prev_front, zone);
states_.Set(node, state);
states_.Set({node, blocks_.Size()}, state);
SLOW_DCHECK(BlocksAndStatesInvariant());
}
......@@ -143,10 +155,8 @@ template <typename NodeState>
void ControlPathState<NodeState>::AddStateInNewBlock(Zone* zone, Node* node,
NodeState state) {
FunctionalList<NodeState> new_block;
if (!LookupState(node).IsSet()) {
new_block.PushFront(state, zone);
states_.Set(node, state);
}
new_block.PushFront(state, zone);
states_.Set({node, blocks_.Size() + 1}, state);
blocks_.PushFront(new_block, zone);
SLOW_DCHECK(BlocksAndStatesInvariant());
}
......@@ -157,13 +167,13 @@ void ControlPathState<NodeState>::ResetToCommonAncestor(
while (other.blocks_.Size() > blocks_.Size()) other.blocks_.DropFront();
while (blocks_.Size() > other.blocks_.Size()) {
for (NodeState state : blocks_.Front()) {
states_.Set(state.node, {});
states_.Set({state.node, blocks_.Size()}, {});
}
blocks_.DropFront();
}
while (blocks_ != other.blocks_) {
for (NodeState state : blocks_.Front()) {
states_.Set(state.node, {});
states_.Set({state.node, blocks_.Size()}, {});
}
blocks_.DropFront();
other.blocks_.DropFront();
......
......@@ -2081,7 +2081,8 @@ struct WasmGCOptimizationPhase {
GraphReducer graph_reducer(
temp_zone, data->graph(), &data->info()->tick_counter(), data->broker(),
data->jsgraph()->Dead(), data->observe_node_manager());
WasmGCOperatorReducer wasm_gc(&graph_reducer, data->mcgraph(), module);
WasmGCOperatorReducer wasm_gc(&graph_reducer, temp_zone, data->mcgraph(),
module);
AddReducer(data, &graph_reducer, &wasm_gc);
graph_reducer.ReduceGraph();
}
......
......@@ -1154,16 +1154,18 @@ struct SimplifiedOperatorGlobalCache final {
LoadStackArgumentOperator kLoadStackArgument;
#if V8_ENABLE_WEBASSEMBLY
// Note: The following two operators have a control input solely to find the
// typing context from the control path in wasm-gc-operator-reducer.
struct IsNullOperator final : public Operator {
IsNullOperator()
: Operator(IrOpcode::kIsNull, Operator::kPure, "IsNull", 1, 0, 0, 1, 0,
: Operator(IrOpcode::kIsNull, Operator::kPure, "IsNull", 1, 0, 1, 1, 0,
0) {}
};
IsNullOperator kIsNull;
struct IsNotNullOperator final : public Operator {
IsNotNullOperator()
: Operator(IrOpcode::kIsNotNull, Operator::kPure, "IsNotNull", 1, 0, 0,
: Operator(IrOpcode::kIsNotNull, Operator::kPure, "IsNotNull", 1, 0, 1,
1, 0, 0) {}
};
IsNotNullOperator kIsNotNull;
......
......@@ -13,26 +13,42 @@ namespace v8 {
namespace internal {
namespace compiler {
WasmGCOperatorReducer::WasmGCOperatorReducer(Editor* editor,
WasmGCOperatorReducer::WasmGCOperatorReducer(Editor* editor, Zone* temp_zone_,
MachineGraph* mcgraph,
const wasm::WasmModule* module)
: AdvancedReducer(editor),
: AdvancedReducerWithControlPathState(editor, temp_zone_, mcgraph->graph()),
mcgraph_(mcgraph),
gasm_(mcgraph, mcgraph->zone()),
module_(module) {}
Reduction WasmGCOperatorReducer::Reduce(Node* node) {
switch (node->opcode()) {
case IrOpcode::kStart:
return ReduceStart(node);
case IrOpcode::kAssertNotNull:
return ReduceAssertNotNull(node);
case IrOpcode::kIsNull:
return ReduceIsNull(node);
case IrOpcode::kIsNotNull:
return ReduceCheckNull(node);
case IrOpcode::kWasmTypeCheck:
return ReduceWasmTypeCheck(node);
case IrOpcode::kWasmTypeCast:
return ReduceWasmTypeCast(node);
case IrOpcode::kMerge:
return ReduceMerge(node);
case IrOpcode::kIfTrue:
return ReduceIf(node, true);
case IrOpcode::kIfFalse:
return ReduceIf(node, false);
case IrOpcode::kLoop:
return TakeStatesFromFirstControl(node);
default:
return NoChange();
if (node->op()->ControlOutputCount() > 0) {
DCHECK_EQ(1, node->op()->ControlInputCount());
return TakeStatesFromFirstControl(node);
} else {
return NoChange();
}
}
}
......@@ -41,6 +57,18 @@ bool InDeadBranch(Node* node) {
return node->opcode() == IrOpcode::kDead ||
NodeProperties::GetType(node).AsWasm().type.is_bottom();
}
Node* GetAlias(Node* node) {
switch (node->opcode()) {
case IrOpcode::kWasmTypeCheck:
case IrOpcode::kTypeGuard:
case IrOpcode::kAssertNotNull:
return NodeProperties::GetValueInput(node, 0);
default:
return nullptr;
}
}
} // namespace
Node* WasmGCOperatorReducer::SetType(Node* node, wasm::ValueType type) {
......@@ -48,38 +76,160 @@ Node* WasmGCOperatorReducer::SetType(Node* node, wasm::ValueType type) {
return node;
}
Reduction WasmGCOperatorReducer::UpdateNodeAndAliasesTypes(
Node* state_owner, ControlPathTypes parent_state, Node* node,
wasm::TypeInModule type, bool in_new_block) {
Node* current = node;
ControlPathTypes current_state = parent_state;
while (current != nullptr) {
UpdateStates(state_owner, current_state, current, {current, type},
in_new_block);
current = GetAlias(current);
current_state = GetState(state_owner);
in_new_block = false;
}
return Changed(state_owner);
}
Reduction WasmGCOperatorReducer::ReduceStart(Node* node) {
return UpdateStates(node, ControlPathTypes(zone()));
}
wasm::TypeInModule WasmGCOperatorReducer::ObjectTypeFromContext(Node* object,
Node* control) {
if (InDeadBranch(object)) return {};
if (!IsReduced(control)) return {};
wasm::TypeInModule type_from_node = NodeProperties::GetType(object).AsWasm();
ControlPathTypes state = GetState(control);
NodeWithType type_from_state = state.LookupState(object);
// We manually resolve TypeGuard aliases in the state.
while (object->opcode() == IrOpcode::kTypeGuard && !type_from_state.IsSet()) {
object = NodeProperties::GetValueInput(object, 0);
type_from_state = state.LookupState(object);
}
return type_from_state.IsSet()
? wasm::Intersection(type_from_node, type_from_state.type)
: type_from_node;
}
Reduction WasmGCOperatorReducer::ReduceIf(Node* node, bool condition) {
DCHECK(node->opcode() == IrOpcode::kIfTrue ||
node->opcode() == IrOpcode::kIfFalse);
Node* branch = NodeProperties::GetControlInput(node);
DCHECK_EQ(branch->opcode(), IrOpcode::kBranch);
if (!IsReduced(branch)) return NoChange();
ControlPathTypes parent_state = GetState(branch);
Node* condition_node = NodeProperties::GetValueInput(branch, 0);
switch (condition_node->opcode()) {
case IrOpcode::kWasmTypeCheck: {
if (!condition) break;
Node* object = NodeProperties::GetValueInput(condition_node, 0);
wasm::TypeInModule object_type = ObjectTypeFromContext(object, branch);
if (object_type.type.is_bottom()) return NoChange();
Node* rtt = NodeProperties::GetValueInput(condition_node, 1);
wasm::ValueType rtt_type = wasm::ValueType::Ref(
NodeProperties::GetType(rtt).AsWasm().type.ref_index(),
wasm::kNullable);
// TODO(manoskouk): Think about {module_} below if we have cross-module
// inlining.
wasm::TypeInModule new_type =
wasm::Intersection(object_type, {rtt_type, module_});
return UpdateNodeAndAliasesTypes(node, parent_state, object, new_type,
true);
}
case IrOpcode::kIsNull:
case IrOpcode::kIsNotNull: {
// If the condition is a failed null check, narrow the type of the checked
// object to non-nullable.
// TODO(manoskouk): Also implement the other branch once we have nullref.
if ((!condition && (condition_node->opcode() == IrOpcode::kIsNotNull)) ||
(condition && (condition_node->opcode() == IrOpcode::kIsNull))) {
break;
}
Node* object = NodeProperties::GetValueInput(condition_node, 0);
Node* control = NodeProperties::GetControlInput(condition_node);
wasm::TypeInModule object_type = ObjectTypeFromContext(object, control);
if (object_type.type.is_bottom()) return NoChange();
object_type.type = object_type.type.AsNonNull();
return UpdateNodeAndAliasesTypes(node, parent_state, object, object_type,
true);
}
default:
break;
}
return TakeStatesFromFirstControl(node);
}
Reduction WasmGCOperatorReducer::ReduceMerge(Node* node) {
// Shortcut for the case when we do not know anything about some
// input.
Node::Inputs inputs = node->inputs();
for (Node* input : inputs) {
if (!IsReduced(input)) return NoChange();
}
auto input_it = inputs.begin();
DCHECK_GT(inputs.count(), 0);
ControlPathTypes types = GetState(*input_it);
++input_it;
auto input_end = inputs.end();
for (; input_it != input_end; ++input_it) {
// Change the current type block list to a longest common prefix of this
// state list and the other list. (The common prefix should correspond to
// the state of the common dominator.)
// TODO(manoskouk): Consider computing intersections for some types.
types.ResetToCommonAncestor(GetState(*input_it));
}
return UpdateStates(node, types);
}
Reduction WasmGCOperatorReducer::ReduceAssertNotNull(Node* node) {
DCHECK_EQ(node->opcode(), IrOpcode::kAssertNotNull);
Node* object = NodeProperties::GetValueInput(node, 0);
Node* control = NodeProperties::GetControlInput(node);
if (InDeadBranch(object)) return NoChange();
wasm::TypeInModule object_type = ObjectTypeFromContext(object, control);
if (object_type.type.is_bottom()) return NoChange();
// Optimize the check away if the argument is known to be non-null.
if (!NodeProperties::GetType(object).AsWasm().type.is_nullable()) {
if (object_type.type.is_non_nullable()) {
ReplaceWithValue(node, object);
node->Kill();
return Replace(object);
}
return NoChange();
object_type.type = object_type.type.AsNonNull();
return UpdateNodeAndAliasesTypes(node, GetState(control), node, object_type,
false);
}
Reduction WasmGCOperatorReducer::ReduceIsNull(Node* node) {
DCHECK_EQ(node->opcode(), IrOpcode::kIsNull);
Reduction WasmGCOperatorReducer::ReduceCheckNull(Node* node) {
DCHECK(node->opcode() == IrOpcode::kIsNull ||
node->opcode() == IrOpcode::kIsNotNull);
Node* object = NodeProperties::GetValueInput(node, 0);
Node* control = NodeProperties::GetControlInput(node);
if (InDeadBranch(object)) return NoChange();
wasm::TypeInModule object_type = ObjectTypeFromContext(object, control);
if (object_type.type.is_bottom()) return NoChange();
// Optimize the check away if the argument is known to be non-null.
if (!NodeProperties::GetType(object).AsWasm().type.is_nullable()) {
ReplaceWithValue(node, gasm_.Int32Constant(0));
if (object_type.type.is_non_nullable()) {
ReplaceWithValue(
node, gasm_.Int32Constant(node->opcode() == IrOpcode::kIsNull ? 0 : 1));
node->Kill();
return Replace(object); // Irrelevant replacement.
}
// Optimize the check away if the argument is known to be null.
if (object->opcode() == IrOpcode::kNull) {
ReplaceWithValue(node, gasm_.Int32Constant(1));
ReplaceWithValue(
node, gasm_.Int32Constant(node->opcode() == IrOpcode::kIsNull ? 1 : 0));
node->Kill();
return Replace(object); // Irrelevant replacement.
}
......@@ -94,12 +244,10 @@ Reduction WasmGCOperatorReducer::ReduceWasmTypeCast(Node* node) {
Node* object = NodeProperties::GetValueInput(node, 0);
Node* rtt = NodeProperties::GetValueInput(node, 1);
if (InDeadBranch(object) || InDeadBranch(rtt)) return NoChange();
wasm::TypeInModule object_type = NodeProperties::GetType(object).AsWasm();
wasm::TypeInModule rtt_type = NodeProperties::GetType(rtt).AsWasm();
wasm::TypeInModule object_type = ObjectTypeFromContext(object, control);
if (object_type.type.is_bottom()) return NoChange();
if (InDeadBranch(rtt)) return NoChange();
wasm::TypeInModule rtt_type = NodeProperties::GetType(rtt).AsWasm();
if (wasm::IsHeapSubtypeOf(object_type.type.heap_type(),
wasm::HeapType(rtt_type.type.ref_index()),
......@@ -122,43 +270,50 @@ Reduction WasmGCOperatorReducer::ReduceWasmTypeCast(Node* node) {
gasm_.TrapUnless(SetType(non_trapping_condition, wasm::kWasmI32),
TrapId::kTrapIllegalCast);
// TODO(manoskouk): Improve the type when we have nullref.
Node* null_node = gasm_.Null();
ReplaceWithValue(
node,
SetType(null_node, wasm::ValueType::Ref(rtt_type.type.ref_index(),
wasm::kNullable)),
gasm_.effect(), gasm_.control());
Node* null_node = SetType(
gasm_.Null(),
wasm::ValueType::Ref(rtt_type.type.ref_index(), wasm::kNullable));
ReplaceWithValue(node, null_node, gasm_.effect(), gasm_.control());
node->Kill();
return Replace(null_node);
}
// Remove the null check from the cast if able.
if (!object_type.type.is_nullable() &&
OpParameter<WasmTypeCheckConfig>(node->op()).object_can_be_null) {
if (!object_type.type.is_nullable()) {
uint8_t rtt_depth = OpParameter<WasmTypeCheckConfig>(node->op()).rtt_depth;
NodeProperties::ChangeOp(
node, gasm_.simplified()->WasmTypeCast(
{/* object_can_be_null = */ false, rtt_depth}));
return Changed(node);
node, gasm_.simplified()->WasmTypeCast({false, // object_can_be_null
rtt_depth}));
}
return NoChange();
// TODO(manoskouk): Think about {module_} below if we have cross-module
// inlining.
wasm::TypeInModule new_type = wasm::Intersection(
object_type,
{wasm::ValueType::Ref(rtt_type.type.ref_index(), wasm::kNullable),
module_});
return UpdateNodeAndAliasesTypes(node, GetState(control), node, new_type,
false);
}
Reduction WasmGCOperatorReducer::ReduceWasmTypeCheck(Node* node) {
DCHECK_EQ(node->opcode(), IrOpcode::kWasmTypeCheck);
Node* object = NodeProperties::GetValueInput(node, 0);
Node* rtt = NodeProperties::GetValueInput(node, 1);
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
if (InDeadBranch(object) || InDeadBranch(rtt)) return NoChange();
wasm::TypeInModule object_type = NodeProperties::GetType(object).AsWasm();
wasm::TypeInModule object_type = ObjectTypeFromContext(object, control);
if (object_type.type.is_bottom()) return NoChange();
if (InDeadBranch(rtt)) return NoChange();
wasm::TypeInModule rtt_type = NodeProperties::GetType(rtt).AsWasm();
if (wasm::IsHeapSubtypeOf(object_type.type.heap_type(),
wasm::HeapType(rtt_type.type.ref_index()),
object_type.module, rtt_type.module)) {
// Type cast will fail only on null.
gasm_.InitializeEffectControl(effect, control);
Node* condition =
SetType(object_type.type.is_nullable() ? gasm_.IsNotNull(object)
: gasm_.Int32Constant(1),
......@@ -178,15 +333,14 @@ Reduction WasmGCOperatorReducer::ReduceWasmTypeCheck(Node* node) {
}
// Remove the null check from the typecheck if able.
if (!object_type.type.is_nullable() &&
OpParameter<WasmTypeCheckConfig>(node->op()).object_can_be_null) {
if (!object_type.type.is_nullable()) {
uint8_t rtt_depth = OpParameter<WasmTypeCheckConfig>(node->op()).rtt_depth;
NodeProperties::ChangeOp(
node, gasm_.simplified()->WasmTypeCheck({false, rtt_depth}));
return Changed(node);
node, gasm_.simplified()->WasmTypeCheck({false, // object_can_be_null
rtt_depth}));
}
return NoChange();
return TakeStatesFromFirstControl(node);
}
} // namespace compiler
......
......@@ -9,8 +9,11 @@
#ifndef V8_COMPILER_WASM_GC_OPERATOR_REDUCER_H_
#define V8_COMPILER_WASM_GC_OPERATOR_REDUCER_H_
#include "src/compiler/control-path-state.h"
#include "src/compiler/graph-reducer.h"
#include "src/compiler/persistent-map.h"
#include "src/compiler/wasm-graph-assembler.h"
#include "src/wasm/wasm-subtyping.h"
namespace v8 {
namespace internal {
......@@ -18,9 +21,28 @@ namespace compiler {
class MachineGraph;
class WasmGCOperatorReducer final : public AdvancedReducer {
struct NodeWithType {
NodeWithType() : node(nullptr), type(wasm::kWasmVoid, nullptr) {}
NodeWithType(Node* node, wasm::TypeInModule type) : node(node), type(type) {}
bool operator==(const NodeWithType& other) const {
return node == other.node && type == other.type;
}
bool operator!=(const NodeWithType& other) const { return !(*this == other); }
bool IsSet() { return node != nullptr; }
Node* node;
wasm::TypeInModule type;
};
// This class optimizes away wasm-gc nodes based on the types of their
// arguments. Although types have been assigned to nodes already, this class
// also tracks additional type information along control paths.
class WasmGCOperatorReducer final
: public AdvancedReducerWithControlPathState<NodeWithType> {
public:
WasmGCOperatorReducer(Editor* editor, MachineGraph* mcgraph,
WasmGCOperatorReducer(Editor* editor, Zone* temp_zone_, MachineGraph* mcgraph,
const wasm::WasmModule* module);
const char* reducer_name() const override { return "WasmGCOperatorReducer"; }
......@@ -28,12 +50,22 @@ class WasmGCOperatorReducer final : public AdvancedReducer {
Reduction Reduce(Node* node) final;
private:
using ControlPathTypes = ControlPathState<NodeWithType>;
Reduction ReduceAssertNotNull(Node* node);
Reduction ReduceIsNull(Node* node);
Reduction ReduceCheckNull(Node* node);
Reduction ReduceWasmTypeCheck(Node* node);
Reduction ReduceWasmTypeCast(Node* node);
Reduction ReduceMerge(Node* node);
Reduction ReduceIf(Node* node, bool condition);
Reduction ReduceStart(Node* node);
Node* SetType(Node* node, wasm::ValueType type);
wasm::TypeInModule ObjectTypeFromContext(Node* object, Node* control);
Reduction UpdateNodeAndAliasesTypes(Node* state_owner,
ControlPathTypes parent_state, Node* node,
wasm::TypeInModule type,
bool in_new_block);
Graph* graph() { return mcgraph_->graph(); }
CommonOperatorBuilder* common() { return mcgraph_->common(); }
......
......@@ -359,11 +359,11 @@ Node* WasmGraphAssembler::Null() {
}
Node* WasmGraphAssembler::IsNull(Node* object) {
return AddNode(graph()->NewNode(simplified_.IsNull(), object));
return AddNode(graph()->NewNode(simplified_.IsNull(), object, control()));
}
Node* WasmGraphAssembler::IsNotNull(Node* object) {
return AddNode(graph()->NewNode(simplified_.IsNotNull(), object));
return AddNode(graph()->NewNode(simplified_.IsNotNull(), object, control()));
}
Node* WasmGraphAssembler::AssertNotNull(Node* object) {
......
......@@ -112,9 +112,9 @@ Reduction WasmTyper::Reduce(Node* node) {
// AssertNotNull: Reverse the order of these operations, as this will
// unlock more optimizations later.
// We are implementing this in the typer so we can retype the nodes.
if (control->opcode() == IrOpcode::kWasmTypeCast && effect == object &&
control == object &&
!NodeProperties::GetType(object).AsWasm().type.is_bottom()) {
while (control->opcode() == IrOpcode::kWasmTypeCast &&
effect == object && control == object &&
!NodeProperties::GetType(object).AsWasm().type.is_bottom()) {
Node* initial_object = NodeProperties::GetValueInput(object, 0);
Node* previous_control = NodeProperties::GetControlInput(object);
Node* previous_effect = NodeProperties::GetEffectInput(object);
......@@ -128,7 +128,11 @@ Reduction WasmTyper::Reduce(Node* node) {
object->ReplaceInput(NodeProperties::FirstValueIndex(object), node);
object->ReplaceInput(NodeProperties::FirstEffectIndex(object), node);
object->ReplaceInput(NodeProperties::FirstControlIndex(object), node);
Revisit(node);
Revisit(object);
object = initial_object;
control = previous_control;
effect = previous_effect;
}
}
......
......@@ -403,3 +403,68 @@ d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
let instance = builder.instantiate();
assertEquals(10, instance.exports.main(10));
})();
(function PathBasedTypedOptimization() {
print(arguments.callee.name);
var builder = new WasmModuleBuilder();
let super_struct = builder.addStruct([makeField(kWasmI32, true)]);
let mid_struct = builder.addStruct(
[makeField(kWasmI32, true), makeField(kWasmI32, true)], super_struct);
let sub_struct = builder.addStruct(
[makeField(kWasmI32, true), makeField(kWasmI32, true),
makeField(kWasmI32, true)],
mid_struct);
let addToLocal = [kExprLocalGet, 1, kExprI32Add, kExprLocalSet, 1];
builder.addFunction(
"main", makeSig([wasmOptRefType(super_struct)], [kWasmI32]))
.addLocals(kWasmI32, 1)
.addBody([
kExprLocalGet, 0,
kGCPrefix, kExprRefTestStatic, sub_struct,
// These casts have to be preserved.
kExprLocalGet, 0,
kGCPrefix, kExprRefCastStatic, mid_struct,
kGCPrefix, kExprRefCastStatic, sub_struct,
kGCPrefix, kExprStructGet, sub_struct, 1,
...addToLocal,
kExprIf, kWasmVoid,
// Both these casts should be optimized away.
kExprLocalGet, 0,
kGCPrefix, kExprRefCastStatic, mid_struct,
kGCPrefix, kExprRefCastStatic, sub_struct,
kGCPrefix, kExprStructGet, sub_struct, 1,
...addToLocal,
kExprBlock, kWasmOptRef, super_struct,
kExprLocalGet, 0,
// This should also get optimized away.
kGCPrefix, kExprBrOnCastStaticFail, 0, mid_struct,
// So should this, despite being represented by a TypeGuard alias.
kGCPrefix, kExprRefCastStatic, sub_struct,
kGCPrefix, kExprStructGet, sub_struct, 1,
...addToLocal,
kExprLocalGet, 0, // Due to the branch result type.
kExprEnd,
kExprDrop,
kExprElse,
// This (always trapping) cast should be preserved.
kExprLocalGet, 0,
kGCPrefix, kExprRefCastStatic, sub_struct,
kGCPrefix, kExprStructGet, sub_struct, 1,
...addToLocal,
kExprEnd,
// This cast should be preserved.
kExprLocalGet, 0,
kGCPrefix, kExprRefCastStatic, sub_struct,
kGCPrefix, kExprStructGet, sub_struct, 1,
kExprLocalGet, 1, kExprI32Add
])
.exportFunc();
builder.instantiate();
})();
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