v8
V8 is Google’s open source high-performance JavaScript and WebAssembly engine, written in C++.
Loading...
Searching...
No Matches
loop-unrolling-reducer.h
Go to the documentation of this file.
1// Copyright 2023 the V8 project authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#ifndef V8_COMPILER_TURBOSHAFT_LOOP_UNROLLING_REDUCER_H_
6#define V8_COMPILER_TURBOSHAFT_LOOP_UNROLLING_REDUCER_H_
7
8#include <optional>
9
10#include "src/base/logging.h"
19
21
23
24// OVERVIEW:
25//
26// LoopUnrollingReducer fully unrolls small inner loops with a small
27// statically-computable number of iterations, partially unrolls other small
28// inner loops, and remove loops that we detect as always having 0 iterations.
29
30#ifdef DEBUG
31#define TRACE(x) \
32 do { \
33 if (v8_flags.turboshaft_trace_unrolling) StdoutStream() << x << std::endl; \
34 } while (false)
35#else
36#define TRACE(x)
37#endif
38
40 enum class Kind { kExact, kApprox, kUnknown };
41
42 public:
43 // Loops with an exact number of iteration could be unrolled.
44 static IterationCount Exact(size_t count) {
46 }
47 // We can remove stack checks from loops with a small number of iterations.
48 static IterationCount Approx(size_t count) {
50 }
52
60
61 size_t exact_count() const {
63 return count_;
64 }
65 size_t approx_count() const {
67 return count_;
68 }
69
70 bool IsExact() const { return kind_ == Kind::kExact; }
71 bool IsApprox() const { return kind_ == Kind::kApprox; }
72 bool IsUnknown() const { return kind_ == Kind::kUnknown; }
73
74 bool IsSmallerThan(size_t max) {
75 return (IsExact() || IsApprox()) && count_ < max;
76 }
77
78 private:
80 size_t count_;
81};
82std::ostream& operator<<(std::ostream& os, const IterationCount& count);
83
85 // In the context of this class, a "static canonical for-loop" is one of the
86 // form `for (let i = cst; i cmp cst; i = i binop cst)`. That is, a fairly
87 // simple for-loop, for which we can statically compute the number of
88 // iterations.
89 //
90 // There is an added constraint that this class can only match loops with few
91 // iterations (controlled by the `max_iter_` parameter), for performance
92 // reasons (because it's a bit tricky to compute how many iterations a loop
93 // has, see the `HasFewerIterationsThan` method).
94 //
95 // This class and its methods are not in OperationMatcher, even though they
96 // could fit there, because they seemed a bit too loop-unrolling specific.
97 // However, if they can ever be useful for something else, any of the
98 // "MatchXXX" method of this class could be moved to OperationMatcher.
99 public:
101 : matcher_(matcher) {}
102
103 IterationCount GetIterCountIfStaticCanonicalForLoop(
104 const Block* header, OpIndex cond_idx, bool loop_if_cond_is) const;
105
117 static constexpr CmpOp ComparisonKindToCmpOp(ComparisonOp::Kind kind);
118 static constexpr CmpOp InvertComparisonOp(CmpOp op);
119 enum class BinOp {
120 kAdd,
121 kMul,
122 kSub,
123 kBitwiseAnd,
124 kBitwiseOr,
125 kBitwiseXor,
126 kOverflowCheckedAdd,
127 kOverflowCheckedMul,
128 kOverflowCheckedSub
129 };
130 static constexpr BinOp BinopFromWordBinopKind(WordBinopOp::Kind kind);
131 static constexpr BinOp BinopFromOverflowCheckedBinopKind(
133 static constexpr bool BinopKindIsSupported(WordBinopOp::Kind binop_kind);
134
135 private:
136 bool MatchPhiCompareCst(OpIndex cond_idx,
138 OpIndex* phi, uint64_t* cst) const;
139 bool MatchCheckedOverflowBinop(OpIndex idx, V<Word>* left, V<Word>* right,
140 BinOp* binop_op,
141 WordRepresentation* binop_rep) const;
142 bool MatchWordBinop(OpIndex idx, V<Word>* left, V<Word>* right,
143 BinOp* binop_op, WordRepresentation* binop_rep) const;
144 IterationCount CountIterations(uint64_t equal_cst, CmpOp cmp_op,
145 uint64_t initial_input, uint64_t binop_cst,
146 BinOp binop_op, WordRepresentation binop_rep,
147 bool loop_if_cond_is) const;
148 template <class Int>
149 IterationCount CountIterationsImpl(
150 Int init, Int max, CmpOp cmp_op, Int binop_cst,
152 WordRepresentation binop_rep, bool loop_if_cond_is) const;
153
155
156 // When trying to compute the number of iterations of a loop, we simulate the
157 // first {kMaxExactIter} iterations of the loop, and check if the loop ends
158 // during these first few iterations. This is slightly inneficient, hence the
159 // small value for {kMaxExactIter}, but it's simpler than using a formula to
160 // compute the number of iterations (in particular because of overflows).
161 static constexpr size_t kMaxExactIter = 5;
162};
163std::ostream& operator<<(std::ostream& os,
165std::ostream& operator<<(std::ostream& os,
167
169 // LoopUnrollingAnalyzer analyzes the loops of the graph, and in particular
170 // tries to figure out if some inner loops have a fixed (and known) number of
171 // iterations. In particular, it tries to pattern match loops like
172 //
173 // for (let i = 0; i < 4; i++) { ... }
174 //
175 // where `i++` could alternatively be pretty much any WordBinopOp or
176 // OverflowCheckedBinopOp, and `i < 4` could be any ComparisonOp.
177 // Such loops, if small enough, could be fully unrolled.
178 //
179 // Loops that don't have statically-known bounds could still be partially
180 // unrolled if they are small enough.
181 public:
182 LoopUnrollingAnalyzer(Zone* phase_zone, Graph* input_graph, bool is_wasm)
183 : input_graph_(input_graph),
184 matcher_(*input_graph),
185 loop_finder_(phase_zone, input_graph),
186 loop_iteration_count_(phase_zone),
187 canonical_loop_matcher_(matcher_),
188 is_wasm_(is_wasm),
189 stack_checks_to_remove_(input_graph->stack_checks_to_remove()) {
190 DetectUnrollableLoops();
191 }
192
193 bool ShouldFullyUnrollLoop(const Block* loop_header) const {
194 DCHECK(loop_header->IsLoop());
195
196 LoopFinder::LoopInfo header_info = loop_finder_.GetLoopInfo(loop_header);
197 if (header_info.has_inner_loops) return false;
198 if (header_info.op_count > kMaxLoopSizeForFullUnrolling) return false;
199
200 auto iter_count = GetIterationCount(loop_header);
201 return iter_count.IsExact() &&
202 iter_count.exact_count() < kMaxLoopIterationsForFullUnrolling;
203 }
204
205 bool ShouldPartiallyUnrollLoop(const Block* loop_header) const {
206 DCHECK(loop_header->IsLoop());
207 LoopFinder::LoopInfo info = loop_finder_.GetLoopInfo(loop_header);
208 return !info.has_inner_loops &&
209 info.op_count < kMaxLoopSizeForPartialUnrolling;
210 }
211
212 // The returned unroll count is the total number of copies of the loop body
213 // in the resulting graph, i.e., an unroll count of N means N-1 copies of the
214 // body which were partially unrolled, and 1 for the original/remaining body.
215 size_t GetPartialUnrollCount(const Block* loop_header) const {
216 // Don't unroll if the function is already huge.
217 // Otherwise we have run into pathological runtimes or large memory usage,
218 // e.g., in register allocation in the past, see https://crbug.com/383661627
219 // for an example / reproducer.
220 // Even though we return an unroll count of one (i.e., don't unroll at all
221 // really), running this phase can speed up subsequent optimizations,
222 // probably because it produces loops in a "compact"/good block order for
223 // analyses, namely <loop header>, <loop body>, <loop exit>, <rest of code>.
224 // In principle, we should fix complexity problems in analyses, make sure
225 // loops are already produced in this order, and not rely on the "unrolling"
226 // here for the order alone, but this is a longer standing issue.
227 if (input_graph_->op_id_count() > kMaxFunctionSizeForPartialUnrolling) {
228 return 1;
229 }
230 if (is_wasm_) {
231 LoopFinder::LoopInfo info = loop_finder_.GetLoopInfo(loop_header);
232 return std::min(
233 LoopUnrollingAnalyzer::kMaxPartialUnrollingCount,
234 LoopUnrollingAnalyzer::kWasmMaxUnrolledLoopSize / info.op_count);
235 }
236 return LoopUnrollingAnalyzer::kMaxPartialUnrollingCount;
237 }
238
239 bool ShouldRemoveLoop(const Block* loop_header) const {
240 auto iter_count = GetIterationCount(loop_header);
241 return iter_count.IsExact() && iter_count.exact_count() == 0;
242 }
243
244 IterationCount GetIterationCount(const Block* loop_header) const {
245 DCHECK(loop_header->IsLoop());
246 auto it = loop_iteration_count_.find(loop_header);
247 if (it == loop_iteration_count_.end()) return IterationCount::Unknown();
248 return it->second;
249 }
250
252 const Block* loop_header) {
253 return loop_finder_.GetLoopBody(loop_header);
254 }
255
256 const Block* GetLoopHeader(const Block* block) {
257 return loop_finder_.GetLoopHeader(block);
258 }
259
260 bool CanUnrollAtLeastOneLoop() const { return can_unroll_at_least_one_loop_; }
261
262 // TODO(dmercadier): consider tweaking these value for a better size-speed
263 // trade-off. In particular, having the number of iterations to unroll be a
264 // function of the loop's size and a MaxLoopSize could make sense.
265 static constexpr size_t kMaxLoopSizeForFullUnrolling = 150;
266 // This function size limit is quite arbitrary. It is large enough that we
267 // probably never hit it in JavaScript and it is lower than the operation
268 // count we have seen in some huge Wasm functions in the past, e.g., function
269 // #21937 of https://crbug.com/383661627 (1.7M operations, 2.7MB wire bytes).
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;
277
278 private:
279 void DetectUnrollableLoops();
280 IterationCount GetLoopIterationCount(const LoopFinder::LoopInfo& info) const;
281
285 // {loop_iteration_count_} maps loop headers to number of iterations. It
286 // doesn't contain entries for loops for which we don't know the number of
287 // iterations.
290 const bool is_wasm_;
291 const size_t kMaxLoopSizeForPartialUnrolling =
292 is_wasm_ ? kWasmMaxLoopSizeForPartialUnrolling
293 : kJSMaxLoopSizeForPartialUnrolling;
294 bool can_unroll_at_least_one_loop_ = false;
295
297};
298
299template <class Next>
301
302template <class Next>
303class LoopStackCheckElisionReducer : public Next {
304 public:
305 TURBOSHAFT_REDUCER_BOILERPLATE(LoopStackCheckElision)
306
307 void Bind(Block* new_block) {
308 Next::Bind(new_block);
309 if (!remove_stack_checks_) return;
310
311 if (new_block->IsLoop()) {
312 const Block* origin = new_block->OriginForBlockEnd();
313 if (origin) {
314 if (stack_checks_to_remove_.contains(origin->index().id())) {
316 }
317 }
318 }
319 }
320
322 const CallOp& call) {
323 LABEL_BLOCK(no_change) { return Next::ReduceInputGraphCall(ig_idx, call); }
324 if (ShouldSkipOptimizationStep()) goto no_change;
325
327 call.IsStackCheck(__ input_graph(), broker_,
330 return {};
331 }
332
333 goto no_change;
334 }
335
337 const JSStackCheckOp& stack_check) {
339 stack_check.kind == JSStackCheckOp::Kind::kLoop) {
341 return {};
342 }
343 return Next::ReduceInputGraphJSStackCheck(ig_idx, stack_check);
344 }
345
346#if V8_ENABLE_WEBASSEMBLY
347 V<None> REDUCE_INPUT_GRAPH(WasmStackCheck)(
348 V<None> ig_idx, const WasmStackCheckOp& stack_check) {
350 stack_check.kind == WasmStackCheckOp::Kind::kLoop) {
352 return {};
353 }
354 return Next::ReduceInputGraphWasmStackCheck(ig_idx, stack_check);
355 }
356#endif
357
358 private:
360
361 // The analysis should have ran before the CopyingPhase starts, and stored in
362 // `PipelineData::Get().stack_checks_to_remove()` the loops whose stack checks
363 // should be removed.
365 __ input_graph().stack_checks_to_remove();
367
369};
370
371template <class Next>
372class LoopUnrollingReducer : public Next {
373 public:
374 TURBOSHAFT_REDUCER_BOILERPLATE(LoopUnrolling)
375
376#if defined(__clang__)
377 // LoopUnrolling and LoopPeeling shouldn't be performed in the same phase, see
378 // the comment in pipeline.cc where LoopUnrolling is triggered.
380
381 // TODO(dmercadier): Add static_assert that this is ran as part of a
382 // CopyingPhase.
383#endif
384
386 // Note that the "ShouldSkipOptimizationStep" are placed in the parts of
387 // this Reduce method triggering the unrolling rather than at the begining.
388 // This is because the backedge skipping is not an optimization but a
389 // mandatory lowering when unrolling is being performed.
390 LABEL_BLOCK(no_change) { return Next::ReduceInputGraphGoto(ig_idx, gto); }
391
392 const Block* dst = gto.destination;
394 !gto.is_backedge) {
395 // We trigger unrolling when reaching the GotoOp that jumps to the loop
396 // header (note that loop headers only have 2 predecessor, including the
397 // backedge), and that isn't the backedge.
398 if (ShouldSkipOptimizationStep()) goto no_change;
399 if (analyzer_.ShouldRemoveLoop(dst)) {
400 RemoveLoop(dst);
401 return {};
402 } else if (analyzer_.ShouldFullyUnrollLoop(dst)) {
403 FullyUnrollLoop(dst);
404 return {};
405 } else if (analyzer_.ShouldPartiallyUnrollLoop(dst)) {
407 return {};
408 }
409 } else if ((unrolling_ == UnrollingStatus::kUnrolling) &&
410 dst == current_loop_header_) {
411 // Skipping the backedge of the loop: FullyUnrollLoop and
412 // PartiallyUnrollLoop will emit a Goto to the next unrolled iteration.
413 return {};
414 }
415 goto no_change;
416 }
417
419 LABEL_BLOCK(no_change) {
420 return Next::ReduceInputGraphBranch(ig_idx, branch);
421 }
422
424 // We know that the branch of the final inlined header of a fully unrolled
425 // loop never actually goes to the loop, so we can replace it by a Goto
426 // (so that the non-unrolled loop doesn't get emitted). We still need to
427 // figure out if we should Goto to the true or false side of the BranchOp.
428 const Block* header = __ current_block()->OriginForBlockEnd();
429 bool is_true_in_loop = analyzer_.GetLoopHeader(branch.if_true) == header;
430 bool is_false_in_loop =
431 analyzer_.GetLoopHeader(branch.if_false) == header;
432
433 if (is_true_in_loop && !is_false_in_loop) {
434 __ Goto(__ MapToNewGraph(branch.if_false));
435 return OpIndex::Invalid();
436 } else if (is_false_in_loop && !is_true_in_loop) {
437 __ Goto(__ MapToNewGraph(branch.if_true));
438 return OpIndex::Invalid();
439 } else {
440 // Both the true and false destinations of this block are in the loop,
441 // which means that the exit of the loop is later down the graph. We
442 // thus still emit the branch, which will lead to the loop being emitted
443 // (unless some other reducers in the stack manage to get rid of the
444 // loop).
445 DCHECK(is_true_in_loop && is_false_in_loop);
446 }
447 }
448 goto no_change;
449 }
450
452 const CallOp& call) {
453 LABEL_BLOCK(no_change) { return Next::ReduceInputGraphCall(ig_idx, call); }
454 if (ShouldSkipOptimizationStep()) goto no_change;
455
458 call.IsStackCheck(__ input_graph(), broker_,
460 // When we unroll a loop, we get rid of its stack checks. (note that
461 // we don't do this for the last folded body of partially unrolled
462 // loops so that the loop keeps one stack check).
463 return {};
464 }
465 }
466
467 goto no_change;
468 }
469
471 const JSStackCheckOp& check) {
473 return Next::ReduceInputGraphJSStackCheck(ig_idx, check);
474 }
475 return V<None>::Invalid();
476 }
477
478#if V8_ENABLE_WEBASSEMBLY
479 V<None> REDUCE_INPUT_GRAPH(WasmStackCheck)(V<None> ig_idx,
480 const WasmStackCheckOp& check) {
482 return Next::ReduceInputGraphWasmStackCheck(ig_idx, check);
483 }
484 return V<None>::Invalid();
485 }
486#endif
487
488 private:
489 enum class UnrollingStatus {
490 // Not currently unrolling a loop.
492 // Currently unrolling a loop.
494 // We use kRemoveLoop in 2 cases:
495 // - When unrolling is finished and we are currently emitting the header
496 // one last time, and should change its final branch into a Goto.
497 // - We decided to remove a loop and will just emit its header.
498 // Both cases are fairly similar: we are currently emitting a loop header,
499 // and would like to not emit the loop body that follows.
501 };
502 void RemoveLoop(const Block* header);
503 void FullyUnrollLoop(const Block* header);
504 void PartiallyUnrollLoop(const Block* header);
505 void FixLoopPhis(const Block* input_graph_loop, Block* output_graph_loop,
506 const Block* backedge_block);
511 std::optional<Block*> output_graph_header = std::nullopt) {
512 if (__ generating_unreachable_operations()) {
513 // By unrolling the loop, we realized that it was actually exiting early
514 // (probably because a Branch inside the loop was using a loop Phi in a
515 // condition, and unrolling showed that this loop Phi became true or
516 // false), and that lasts iterations were unreachable. We thus don't both
517 // unrolling the next iterations of the loop.
519 if (output_graph_header.has_value()) {
520 // The loop that we're unrolling has a header (which means that we're
521 // only partially unrolling), which needs to be turned into a Merge (and
522 // its PendingLoopPhis into regular Phis).
523 __ FinalizeLoop(*output_graph_header);
524 }
525 return true;
526 }
527 return false;
528 }
529
530 // The analysis should be ran ahead of time so that the LoopUnrollingPhase
531 // doesn't trigger the CopyingPhase if there are no loops to unroll.
533 *__ input_graph().loop_unrolling_analyzer();
534 // {unrolling_} is true if a loop is currently being unrolled.
537
538 const Block* current_loop_header_ = nullptr;
540};
541
542template <class Next>
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;
548
549 auto loop_body = analyzer_.GetLoopBody(header);
550 current_loop_header_ = header;
551
552 size_t unroll_count = analyzer_.GetPartialUnrollCount(header);
553 DCHECK_GT(unroll_count, 0);
554 TRACE("> UnrollCount: " << unroll_count);
555
556 ScopedModification<bool> set_true(__ turn_loop_without_backedge_into_merge(),
557 false);
558
559 // We remove the stack check of all iterations but the last one.
560 // Emitting the 1st iteration of the loop (with a proper loop header). We
561 // remove the stack check of all iterations except the last one.
562 ScopedModification<bool> skip_stack_checks(&skip_next_stack_check_, true);
563 TRACE("> Emitting first iteraton (with header)");
564 Block* output_graph_header =
565 __ CloneSubGraph(loop_body, /* keep_loop_kinds */ true);
566 if (StopUnrollingIfUnreachable(output_graph_header)) {
567 TRACE("> Next iteration is unreachable, stopping unrolling");
568 return;
569 }
570
571 // Emitting the subsequent folded iterations. We set `unrolling_` to
572 // kUnrolling so that stack checks are skipped.
573 unrolling_ = UnrollingStatus::kUnrolling;
574 for (size_t i = 0; i < unroll_count - 1; i++) {
575 // We remove the stack check of all iterations but the last one.
576 TRACE("> Emitting iteration " << i);
577 bool is_last_iteration = i == unroll_count - 2;
578 ScopedModification<bool> inner_skip_stack_checks(&skip_next_stack_check_,
579 !is_last_iteration);
580
581 __ CloneSubGraph(loop_body, /* keep_loop_kinds */ false);
582 if (StopUnrollingIfUnreachable(output_graph_header)) {
583 TRACE("> Next iteration is unreachable, stopping unrolling");
584 return;
585 }
586 }
587
588 // ReduceInputGraphGoto ignores backedge Gotos while kUnrolling is true, which
589 // means that we are still missing the loop's backedge, which we thus emit
590 // now.
591 DCHECK(output_graph_header->IsLoop());
592 Block* backedge_block = __ current_block();
593 __ Goto(output_graph_header);
594 // We use a custom `FixLoopPhis` because the mapping from old->new is a bit
595 // "messed up" by having emitted multiple times the same block. See the
596 // comments in `FixLoopPhis` for more details.
597 TRACE("> Patching loop phis");
598 FixLoopPhis(header, output_graph_header, backedge_block);
599
600 unrolling_ = UnrollingStatus::kNotUnrolling;
601 TRACE("> Finished partially unrolling loop " << header->index().id());
602}
603
604template <class Next>
606 Block* output_graph_loop,
607 const Block* backedge_block) {
608 // FixLoopPhis for partially unrolled loops is a bit tricky: the mapping from
609 // input Loop Phis to output Loop Phis is in the Variable Snapshot of the
610 // header (`output_graph_loop`), but the mapping from the 2nd input of the
611 // input graph loop phis to the 2nd input of the output graph loop phis is in
612 // the snapshot of the backedge (`backedge_block`).
613 // VariableReducer::ReduceGotoOp (which was called right before this function
614 // because we emitted the backedge Goto) already set the current snapshot to
615 // be at the loop header. So, we start by computing the mapping input loop
616 // phis -> output loop phis (using the loop header's snapshot). Then, we
617 // restore the backedge snapshot to compute the mapping input graph 2nd phi
618 // input to output graph 2nd phi input.
619 DCHECK(input_graph_loop->IsLoop());
620 DCHECK(output_graph_loop->IsLoop());
621
622 // The mapping InputGraphPhi -> OutputGraphPendingPhi should be retrieved from
623 // `output_graph_loop`'s snapshot (the current mapping is for the latest
624 // folded loop iteration, not for the loop header).
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>()) {
631 OpIndex phi_index =
632 __ template MapToNewGraph<true>(__ input_graph().Index(*input_phi));
633 if (!phi_index.valid() || !output_graph_loop->Contains(phi_index)) {
634 // Unused phis are skipped, so they are not be mapped to anything in
635 // the new graph. If the phi is reduced to an operation from a
636 // different block, then there is no loop phi in the current loop
637 // header to take care of.
638 continue;
639 }
640 phis.push_back({input_phi, phi_index});
641 }
642 }
643
644 // The mapping for the InputGraphPhi 2nd input should however be retrieved
645 // from the last block of the loop.
646 __ CloseTemporaryVariableSnapshot();
647 __ RestoreTemporaryVariableSnapshotAfter(backedge_block);
648
649 for (auto [input_phi, output_phi_index] : phis) {
650 __ FixLoopPhi(*input_phi, output_phi_index, output_graph_loop);
651 }
652
653 __ CloseTemporaryVariableSnapshot();
654}
655
656template <class Next>
658 TRACE("LoopUnrolling: removing loop at " << header->index().id());
659 DCHECK_EQ(unrolling_, UnrollingStatus::kNotUnrolling);
660 DCHECK(!skip_next_stack_check_);
661 // When removing a loop, we still need to emit the header (since it has to
662 // always be executed before the 1st iteration anyways), but by setting
663 // {unrolling_} to `kRemoveLoop`, the final Branch of the loop will become a
664 // Goto to outside the loop.
665 unrolling_ = UnrollingStatus::kRemoveLoop;
666 __ CloneAndInlineBlock(header);
667 unrolling_ = UnrollingStatus::kNotUnrolling;
668}
669
670template <class Next>
672 TRACE("LoopUnrolling: fully unrolling loop at " << header->index().id());
673 DCHECK_EQ(unrolling_, UnrollingStatus::kNotUnrolling);
674 DCHECK(!skip_next_stack_check_);
675 ScopedModification<bool> skip_stack_checks(&skip_next_stack_check_, true);
676
677 size_t iter_count = analyzer_.GetIterationCount(header).exact_count();
678 TRACE("> iter_count: " << iter_count);
679
680 auto loop_body = analyzer_.GetLoopBody(header);
681 current_loop_header_ = header;
682
683 unrolling_ = UnrollingStatus::kUnrolling;
684 for (size_t i = 0; i < iter_count; i++) {
685 TRACE("> Emitting iteration " << i);
686 __ CloneSubGraph(loop_body, /* keep_loop_kinds */ false);
687 if (StopUnrollingIfUnreachable()) {
688 TRACE("> Next iteration is unreachable, stopping unrolling");
689 return;
690 }
691 }
692
693 // The loop actually finishes on the header rather than its last block. We
694 // thus inline the header, and we'll replace its final BranchOp by a GotoOp to
695 // outside of the loop.
696 TRACE("> Emitting the final header");
697 unrolling_ = UnrollingStatus::kRemoveLoop;
698 __ CloneAndInlineBlock(header);
699
700 unrolling_ = UnrollingStatus::kNotUnrolling;
701 TRACE("> Finished fully unrolling loop " << header->index().id());
702}
703
704#undef TRACE
705
707
708} // namespace v8::internal::compiler::turboshaft
709
710#endif // V8_COMPILER_TURBOSHAFT_LOOP_UNROLLING_REDUCER_H_
#define REDUCE_INPUT_GRAPH(operation)
union v8::internal::@341::BuiltinMetadata::KindSpecificData data
Builtins::Kind kind
Definition builtins.cc:40
const Block * OriginForBlockEnd() const
Definition graph.h:428
bool Contains(OpIndex op_idx) const
Definition graph.h:322
V< None > REDUCE_INPUT_GRAPH JSStackCheck(V< None > ig_idx, const JSStackCheckOp &stack_check)
LoopUnrollingAnalyzer(Zone *phase_zone, Graph *input_graph, bool is_wasm)
IterationCount GetIterationCount(const Block *loop_header) const
ZoneSet< const Block *, LoopFinder::BlockCmp > GetLoopBody(const Block *loop_header)
ZoneUnorderedMap< const Block *, IterationCount > loop_iteration_count_
bool StopUnrollingIfUnreachable(std::optional< Block * > output_graph_header=std::nullopt)
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 &gto)
void FixLoopPhis(const Block *input_graph_loop, Block *output_graph_loop, const Block *backedge_block)
V< None > REDUCE_INPUT_GRAPH JSStackCheck(V< None > ig_idx, const JSStackCheckOp &check)
static constexpr OpIndex Invalid()
Definition index.h:88
#define TURBOSHAFT_REDUCER_BOILERPLATE(Name)
Definition assembler.h:823
#define LABEL_BLOCK(label)
Definition assembler.h:910
TurboshaftPipelineKind pipeline_kind
JSHeapBroker * broker
#define TRACE(...)
std::ostream & operator<<(std::ostream &os, PaddingSpace padding)
any_of(const Args &...) -> any_of< Args... >
V8_EXPORT_PRIVATE bool ShouldSkipOptimizationStep()
Definition utils.h:84
#define DCHECK_NE(v1, v2)
Definition logging.h:486
#define DCHECK(condition)
Definition logging.h:482
#define DCHECK_EQ(v1, v2)
Definition logging.h:485
#define DCHECK_GT(v1, v2)
Definition logging.h:487
#define V8_EXPORT_PRIVATE
Definition macros.h:460
#define V8_LIKELY(condition)
Definition v8config.h:661