5#ifndef V8_COMPILER_TURBOSHAFT_LOOP_UNROLLING_REDUCER_H_
6#define V8_COMPILER_TURBOSHAFT_LOOP_UNROLLING_REDUCER_H_
33 if (v8_flags.turboshaft_trace_unrolling) StdoutStream() << x << std::endl; \
101 : matcher_(matcher) {}
104 const Block* header,
OpIndex cond_idx,
bool loop_if_cond_is)
const;
118 static constexpr CmpOp InvertComparisonOp(
CmpOp op);
131 static constexpr BinOp BinopFromOverflowCheckedBinopKind(
136 bool MatchPhiCompareCst(
OpIndex cond_idx,
138 OpIndex* phi, uint64_t* cst)
const;
145 uint64_t initial_input, uint64_t binop_cst,
147 bool loop_if_cond_is)
const;
150 Int init, Int max,
CmpOp cmp_op, Int binop_cst,
161 static constexpr size_t kMaxExactIter = 5;
183 : input_graph_(input_graph),
184 matcher_(*input_graph),
187 canonical_loop_matcher_(matcher_),
189 stack_checks_to_remove_(input_graph->stack_checks_to_remove()) {
190 DetectUnrollableLoops();
198 if (header_info.
op_count > kMaxLoopSizeForFullUnrolling)
return false;
200 auto iter_count = GetIterationCount(loop_header);
201 return iter_count.IsExact() &&
202 iter_count.exact_count() < kMaxLoopIterationsForFullUnrolling;
209 info.op_count < kMaxLoopSizeForPartialUnrolling;
227 if (input_graph_->op_id_count() > kMaxFunctionSizeForPartialUnrolling) {
233 LoopUnrollingAnalyzer::kMaxPartialUnrollingCount,
234 LoopUnrollingAnalyzer::kWasmMaxUnrolledLoopSize / info.op_count);
236 return LoopUnrollingAnalyzer::kMaxPartialUnrollingCount;
240 auto iter_count = GetIterationCount(loop_header);
241 return iter_count.IsExact() && iter_count.exact_count() == 0;
246 auto it = loop_iteration_count_.find(loop_header);
247 if (it == loop_iteration_count_.end())
return IterationCount::Unknown();
252 const Block* loop_header) {
253 return loop_finder_.GetLoopBody(loop_header);
257 return loop_finder_.GetLoopHeader(block);
265 static constexpr size_t kMaxLoopSizeForFullUnrolling = 150;
270 static constexpr size_t kMaxFunctionSizeForPartialUnrolling = 1'000'000;
271 static constexpr size_t kJSMaxLoopSizeForPartialUnrolling = 50;
272 static constexpr size_t kWasmMaxLoopSizeForPartialUnrolling = 80;
273 static constexpr size_t kWasmMaxUnrolledLoopSize = 240;
274 static constexpr size_t kMaxLoopIterationsForFullUnrolling = 4;
275 static constexpr size_t kMaxPartialUnrollingCount = 4;
276 static constexpr size_t kMaxIterForStackCheckRemoval = 5000;
279 void DetectUnrollableLoops();
291 const size_t kMaxLoopSizeForPartialUnrolling =
292 is_wasm_ ? kWasmMaxLoopSizeForPartialUnrolling
293 : kJSMaxLoopSizeForPartialUnrolling;
294 bool can_unroll_at_least_one_loop_ =
false;
308 Next::Bind(new_block);
311 if (new_block->IsLoop()) {
323 LABEL_BLOCK(no_change) {
return Next::ReduceInputGraphCall(ig_idx, call); }
327 call.IsStackCheck(
__ input_graph(),
broker_,
343 return Next::ReduceInputGraphJSStackCheck(ig_idx, stack_check);
346#if V8_ENABLE_WEBASSEMBLY
348 V<None> ig_idx,
const WasmStackCheckOp& stack_check) {
350 stack_check.kind == WasmStackCheckOp::Kind::kLoop) {
354 return Next::ReduceInputGraphWasmStackCheck(ig_idx, stack_check);
365 __ input_graph().stack_checks_to_remove();
376#if defined(__clang__)
390 LABEL_BLOCK(no_change) {
return Next::ReduceInputGraphGoto(ig_idx, gto); }
392 const Block* dst = gto.destination;
420 return Next::ReduceInputGraphBranch(ig_idx, branch);
430 bool is_false_in_loop =
433 if (is_true_in_loop && !is_false_in_loop) {
434 __ Goto(
__ MapToNewGraph(branch.if_false));
436 }
else if (is_false_in_loop && !is_true_in_loop) {
437 __ Goto(
__ MapToNewGraph(branch.if_true));
445 DCHECK(is_true_in_loop && is_false_in_loop);
453 LABEL_BLOCK(no_change) {
return Next::ReduceInputGraphCall(ig_idx, call); }
458 call.IsStackCheck(
__ input_graph(),
broker_,
473 return Next::ReduceInputGraphJSStackCheck(ig_idx, check);
478#if V8_ENABLE_WEBASSEMBLY
480 const WasmStackCheckOp& check) {
482 return Next::ReduceInputGraphWasmStackCheck(ig_idx, check);
506 const Block* backedge_block);
511 std::optional<Block*> output_graph_header = std::nullopt) {
512 if (
__ generating_unreachable_operations()) {
519 if (output_graph_header.has_value()) {
523 __ FinalizeLoop(*output_graph_header);
533 *
__ input_graph().loop_unrolling_analyzer();
544 TRACE(
"LoopUnrolling: partially unrolling loop at " << header->
index().
id());
545 DCHECK_EQ(unrolling_, UnrollingStatus::kNotUnrolling);
546 DCHECK(!skip_next_stack_check_);
547 unrolling_ = UnrollingStatus::kUnrolling;
550 current_loop_header_ = header;
554 TRACE(
"> UnrollCount: " << unroll_count);
563 TRACE(
"> Emitting first iteraton (with header)");
564 Block* output_graph_header =
565 __ CloneSubGraph(loop_body,
true);
566 if (StopUnrollingIfUnreachable(output_graph_header)) {
567 TRACE(
"> Next iteration is unreachable, stopping unrolling");
573 unrolling_ = UnrollingStatus::kUnrolling;
574 for (
size_t i = 0;
i < unroll_count - 1;
i++) {
576 TRACE(
"> Emitting iteration " <<
i);
577 bool is_last_iteration =
i == unroll_count - 2;
581 __ CloneSubGraph(loop_body,
false);
582 if (StopUnrollingIfUnreachable(output_graph_header)) {
583 TRACE(
"> Next iteration is unreachable, stopping unrolling");
592 Block* backedge_block =
__ current_block();
593 __ Goto(output_graph_header);
597 TRACE(
"> Patching loop phis");
598 FixLoopPhis(header, output_graph_header, backedge_block);
600 unrolling_ = UnrollingStatus::kNotUnrolling;
601 TRACE(
"> Finished partially unrolling loop " << header->
index().
id());
606 Block* output_graph_loop,
607 const Block* backedge_block) {
625 __ SealAndSaveVariableSnapshot();
626 __ RestoreTemporaryVariableSnapshotAfter(output_graph_loop);
628 for (
const Operation& op :
__ input_graph().operations(
629 input_graph_loop->
begin(), input_graph_loop->
end())) {
630 if (
auto* input_phi = op.TryCast<
PhiOp>()) {
632 __ template MapToNewGraph<true>(
__ input_graph().Index(*input_phi));
633 if (!phi_index.
valid() || !output_graph_loop->
Contains(phi_index)) {
646 __ CloseTemporaryVariableSnapshot();
647 __ RestoreTemporaryVariableSnapshotAfter(backedge_block);
649 for (
auto [input_phi, output_phi_index] : phis) {
650 __ FixLoopPhi(*input_phi, output_phi_index, output_graph_loop);
653 __ CloseTemporaryVariableSnapshot();
658 TRACE(
"LoopUnrolling: removing loop at " << header->
index().
id());
659 DCHECK_EQ(unrolling_, UnrollingStatus::kNotUnrolling);
660 DCHECK(!skip_next_stack_check_);
665 unrolling_ = UnrollingStatus::kRemoveLoop;
666 __ CloneAndInlineBlock(header);
667 unrolling_ = UnrollingStatus::kNotUnrolling;
672 TRACE(
"LoopUnrolling: fully unrolling loop at " << header->
index().
id());
673 DCHECK_EQ(unrolling_, UnrollingStatus::kNotUnrolling);
674 DCHECK(!skip_next_stack_check_);
678 TRACE(
"> iter_count: " << iter_count);
681 current_loop_header_ = header;
683 unrolling_ = UnrollingStatus::kUnrolling;
684 for (
size_t i = 0;
i < iter_count;
i++) {
685 TRACE(
"> Emitting iteration " <<
i);
686 __ CloneSubGraph(loop_body,
false);
687 if (StopUnrollingIfUnreachable()) {
688 TRACE(
"> Next iteration is unreachable, stopping unrolling");
696 TRACE(
"> Emitting the final header");
697 unrolling_ = UnrollingStatus::kRemoveLoop;
698 __ CloneAndInlineBlock(header);
700 unrolling_ = UnrollingStatus::kNotUnrolling;
701 TRACE(
"> Finished fully unrolling loop " << header->
index().
id());
#define REDUCE_INPUT_GRAPH(operation)
union v8::internal::@341::BuiltinMetadata::KindSpecificData data
const Block * OriginForBlockEnd() const
bool Contains(OpIndex op_idx) const
IterationCount(Kind kind, size_t count)
static IterationCount Approx(size_t count)
size_t exact_count() const
bool IsSmallerThan(size_t max)
static IterationCount Exact(size_t count)
IterationCount(Kind kind)
size_t approx_count() const
static IterationCount Unknown()
V< None > REDUCE_INPUT_GRAPH JSStackCheck(V< None > ig_idx, const JSStackCheckOp &stack_check)
bool skip_next_stack_check_
const ZoneAbslFlatHashSet< uint32_t > & stack_checks_to_remove_
bool remove_stack_checks_
void Bind(Block *new_block)
bool ShouldRemoveLoop(const Block *loop_header) const
ZoneAbslFlatHashSet< uint32_t > & stack_checks_to_remove_
bool ShouldPartiallyUnrollLoop(const Block *loop_header) const
bool CanUnrollAtLeastOneLoop() const
bool ShouldFullyUnrollLoop(const Block *loop_header) const
OperationMatcher matcher_
LoopUnrollingAnalyzer(Zone *phase_zone, Graph *input_graph, bool is_wasm)
IterationCount GetIterationCount(const Block *loop_header) const
const Block * GetLoopHeader(const Block *block)
ZoneSet< const Block *, LoopFinder::BlockCmp > GetLoopBody(const Block *loop_header)
size_t GetPartialUnrollCount(const Block *loop_header) const
const StaticCanonicalForLoopMatcher canonical_loop_matcher_
ZoneUnorderedMap< const Block *, IterationCount > loop_iteration_count_
const Block * current_loop_header_
void RemoveLoop(const Block *header)
bool StopUnrollingIfUnreachable(std::optional< Block * > output_graph_header=std::nullopt)
bool skip_next_stack_check_
void PartiallyUnrollLoop(const Block *header)
LoopUnrollingAnalyzer & analyzer_
V< None > REDUCE_INPUT_GRAPH Branch(V< None > ig_idx, const BranchOp &branch)
V< None > REDUCE_INPUT_GRAPH Goto(V< None > ig_idx, const GotoOp >o)
void FixLoopPhis(const Block *input_graph_loop, Block *output_graph_loop, const Block *backedge_block)
void FullyUnrollLoop(const Block *header)
bool IsRunningBuiltinPipeline()
UnrollingStatus unrolling_
V< None > REDUCE_INPUT_GRAPH JSStackCheck(V< None > ig_idx, const JSStackCheckOp &check)
static constexpr OpIndex Invalid()
constexpr bool valid() const
const OperationMatcher & matcher_
StaticCanonicalForLoopMatcher(const OperationMatcher &matcher)
#define TURBOSHAFT_REDUCER_BOILERPLATE(Name)
#define LABEL_BLOCK(label)
TurboshaftPipelineKind pipeline_kind
std::ostream & operator<<(std::ostream &os, PaddingSpace padding)
any_of(const Args &...) -> any_of< Args... >
V8_EXPORT_PRIVATE bool ShouldSkipOptimizationStep()
@ kSignedGreaterThanOrEqual
@ kUnsignedLessThanOrEqual
@ kUnsignedGreaterThanOrEqual
#define DCHECK_NE(v1, v2)
#define DCHECK(condition)
#define DCHECK_EQ(v1, v2)
#define DCHECK_GT(v1, v2)
#define V8_EXPORT_PRIVATE
#define V8_LIKELY(condition)