[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:
Nico Hartmann 2023-01-10 13:47:24 +01:00 committed by V8 LUCI CQ
parent edd7739b63
commit d25d23f54d
9 changed files with 241 additions and 25 deletions

View File

@ -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",

View File

@ -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",

View File

@ -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());
}
};

View File

@ -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;
}

View File

@ -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;
}

View 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_

View File

@ -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_;

View File

@ -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;
}
}

View File

@ -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.