[turboshaft] Type-based reduction verification
Bug: v8:12783 Change-Id: Icb0ff1ff228acf84c8fd4e5a0896ef6558f57248 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/4120260 Commit-Queue: Nico Hartmann <nicohartmann@chromium.org> Reviewed-by: Darius Mercadier <dmercadier@chromium.org> Cr-Commit-Position: refs/heads/main@{#85179}
This commit is contained in:
parent
edd7739b63
commit
d25d23f54d
@ -2923,6 +2923,7 @@ filegroup(
|
||||
"src/compiler/turboshaft/optimization-phase.h",
|
||||
"src/compiler/turboshaft/recreate-schedule.cc",
|
||||
"src/compiler/turboshaft/recreate-schedule.h",
|
||||
"src/compiler/turboshaft/reducer-traits.h",
|
||||
"src/compiler/turboshaft/representations.cc",
|
||||
"src/compiler/turboshaft/representations.h",
|
||||
"src/compiler/turboshaft/select-lowering-reducer.h",
|
||||
|
1
BUILD.gn
1
BUILD.gn
@ -3018,6 +3018,7 @@ v8_header_set("v8_internal_headers") {
|
||||
"src/compiler/turboshaft/operations.h",
|
||||
"src/compiler/turboshaft/optimization-phase.h",
|
||||
"src/compiler/turboshaft/recreate-schedule.h",
|
||||
"src/compiler/turboshaft/reducer-traits.h",
|
||||
"src/compiler/turboshaft/representations.h",
|
||||
"src/compiler/turboshaft/select-lowering-reducer.h",
|
||||
"src/compiler/turboshaft/sidetable.h",
|
||||
|
@ -1985,7 +1985,8 @@ struct LateOptimizationPhase {
|
||||
turboshaft::VariableReducer, turboshaft::BranchEliminationReducer,
|
||||
turboshaft::SelectLoweringReducer,
|
||||
turboshaft::MachineOptimizationReducerSignallingNanImpossible,
|
||||
turboshaft::ValueNumberingReducer>::Run(&data->turboshaft_graph(),
|
||||
turboshaft::ValueNumberingReducer>::Run(data->isolate(),
|
||||
&data->turboshaft_graph(),
|
||||
temp_zone,
|
||||
data->node_origins());
|
||||
} else {
|
||||
@ -2117,7 +2118,8 @@ struct OptimizeTurboshaftPhase {
|
||||
turboshaft::MemoryOptimizationReducer, turboshaft::VariableReducer,
|
||||
turboshaft::MachineOptimizationReducerSignallingNanImpossible,
|
||||
turboshaft::ValueNumberingReducer>::
|
||||
Run(&data->turboshaft_graph(), temp_zone, data->node_origins(),
|
||||
Run(data->isolate(), &data->turboshaft_graph(), temp_zone,
|
||||
data->node_origins(),
|
||||
std::tuple{
|
||||
turboshaft::MemoryOptimizationReducerArgs{data->isolate()}});
|
||||
}
|
||||
@ -2130,7 +2132,8 @@ struct TurboshaftTypedOptimizationsPhase {
|
||||
DCHECK(data->HasTurboshaftGraph());
|
||||
turboshaft::OptimizationPhase<turboshaft::TypedOptimizationsReducer,
|
||||
turboshaft::TypeInferenceReducer>::
|
||||
Run(&data->turboshaft_graph(), temp_zone, data->node_origins(),
|
||||
Run(data->isolate(), &data->turboshaft_graph(), temp_zone,
|
||||
data->node_origins(),
|
||||
std::tuple{turboshaft::TypeInferenceReducerArgs{data->isolate()}});
|
||||
}
|
||||
};
|
||||
@ -2145,7 +2148,8 @@ struct TurboshaftTypeAssertionsPhase {
|
||||
turboshaft::OptimizationPhase<turboshaft::AssertTypesReducer,
|
||||
turboshaft::ValueNumberingReducer,
|
||||
turboshaft::TypeInferenceReducer>::
|
||||
Run(&data->turboshaft_graph(), temp_zone, data->node_origins(),
|
||||
Run(data->isolate(), &data->turboshaft_graph(), temp_zone,
|
||||
data->node_origins(),
|
||||
std::tuple{turboshaft::TypeInferenceReducerArgs{data->isolate()},
|
||||
turboshaft::AssertTypesReducerArgs{data->isolate()}});
|
||||
}
|
||||
@ -2158,8 +2162,8 @@ struct TurboshaftDeadCodeEliminationPhase {
|
||||
DCHECK(data->HasTurboshaftGraph());
|
||||
|
||||
turboshaft::OptimizationPhase<turboshaft::DeadCodeEliminationReducer>::Run(
|
||||
&data->turboshaft_graph(), temp_zone, data->node_origins(),
|
||||
std::tuple{});
|
||||
data->isolate(), &data->turboshaft_graph(), temp_zone,
|
||||
data->node_origins());
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include "src/compiler/turboshaft/operation-matching.h"
|
||||
#include "src/compiler/turboshaft/operations.h"
|
||||
#include "src/compiler/turboshaft/optimization-phase.h"
|
||||
#include "src/compiler/turboshaft/reducer-traits.h"
|
||||
#include "src/compiler/turboshaft/representations.h"
|
||||
#include "src/compiler/turboshaft/sidetable.h"
|
||||
#include "src/compiler/turboshaft/snapshot-table.h"
|
||||
@ -61,13 +62,6 @@ class ReducerStack<Assembler> {
|
||||
template <typename Next>
|
||||
class ReducerBase;
|
||||
|
||||
template <typename Next>
|
||||
struct next_is_bottom_of_assembler_stack
|
||||
: public std::integral_constant<bool, false> {};
|
||||
template <typename A>
|
||||
struct next_is_bottom_of_assembler_stack<ReducerStack<A, ReducerBase>>
|
||||
: public std::integral_constant<bool, true> {};
|
||||
|
||||
// LABEL_BLOCK is used in Reducers to have a single call forwarding to the next
|
||||
// reducer without change. A typical use would be:
|
||||
//
|
||||
@ -116,6 +110,14 @@ class ReducerBase : public ReducerBaseForwarder<Next> {
|
||||
|
||||
void Analyze() {}
|
||||
|
||||
#ifdef DEBUG
|
||||
void Verify(OpIndex old_index, OpIndex new_index) {}
|
||||
#endif // DEBUG
|
||||
|
||||
void RemoveLast(OpIndex index_of_last_operation) {
|
||||
Asm().output_graph().RemoveLast();
|
||||
}
|
||||
|
||||
bool ShouldEliminateOperation(OpIndex index, const Operation& op) {
|
||||
return op.saturated_use_count == 0;
|
||||
}
|
||||
|
@ -19,10 +19,17 @@
|
||||
#include "src/compiler/turboshaft/graph.h"
|
||||
#include "src/compiler/turboshaft/index.h"
|
||||
#include "src/compiler/turboshaft/operations.h"
|
||||
#include "src/compiler/turboshaft/reducer-traits.h"
|
||||
#include "src/compiler/turboshaft/snapshot-table.h"
|
||||
|
||||
namespace v8::internal::compiler::turboshaft {
|
||||
|
||||
template <typename>
|
||||
class TypeInferenceReducer;
|
||||
struct TypeInferenceReducerArgs {
|
||||
Isolate* isolate;
|
||||
};
|
||||
|
||||
using Variable =
|
||||
SnapshotTable<OpIndex, base::Optional<RegisterRepresentation>>::Key;
|
||||
using MaybeVariable = base::Optional<Variable>;
|
||||
@ -58,9 +65,8 @@ V8_INLINE bool ShouldSkipOperation(const Operation& op) {
|
||||
}
|
||||
|
||||
template <template <class> class... Reducers>
|
||||
class OptimizationPhase {
|
||||
class OptimizationPhaseImpl {
|
||||
public:
|
||||
template <class... ReducerArgs>
|
||||
static void Run(Graph* input, Zone* phase_zone, NodeOriginTable* origins,
|
||||
const typename Assembler<Reducers...>::ArgT& reducer_args =
|
||||
std::tuple<>{}) {
|
||||
@ -72,10 +78,61 @@ class OptimizationPhase {
|
||||
phase.template VisitGraph<false>();
|
||||
}
|
||||
}
|
||||
static void RunWithoutTracing(Graph* input, Zone* phase_zone) {
|
||||
Assembler<Reducers...> phase(input, input->GetOrCreateCompanion(),
|
||||
phase_zone, nullptr);
|
||||
phase->template VisitGraph<false>();
|
||||
};
|
||||
|
||||
template <template <typename> typename... Reducers>
|
||||
class OptimizationPhase {
|
||||
using impl_t = OptimizationPhaseImpl<Reducers...>;
|
||||
#ifdef DEBUG
|
||||
// In Debug builds we provide two different versions of the reducer stack:
|
||||
//
|
||||
// 1. As specified by the pipeline (see impl_t).
|
||||
// 2. With an additional TypeInferenceReducer added that computes and
|
||||
// verifies the types of the output graph with respect to the input
|
||||
// graph (see impl_with_verification_t). If the stack for a particular
|
||||
// phase already contains a TypeInferenceReducer, we don't add another
|
||||
// one and impl_t and impl_with_verification_t are identical.
|
||||
//
|
||||
|
||||
// Check if the reducer stack already contains a TypeInferenceReducer.
|
||||
static constexpr bool has_type_inference =
|
||||
reducer_list_contains<reducer_list<Reducers...>,
|
||||
TypeInferenceReducer>::value;
|
||||
// If it does not, add a TypeInferenceReducer at the bottom of the stack.
|
||||
// Otherwise just use the stack (impl_t) for the verification.
|
||||
using impl_with_verification_t = std::conditional_t<
|
||||
has_type_inference, impl_t,
|
||||
OptimizationPhaseImpl<Reducers..., TypeInferenceReducer>>;
|
||||
// If the stack (impl_t) did not yet contain a TypeInferenceReducer and we
|
||||
// added one, we also have to add the appropriate arguments to the tuple.
|
||||
template <typename... Args>
|
||||
static auto AdaptArgsForVerification(std::tuple<Args...> args,
|
||||
Isolate* isolate) {
|
||||
if constexpr (has_type_inference) {
|
||||
return args;
|
||||
} else {
|
||||
return std::tuple_cat(std::make_tuple(TypeInferenceReducerArgs{isolate}),
|
||||
args);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
public:
|
||||
static void Run(Isolate* isolate, Graph* input, Zone* phase_zone,
|
||||
NodeOriginTable* origins,
|
||||
const typename Assembler<Reducers...>::ArgT& reducer_args =
|
||||
std::tuple<>{}) {
|
||||
#ifdef DEBUG
|
||||
if (v8_flags.turboshaft_verify_reductions) {
|
||||
impl_with_verification_t::Run(
|
||||
input, phase_zone, origins,
|
||||
AdaptArgsForVerification(reducer_args, isolate));
|
||||
} else {
|
||||
#endif // DEBUG
|
||||
impl_t::Run(input, phase_zone, origins, reducer_args);
|
||||
#ifdef DEBUG
|
||||
}
|
||||
#endif // DEBUG
|
||||
}
|
||||
};
|
||||
|
||||
@ -316,6 +373,13 @@ class GraphVisitor {
|
||||
TraceReductionResult(current_block, first_output_index, new_index);
|
||||
}
|
||||
}
|
||||
#ifdef DEBUG
|
||||
if (V8_UNLIKELY(v8_flags.turboshaft_verify_reductions)) {
|
||||
if (new_index.valid()) {
|
||||
assembler().Verify(index, new_index);
|
||||
}
|
||||
}
|
||||
#endif // DEBUG
|
||||
return true;
|
||||
}
|
||||
|
||||
|
81
src/compiler/turboshaft/reducer-traits.h
Normal file
81
src/compiler/turboshaft/reducer-traits.h
Normal file
@ -0,0 +1,81 @@
|
||||
// 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.
|
||||
|
||||
#ifndef V8_COMPILER_TURBOSHAFT_REDUCER_TRAITS_H_
|
||||
#define V8_COMPILER_TURBOSHAFT_REDUCER_TRAITS_H_
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
namespace v8::internal::compiler::turboshaft {
|
||||
|
||||
template <class Assembler, template <class> class... Reducers>
|
||||
class ReducerStack;
|
||||
|
||||
template <typename Next>
|
||||
class ReducerBase;
|
||||
|
||||
// is_same_reducer compares two reducers.
|
||||
template <template <typename> typename T, template <typename> typename U>
|
||||
struct is_same_reducer : public std::bool_constant<false> {};
|
||||
|
||||
template <template <typename> typename Reducer>
|
||||
struct is_same_reducer<Reducer, Reducer> : public std::bool_constant<true> {};
|
||||
|
||||
template <template <typename> typename...>
|
||||
struct reducer_list {};
|
||||
// Converts a ReducerStack {Next} to a reducer_list<>;
|
||||
template <typename Next>
|
||||
struct reducer_stack_to_list;
|
||||
template <typename A, template <typename> typename... Reducers>
|
||||
struct reducer_stack_to_list<ReducerStack<A, Reducers...>> {
|
||||
using type = reducer_list<Reducers...>;
|
||||
};
|
||||
|
||||
// Checks if a reducer_list<> {RL} contains reducer {R}.
|
||||
template <typename RL, template <typename> typename R>
|
||||
struct reducer_list_contains;
|
||||
template <template <typename> typename R, template <typename> typename Head,
|
||||
template <typename> typename... Tail>
|
||||
struct reducer_list_contains<reducer_list<Head, Tail...>, R> {
|
||||
static constexpr bool value =
|
||||
is_same_reducer<Head, R>::value ||
|
||||
reducer_list_contains<reducer_list<Tail...>, R>::value;
|
||||
};
|
||||
template <template <typename> typename R>
|
||||
struct reducer_list_contains<reducer_list<>, R>
|
||||
: public std::bool_constant<false> {};
|
||||
|
||||
// Checks if a reducer_list<> {RL} starts with reducer {R}.
|
||||
template <typename RL, template <typename> typename R>
|
||||
struct reducer_list_starts_with;
|
||||
template <template <typename> typename R, template <typename> typename Head,
|
||||
template <typename> typename... Tail>
|
||||
struct reducer_list_starts_with<reducer_list<Head, Tail...>, R>
|
||||
: public std::bool_constant<is_same_reducer<Head, R>::value> {};
|
||||
template <template <typename> typename R>
|
||||
struct reducer_list_starts_with<reducer_list<>, R>
|
||||
: public std::bool_constant<false> {};
|
||||
|
||||
// Check if the {Next} ReducerStack contains {Reducer}.
|
||||
template <typename Next, template <typename> typename Reducer>
|
||||
struct next_contains_reducer {
|
||||
using list = typename reducer_stack_to_list<Next>::type;
|
||||
static constexpr bool value = reducer_list_contains<list, Reducer>::value;
|
||||
};
|
||||
|
||||
// Check if in the {Next} ReducerStack, {Reducer} comes next.
|
||||
template <typename Next, template <typename> typename Reducer>
|
||||
struct next_reducer_is {
|
||||
using list = typename reducer_stack_to_list<Next>::type;
|
||||
static constexpr bool value = reducer_list_starts_with<list, Reducer>::value;
|
||||
};
|
||||
|
||||
// Check if {Next} is the bottom of the ReducerStack.
|
||||
template <typename Next>
|
||||
struct next_is_bottom_of_assembler_stack
|
||||
: public next_reducer_is<Next, ReducerBase> {};
|
||||
|
||||
} // namespace v8::internal::compiler::turboshaft
|
||||
|
||||
#endif // V8_COMPILER_TURBOSHAFT_REDUCER_TRAITS_H_
|
@ -985,10 +985,6 @@ class Typer {
|
||||
static bool allow_invalid_inputs() { return true; }
|
||||
};
|
||||
|
||||
struct TypeInferenceReducerArgs {
|
||||
Isolate* isolate;
|
||||
};
|
||||
|
||||
template <class Next>
|
||||
class TypeInferenceReducer : public Next {
|
||||
static_assert(next_is_bottom_of_assembler_stack<Next>::value);
|
||||
@ -1200,7 +1196,9 @@ class TypeInferenceReducer : public Next {
|
||||
Asm().output_graph().Get(op).ToString().substr(0, 40).c_str(),
|
||||
type.ToString().c_str());
|
||||
|
||||
SetType(op, type);
|
||||
auto key_opt = op_to_key_mapping_[op];
|
||||
DCHECK(key_opt.has_value());
|
||||
table_.Set(*key_opt, type);
|
||||
|
||||
// TODO(nicohartmann@): One could push the refined type deeper into the
|
||||
// operations.
|
||||
@ -1383,6 +1381,11 @@ class TypeInferenceReducer : public Next {
|
||||
if (!result_type.IsInvalid()) {
|
||||
if (auto key_opt = op_to_key_mapping_[index]) {
|
||||
table_.Set(*key_opt, result_type);
|
||||
// The new type we see might be unrelated to the existing type because
|
||||
// of value numbering. It may run the typer multiple times on the same
|
||||
// index if the last operation gets removed and the index reused.
|
||||
DCHECK(result_type.IsSubtypeOf(types_[index]));
|
||||
types_[index] = result_type;
|
||||
DCHECK(!types_[index].IsInvalid());
|
||||
} else {
|
||||
auto key = table_.NewKey(Type::None());
|
||||
@ -1399,6 +1402,63 @@ class TypeInferenceReducer : public Next {
|
||||
(result_type.IsInvalid() ? "" : result_type.ToString().c_str()));
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
void Verify(OpIndex input_index, OpIndex output_index) {
|
||||
DCHECK(input_index.valid());
|
||||
DCHECK(output_index.valid());
|
||||
|
||||
const auto& input_type = Asm().input_graph().operation_types()[input_index];
|
||||
const auto& output_type = types_[output_index];
|
||||
|
||||
if (input_type.IsInvalid()) return;
|
||||
DCHECK(!output_type.IsInvalid());
|
||||
|
||||
const bool is_okay = output_type.IsSubtypeOf(input_type);
|
||||
|
||||
TRACE_TYPING(
|
||||
"\033[%s %3d:%-40s %-40s\n %3d:%-40s %-40s\033[0m\n",
|
||||
is_okay ? "32mOK " : "31mFAIL", input_index.id(),
|
||||
Asm().input_graph().Get(input_index).ToString().substr(0, 40).c_str(),
|
||||
input_type.ToString().substr(0, 40).c_str(), output_index.id(),
|
||||
Asm().output_graph().Get(output_index).ToString().substr(0, 40).c_str(),
|
||||
output_type.ToString().substr(0, 40).c_str());
|
||||
|
||||
if (V8_UNLIKELY(!is_okay)) {
|
||||
FATAL(
|
||||
"\033[%s %3d:%-40s %-40s\n %3d:%-40s %-40s\033[0m\n",
|
||||
is_okay ? "32mOK " : "31mFAIL", input_index.id(),
|
||||
Asm().input_graph().Get(input_index).ToString().substr(0, 40).c_str(),
|
||||
input_type.ToString().substr(0, 40).c_str(), output_index.id(),
|
||||
Asm()
|
||||
.output_graph()
|
||||
.Get(output_index)
|
||||
.ToString()
|
||||
.substr(0, 40)
|
||||
.c_str(),
|
||||
output_type.ToString().substr(0, 40).c_str());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void RemoveLast(OpIndex index_of_last_operation) {
|
||||
if (auto key_opt = op_to_key_mapping_[index_of_last_operation]) {
|
||||
op_to_key_mapping_[index_of_last_operation] = base::nullopt;
|
||||
TRACE_TYPING(
|
||||
"\033[32mREM %3d:%-40s %-40s\033[0m\n", index_of_last_operation.id(),
|
||||
Asm()
|
||||
.output_graph()
|
||||
.Get(index_of_last_operation)
|
||||
.ToString()
|
||||
.substr(0, 40)
|
||||
.c_str(),
|
||||
types_[index_of_last_operation].ToString().substr(0, 40).c_str());
|
||||
types_[index_of_last_operation] = Type::Invalid();
|
||||
} else {
|
||||
DCHECK(types_[index_of_last_operation].IsInvalid());
|
||||
}
|
||||
Next::RemoveLast(index_of_last_operation);
|
||||
}
|
||||
|
||||
private:
|
||||
GrowingSidetable<Type>& types_;
|
||||
table_t table_;
|
||||
|
@ -160,7 +160,7 @@ class ValueNumberingReducer : public Next {
|
||||
(!same_block_only ||
|
||||
entry.block == Asm().current_block()->index()) &&
|
||||
entry_op.Cast<Op>() == op) {
|
||||
Asm().output_graph().RemoveLast();
|
||||
Next::RemoveLast(op_idx);
|
||||
return entry.value;
|
||||
}
|
||||
}
|
||||
|
@ -1008,6 +1008,9 @@ DEFINE_UINT64(turboshaft_opt_bisect_limit, std::numeric_limits<uint64_t>::max(),
|
||||
DEFINE_UINT64(turboshaft_opt_bisect_break, std::numeric_limits<uint64_t>::max(),
|
||||
"abort after a specified number of steps, useful for bisecting "
|
||||
"optimization bugs")
|
||||
DEFINE_BOOL(turboshaft_verify_reductions, false,
|
||||
"check that turboshaft reductions are correct with respect to "
|
||||
"inferred types")
|
||||
#endif // DEBUG
|
||||
|
||||
// Favor memory over execution speed.
|
||||
|
Loading…
Reference in New Issue
Block a user