[wasm-gc] Introduce typed-based optimizations

We introduce a Turbofan pass which optimizes wasm-gc nodes based on
the types of their inputs.

Bug: v8:7748
Change-Id: I281eb0785e9e4201ef925ec201d76dc3d274ad05
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3679198
Reviewed-by: Maya Lekova <mslekova@chromium.org>
Reviewed-by: Jakob Kummerow <jkummerow@chromium.org>
Commit-Queue: Manos Koukoutos <manoskouk@chromium.org>
Cr-Commit-Position: refs/heads/main@{#80929}
This commit is contained in:
Manos Koukoutos 2022-06-03 04:41:19 +00:00 committed by V8 LUCI CQ
parent 80c0e707ae
commit 51d662f712
17 changed files with 335 additions and 5 deletions

View File

@ -2876,6 +2876,8 @@ filegroup(
"src/compiler/wasm-loop-peeling.h",
"src/compiler/wasm-gc-lowering.cc",
"src/compiler/wasm-gc-lowering.h",
"src/compiler/wasm-gc-operator-reducer.cc",
"src/compiler/wasm-gc-operator-reducer.h",
"src/compiler/wasm-graph-assembler.cc",
"src/compiler/wasm-graph-assembler.h",
"src/compiler/wasm-inlining.cc",

View File

@ -3562,6 +3562,7 @@ v8_header_set("v8_internal_headers") {
"src/compiler/wasm-compiler.h",
"src/compiler/wasm-escape-analysis.h",
"src/compiler/wasm-gc-lowering.h",
"src/compiler/wasm-gc-operator-reducer.h",
"src/compiler/wasm-graph-assembler.h",
"src/compiler/wasm-inlining.h",
"src/compiler/wasm-loop-peeling.h",
@ -4063,6 +4064,7 @@ if (v8_enable_webassembly) {
"src/compiler/wasm-compiler.cc",
"src/compiler/wasm-escape-analysis.cc",
"src/compiler/wasm-gc-lowering.cc",
"src/compiler/wasm-gc-operator-reducer.cc",
"src/compiler/wasm-graph-assembler.cc",
"src/compiler/wasm-inlining.cc",
"src/compiler/wasm-loop-peeling.cc",

View File

@ -507,6 +507,7 @@
#define SIMPLIFIED_WASM_OP_LIST(V) \
V(AssertNotNull) \
V(IsNull) \
V(IsNotNull) \
V(Null) \
V(RttCanon) \
V(WasmTypeCast) \

View File

@ -107,6 +107,7 @@
#include "src/compiler/wasm-compiler.h"
#include "src/compiler/wasm-escape-analysis.h"
#include "src/compiler/wasm-gc-lowering.h"
#include "src/compiler/wasm-gc-operator-reducer.h"
#include "src/compiler/wasm-inlining.h"
#include "src/compiler/wasm-loop-peeling.h"
#include "src/compiler/wasm-typer.h"
@ -2073,6 +2074,20 @@ struct WasmTypingPhase {
}
};
struct WasmGCOptimizationPhase {
DECL_PIPELINE_PHASE_CONSTANTS(WasmGCOptimization)
void Run(PipelineData* data, Zone* temp_zone,
const wasm::WasmModule* module) {
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);
AddReducer(data, &graph_reducer, &wasm_gc);
graph_reducer.ReduceGraph();
}
};
struct WasmGCLoweringPhase {
DECL_PIPELINE_PHASE_CONSTANTS(WasmGCLowering)
@ -3299,6 +3314,8 @@ void Pipeline::GenerateCodeForWasmFunction(
if (FLAG_experimental_wasm_gc) {
pipeline.Run<WasmTypingPhase>(function_index);
pipeline.RunPrintAndVerify(WasmTypingPhase::phase_name(), true);
pipeline.Run<WasmGCOptimizationPhase>(module);
pipeline.RunPrintAndVerify(WasmGCOptimizationPhase::phase_name(), true);
pipeline.Run<WasmGCLoweringPhase>();
pipeline.RunPrintAndVerify(WasmGCLoweringPhase::phase_name(), true);
}

View File

@ -1153,6 +1153,13 @@ struct SimplifiedOperatorGlobalCache final {
};
IsNullOperator kIsNull;
struct IsNotNullOperator final : public Operator {
explicit IsNotNullOperator()
: Operator(IrOpcode::kIsNotNull, Operator::kPure, "IsNotNull", 1, 0, 0,
1, 0, 0) {}
};
IsNotNullOperator kIsNotNull;
struct NullOperator final : public Operator {
NullOperator()
: Operator(IrOpcode::kNull, Operator::kPure, "Null", 0, 0, 0, 1, 0, 0) {
@ -1360,6 +1367,9 @@ const Operator* SimplifiedOperatorBuilder::AssertNotNull() {
}
const Operator* SimplifiedOperatorBuilder::IsNull() { return &cache_.kIsNull; }
const Operator* SimplifiedOperatorBuilder::IsNotNull() {
return &cache_.kIsNotNull;
}
#endif // V8_ENABLE_WEBASSEMBLY
const Operator* SimplifiedOperatorBuilder::CheckIf(

View File

@ -1064,6 +1064,7 @@ class V8_EXPORT_PRIVATE SimplifiedOperatorBuilder final
#if V8_ENABLE_WEBASSEMBLY
const Operator* AssertNotNull();
const Operator* IsNull();
const Operator* IsNotNull();
const Operator* Null();
const Operator* RttCanon(int index);
const Operator* WasmTypeCheck(WasmTypeCheckConfig config);

View File

@ -1651,6 +1651,7 @@ void Verifier::Visitor::Check(Node* node, const AllNodes& all) {
case IrOpcode::kRttCanon:
case IrOpcode::kNull:
case IrOpcode::kIsNull:
case IrOpcode::kIsNotNull:
case IrOpcode::kAssertNotNull:
// TODO(manoskouk): What are the constraints here?
break;

View File

@ -50,6 +50,8 @@ Reduction WasmGCLowering::Reduce(Node* node) {
return ReduceNull(node);
case IrOpcode::kIsNull:
return ReduceIsNull(node);
case IrOpcode::kIsNotNull:
return ReduceIsNotNull(node);
case IrOpcode::kRttCanon:
return ReduceRttCanon(node);
case IrOpcode::kTypeGuard:
@ -202,6 +204,13 @@ Reduction WasmGCLowering::ReduceIsNull(Node* node) {
return Replace(gasm_.TaggedEqual(object, Null()));
}
Reduction WasmGCLowering::ReduceIsNotNull(Node* node) {
DCHECK_EQ(node->opcode(), IrOpcode::kIsNotNull);
Node* object = NodeProperties::GetValueInput(node, 0);
return Replace(gasm_.Word32Equal(gasm_.TaggedEqual(object, Null()),
gasm_.Int32Constant(0)));
}
Reduction WasmGCLowering::ReduceRttCanon(Node* node) {
DCHECK_EQ(node->opcode(), IrOpcode::kRttCanon);
int type_index = OpParameter<int>(node->op());

View File

@ -33,6 +33,7 @@ class WasmGCLowering final : public AdvancedReducer {
Reduction ReduceAssertNotNull(Node* node);
Reduction ReduceNull(Node* node);
Reduction ReduceIsNull(Node* node);
Reduction ReduceIsNotNull(Node* node);
Reduction ReduceRttCanon(Node* node);
Reduction ReduceTypeGuard(Node* node);
Node* Null();

View File

@ -0,0 +1,195 @@
// Copyright 2022 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/compiler/wasm-gc-operator-reducer.h"
#include "src/compiler/node-properties.h"
#include "src/compiler/simplified-operator.h"
#include "src/compiler/wasm-compiler-definitions.h"
#include "src/wasm/wasm-subtyping.h"
namespace v8 {
namespace internal {
namespace compiler {
WasmGCOperatorReducer::WasmGCOperatorReducer(Editor* editor,
MachineGraph* mcgraph,
const wasm::WasmModule* module)
: AdvancedReducer(editor),
mcgraph_(mcgraph),
gasm_(mcgraph, mcgraph->zone()),
module_(module) {}
Reduction WasmGCOperatorReducer::Reduce(Node* node) {
switch (node->opcode()) {
case IrOpcode::kAssertNotNull:
return ReduceAssertNotNull(node);
case IrOpcode::kIsNull:
return ReduceIsNull(node);
case IrOpcode::kWasmTypeCheck:
return ReduceWasmTypeCheck(node);
case IrOpcode::kWasmTypeCast:
return ReduceWasmTypeCast(node);
default:
return NoChange();
}
}
namespace {
bool InDeadBranch(Node* node) {
return node->opcode() == IrOpcode::kDead ||
NodeProperties::GetType(node).AsWasm().type.is_bottom();
}
} // namespace
Node* WasmGCOperatorReducer::SetType(Node* node, wasm::ValueType type) {
NodeProperties::SetType(node, Type::Wasm(type, module_, graph()->zone()));
return node;
}
Reduction WasmGCOperatorReducer::ReduceAssertNotNull(Node* node) {
DCHECK_EQ(node->opcode(), IrOpcode::kAssertNotNull);
Node* object = NodeProperties::GetValueInput(node, 0);
if (InDeadBranch(object)) 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, object);
node->Kill();
return Replace(object);
}
return NoChange();
}
Reduction WasmGCOperatorReducer::ReduceIsNull(Node* node) {
DCHECK_EQ(node->opcode(), IrOpcode::kIsNull);
Node* object = NodeProperties::GetValueInput(node, 0);
if (InDeadBranch(object)) 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));
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));
node->Kill();
return Replace(object); // Irrelevant replacement.
}
return NoChange();
}
Reduction WasmGCOperatorReducer::ReduceWasmTypeCast(Node* node) {
DCHECK_EQ(node->opcode(), IrOpcode::kWasmTypeCast);
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(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();
if (object_type.type.is_bottom()) return NoChange();
if (wasm::IsHeapSubtypeOf(object_type.type.heap_type(),
wasm::HeapType(rtt_type.type.ref_index()),
object_type.module, rtt_type.module)) {
// Type cast will always succeed. Remove it.
ReplaceWithValue(node, object);
node->Kill();
return Replace(object);
}
if (wasm::HeapTypesUnrelated(object_type.type.heap_type(),
wasm::HeapType(rtt_type.type.ref_index()),
object_type.module, rtt_type.module)) {
gasm_.InitializeEffectControl(effect, control);
// A cast between unrelated types can only succeed if the argument is null.
// Otherwise, it always fails.
Node* non_trapping_condition = object_type.type.is_nullable()
? gasm_.IsNull(object)
: gasm_.Int32Constant(0);
Node* trap =
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)),
effect, trap);
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) {
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);
}
return NoChange();
}
Reduction WasmGCOperatorReducer::ReduceWasmTypeCheck(Node* node) {
DCHECK_EQ(node->opcode(), IrOpcode::kWasmTypeCheck);
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();
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.
Node* condition =
SetType(object_type.type.is_nullable() ? gasm_.IsNotNull(object)
: gasm_.Int32Constant(1),
wasm::kWasmI32);
ReplaceWithValue(node, condition);
node->Kill();
return Replace(condition);
}
if (wasm::HeapTypesUnrelated(object_type.type.heap_type(),
wasm::HeapType(rtt_type.type.ref_index()),
object_type.module, rtt_type.module)) {
Node* condition = SetType(gasm_.Int32Constant(0), wasm::kWasmI32);
ReplaceWithValue(node, condition);
node->Kill();
return Replace(condition);
}
// Remove the null check from the typecheck if able.
if (!object_type.type.is_nullable() &&
OpParameter<WasmTypeCheckConfig>(node->op()).object_can_be_null) {
uint8_t rtt_depth = OpParameter<WasmTypeCheckConfig>(node->op()).rtt_depth;
NodeProperties::ChangeOp(
node, gasm_.simplified()->WasmTypeCheck({false, rtt_depth}));
return Changed(node);
}
return NoChange();
}
} // namespace compiler
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,50 @@
// Copyright 2022 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#if !V8_ENABLE_WEBASSEMBLY
#error This header should only be included if WebAssembly is enabled.
#endif // !V8_ENABLE_WEBASSEMBLY
#ifndef V8_COMPILER_WASM_GC_OPERATOR_REDUCER_H_
#define V8_COMPILER_WASM_GC_OPERATOR_REDUCER_H_
#include "src/compiler/graph-reducer.h"
#include "src/compiler/wasm-graph-assembler.h"
namespace v8 {
namespace internal {
namespace compiler {
class MachineGraph;
class WasmGCOperatorReducer final : public AdvancedReducer {
public:
WasmGCOperatorReducer(Editor* editor, MachineGraph* mcgraph,
const wasm::WasmModule* module);
const char* reducer_name() const override { return "WasmGCOperatorReducer"; }
Reduction Reduce(Node* node) final;
private:
Reduction ReduceAssertNotNull(Node* node);
Reduction ReduceIsNull(Node* node);
Reduction ReduceWasmTypeCheck(Node* node);
Reduction ReduceWasmTypeCast(Node* node);
Node* SetType(Node* node, wasm::ValueType type);
Graph* graph() { return mcgraph_->graph(); }
CommonOperatorBuilder* common() { return mcgraph_->common(); }
MachineGraph* mcgraph_;
WasmGraphAssembler gasm_;
const wasm::WasmModule* module_;
};
} // namespace compiler
} // namespace internal
} // namespace v8
#endif // V8_COMPILER_WASM_GC_OPERATOR_REDUCER_H_

View File

@ -368,6 +368,10 @@ Node* WasmGraphAssembler::IsNull(Node* object) {
return AddNode(graph()->NewNode(simplified_.IsNull(), object));
}
Node* WasmGraphAssembler::IsNotNull(Node* object) {
return AddNode(graph()->NewNode(simplified_.IsNotNull(), object));
}
Node* WasmGraphAssembler::AssertNotNull(Node* object) {
return AddNode(graph()->NewNode(simplified_.AssertNotNull(), object, effect(),
control()));

View File

@ -248,6 +248,8 @@ class WasmGraphAssembler : public GraphAssembler {
Node* IsNull(Node* object);
Node* IsNotNull(Node* object);
Node* AssertNotNull(Node* object);
// Generic helpers.

View File

@ -103,6 +103,35 @@ Reduction WasmTyper::Reduce(Node* node) {
break;
}
case IrOpcode::kAssertNotNull: {
{
Node* object = NodeProperties::GetValueInput(node, 0);
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
// Optimize the common pattern where a TypeCast is followed by an
// 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) {
Node* initial_object = NodeProperties::GetValueInput(object, 0);
Node* previous_control = NodeProperties::GetControlInput(object);
Node* previous_effect = NodeProperties::GetEffectInput(object);
ReplaceWithValue(node, object);
node->ReplaceInput(NodeProperties::FirstValueIndex(node),
initial_object);
node->ReplaceInput(NodeProperties::FirstEffectIndex(node),
previous_effect);
node->ReplaceInput(NodeProperties::FirstControlIndex(node),
previous_control);
// We do not replace {object}'s effect input to {node} because {node}
// has no effect output.
object->ReplaceInput(NodeProperties::FirstValueIndex(object), node);
object->ReplaceInput(NodeProperties::FirstControlIndex(object), node);
Revisit(object);
}
}
if (!AllInputsTyped(node)) return NoChange();
TypeInModule object_type =
NodeProperties::GetType(NodeProperties::GetValueInput(node, 0))

View File

@ -380,6 +380,7 @@ class RuntimeCallTimer final {
ADD_THREAD_SPECIFIC_COUNTER(V, Optimize, VerifyGraph) \
ADD_THREAD_SPECIFIC_COUNTER(V, Optimize, WasmBaseOptimization) \
ADD_THREAD_SPECIFIC_COUNTER(V, Optimize, WasmGCLowering) \
ADD_THREAD_SPECIFIC_COUNTER(V, Optimize, WasmGCOptimization) \
ADD_THREAD_SPECIFIC_COUNTER(V, Optimize, WasmInlining) \
ADD_THREAD_SPECIFIC_COUNTER(V, Optimize, WasmLoopPeeling) \
ADD_THREAD_SPECIFIC_COUNTER(V, Optimize, WasmLoopUnrolling) \

View File

@ -4220,12 +4220,10 @@ class WasmFullDecoder : public WasmDecoder<validate, decoding_mode> {
this->module_);
}
// Checks it {obj} is a nominal type which is a subtype of {rtt}'s index, thus
// checking will always succeed. Does not account for nullability.
// Checks it {obj} is a subtype of {rtt}'s type, thus checking will always
// succeed. Does not account for nullability.
bool TypeCheckAlwaysSucceeds(Value obj, Value rtt) {
return obj.type.has_index() &&
this->module_->has_supertype(obj.type.ref_index()) &&
IsSubtypeOf(obj.type,
return IsSubtypeOf(obj.type,
ValueType::Ref(rtt.type.ref_index(), kNullable),
this->module_);
}

View File

@ -90,6 +90,13 @@ V8_INLINE bool IsHeapSubtypeOf(HeapType subtype, HeapType supertype,
return IsHeapSubtypeOfImpl(subtype, supertype, module, module);
}
V8_INLINE bool HeapTypesUnrelated(HeapType heap1, HeapType heap2,
const WasmModule* module1,
const WasmModule* module2) {
return !IsHeapSubtypeOf(heap1, heap2, module1, module2) &&
!IsHeapSubtypeOf(heap2, heap1, module2, module1);
}
// Checks whether {subtype_index} is valid as a declared subtype of
// {supertype_index}.
// - Both type must be of the same kind (function, struct, or array).