v8
V8 is Google’s open source high-performance JavaScript and WebAssembly engine, written in C++.
Loading...
Searching...
No Matches
loop-unrolling-reducer.cc
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
6
7#include <optional>
8
9#include "src/base/bits.h"
12
13#ifdef DEBUG
14#define TRACE(x) \
15 do { \
16 if (v8_flags.turboshaft_trace_unrolling) StdoutStream() << x << std::endl; \
17 } while (false)
18#else
19#define TRACE(x)
20#endif
21
23
26
28 for (const auto& [start, info] : loop_finder_.LoopHeaders()) {
29 IterationCount iter_count = GetLoopIterationCount(info);
30 TRACE("LoopUnrollingAnalyzer: loop at "
31 << start->index() << " ==> iter_count=" << iter_count);
32 loop_iteration_count_.insert({start, iter_count});
33
36 }
37
39 stack_checks_to_remove_.insert(start->index().id());
40 }
41 }
42}
43
45 const LoopFinder::LoopInfo& info) const {
46 const Block* start = info.start;
47 DCHECK(start->IsLoop());
48
49 // Checking that the condition for the loop can be computed statically, and
50 // that the loop contains no more than kMaxLoopIterationsForFullUnrolling
51 // iterations.
52 const BranchOp* branch =
53 start->LastOperation(*input_graph_).TryCast<BranchOp>();
54 if (!branch) {
55 // This looks like an infinite loop, or like something weird is used to
56 // decide whether to loop or not.
57 return {};
58 }
59
60 // Checking that one of the successor of the loop header is indeed not in the
61 // loop (otherwise, the Branch that ends the loop header is not the Branch
62 // that decides to exit the loop).
63 const Block* if_true_header = loop_finder_.GetLoopHeader(branch->if_true);
64 const Block* if_false_header = loop_finder_.GetLoopHeader(branch->if_false);
65 if (if_true_header == if_false_header) {
66 return {};
67 }
68
69 // If {if_true} is in the loop, then we're looping if the condition is true,
70 // but if {if_false} is in the loop, then we're looping if the condition is
71 // false.
72 bool loop_if_cond_is = if_true_header == start;
73
74 return canonical_loop_matcher_.GetIterCountIfStaticCanonicalForLoop(
75 start, branch->condition(), loop_if_cond_is);
76}
77
78// Tries to match `phi cmp cst` (or `cst cmp phi`).
81 OpIndex* phi, uint64_t* cst) const {
82 const Operation& cond = matcher_.Get(cond_idx);
83
84 if (const ComparisonOp* cmp = cond.TryCast<ComparisonOp>()) {
85 *cmp_op = ComparisonKindToCmpOp(cmp->kind);
86 } else {
87 return false;
88 }
89
90 OpIndex left = cond.input(0);
91 OpIndex right = cond.input(1);
92
93 if (matcher_.MatchPhi(left, 2)) {
94 if (matcher_.MatchUnsignedIntegralConstant(right, cst)) {
95 *phi = left;
96 return true;
97 }
98 } else if (matcher_.MatchPhi(right, 2)) {
99 if (matcher_.MatchUnsignedIntegralConstant(left, cst)) {
100 *cmp_op = InvertComparisonOp(*cmp_op);
101 *phi = right;
102 return true;
103 }
104 }
105 return false;
106}
107
109 OpIndex idx, V<Word>* left, V<Word>* right, BinOp* binop_op,
110 WordRepresentation* binop_rep) const {
111 if (const ProjectionOp* proj = matcher_.TryCast<ProjectionOp>(idx)) {
112 if (proj->index != OverflowCheckedBinopOp::kValueIndex) return false;
113 if (const OverflowCheckedBinopOp* binop =
114 matcher_.TryCast<OverflowCheckedBinopOp>(proj->input())) {
115 *left = binop->left();
116 *right = binop->right();
117 *binop_op = BinopFromOverflowCheckedBinopKind(binop->kind);
118 *binop_rep = binop->rep;
119 return true;
120 }
121 }
122 return false;
123}
124
126 OpIndex idx, V<Word>* left, V<Word>* right, BinOp* binop_op,
127 WordRepresentation* binop_rep) const {
129 if (matcher_.MatchWordBinop<Word>(idx, left, right, &kind, binop_rep) &&
131 *binop_op = BinopFromWordBinopKind(kind);
132 return true;
133 }
134 return false;
135}
136
139 const Block* header, OpIndex cond_idx, bool loop_if_cond_is) const {
140 CmpOp cmp_op;
141 OpIndex phi_idx;
142 uint64_t cmp_cst;
143 if (!MatchPhiCompareCst(cond_idx, &cmp_op, &phi_idx, &cmp_cst)) {
144 return {};
145 }
146 if (!header->Contains(phi_idx)) {
147 // The termination condition for this loop is based on a Phi that is defined
148 // in another loop.
149 return {};
150 }
151
152 const PhiOp& phi = matcher_.Cast<PhiOp>(phi_idx);
153
154 // We have: phi(..., ...) cmp_op cmp_cst
155 // eg, for (i = ...; i < 42; ...)
156 uint64_t phi_cst;
157 if (matcher_.MatchUnsignedIntegralConstant(phi.input(0), &phi_cst)) {
158 // We have: phi(phi_cst, ...) cmp_op cmp_cst
159 // eg, for (i = 0; i < 42; ...)
160 V<Word> left, right;
161 BinOp binop_op;
162 WordRepresentation binop_rep;
163 if (MatchWordBinop(phi.input(1), &left, &right, &binop_op, &binop_rep) ||
164 MatchCheckedOverflowBinop(phi.input(1), &left, &right, &binop_op,
165 &binop_rep)) {
166 // We have: phi(phi_cst, ... binop_op ...) cmp_op cmp_cst
167 // eg, for (i = 0; i < 42; i = ... + ...)
168 if (left == phi_idx) {
169 // We have: phi(phi_cst, phi binop_op ...) cmp_op cmp_cst
170 // eg, for (i = 0; i < 42; i = i + ...)
171 uint64_t binop_cst;
172 if (matcher_.MatchUnsignedIntegralConstant(right, &binop_cst)) {
173 // We have: phi(phi_cst, phi binop_op binop_cst) cmp_op cmp_cst
174 // eg, for (i = 0; i < 42; i = i + 2)
175 return CountIterations(cmp_cst, cmp_op, phi_cst, binop_cst, binop_op,
176 binop_rep, loop_if_cond_is);
177 }
178 } else if (right == phi_idx) {
179 // We have: phi(phi_cst, ... binop_op phi) cmp_op cmp_cst
180 // eg, for (i = 0; i < 42; i = ... + i)
181 uint64_t binop_cst;
182 if (matcher_.MatchUnsignedIntegralConstant(left, &binop_cst)) {
183 // We have: phi(phi_cst, binop_cst binop_op phi) cmp_op cmp_cst
184 // eg, for (i = 0; i < 42; i = 2 + i)
185 return CountIterations(cmp_cst, cmp_op, phi_cst, binop_cst, binop_op,
186 binop_rep, loop_if_cond_is);
187 }
188 }
189 }
190 }
191
192 // The condition is not an operation that we support.
193 return {};
194}
195
197 WordBinopOp::Kind binop_kind) {
198 switch (binop_kind) {
199 // This list needs to be kept in sync with the `Next` function that follows.
206 return true;
207 default:
208 return false;
209 }
210}
211
215 switch (kind) {
217 return BinOp::kAdd;
219 return BinOp::kMul;
221 return BinOp::kSub;
223 return BinOp::kBitwiseAnd;
225 return BinOp::kBitwiseOr;
227 return BinOp::kBitwiseXor;
228 default:
229 UNREACHABLE();
230 }
231}
232
236 switch (kind) {
238 return BinOp::kOverflowCheckedAdd;
240 return BinOp::kOverflowCheckedMul;
242 return BinOp::kOverflowCheckedSub;
243 }
244}
245
246std::ostream& operator<<(std::ostream& os, const IterationCount& count) {
247 if (count.IsExact()) {
248 return os << "Exact[" << count.exact_count() << "]";
249 } else if (count.IsApprox()) {
250 return os << "Approx[" << count.approx_count() << "]";
251 } else {
252 DCHECK(count.IsUnknown());
253 return os << "Unknown";
254 }
255}
256
257std::ostream& operator<<(std::ostream& os, const CmpOp& cmp) {
258 switch (cmp) {
259 case CmpOp::kEqual:
260 return os << "==";
261 case CmpOp::kSignedLessThan:
262 return os << "<ˢ";
263 case CmpOp::kSignedLessThanOrEqual:
264 return os << "<=ˢ";
265 case CmpOp::kUnsignedLessThan:
266 return os << "<ᵘ";
267 case CmpOp::kUnsignedLessThanOrEqual:
268 return os << "<=ᵘ";
269 case CmpOp::kSignedGreaterThan:
270 return os << ">ˢ";
271 case CmpOp::kSignedGreaterThanOrEqual:
272 return os << ">=ˢ";
273 case CmpOp::kUnsignedGreaterThan:
274 return os << ">ᵘ";
275 case CmpOp::kUnsignedGreaterThanOrEqual:
276 return os << ">=ᵘ";
277 }
278}
279
280std::ostream& operator<<(std::ostream& os, const BinOp& binop) {
281 switch (binop) {
282 case BinOp::kAdd:
283 return os << "+";
284 case BinOp::kMul:
285 return os << "*";
286 case BinOp::kSub:
287 return os << "-";
288 case BinOp::kBitwiseAnd:
289 return os << "&";
290 case BinOp::kBitwiseOr:
291 return os << "|";
292 case BinOp::kBitwiseXor:
293 return os << "^";
294 case BinOp::kOverflowCheckedAdd:
295 return os << "+ᵒ";
296 case BinOp::kOverflowCheckedMul:
297 return os << "*ᵒ";
298 case BinOp::kOverflowCheckedSub:
299 return os << "-ᵒ";
300 }
301}
302
303namespace {
304
305template <class Int>
306std::optional<Int> Next(Int val, Int incr,
308 WordRepresentation binop_rep) {
309 switch (binop_op) {
310 case BinOp::kBitwiseAnd:
311 return val & incr;
312 case BinOp::kBitwiseOr:
313 return val | incr;
314 case BinOp::kBitwiseXor:
315 return val ^ incr;
316 // Even regular Add/Sub/Mul probably shouldn't under/overflow here, so we
317 // check for overflow in all cases (and C++ signed integer overflow is
318 // undefined behavior, so have to use something from base::bits anyways).
319#define CASE_ARITH(op) \
320 case BinOp::k##op: \
321 case BinOp::kOverflowChecked##op: { \
322 if (binop_rep == WordRepresentation::Word32()) { \
323 int32_t res; \
324 if (base::bits::Signed##op##Overflow32( \
325 static_cast<int32_t>(val), static_cast<int32_t>(incr), &res)) { \
326 return std::nullopt; \
327 } \
328 return static_cast<Int>(res); \
329 } else { \
330 DCHECK_EQ(binop_rep, WordRepresentation::Word64()); \
331 int64_t res; \
332 if (base::bits::Signed##op##Overflow64(val, incr, &res)) { \
333 return std::nullopt; \
334 } \
335 return static_cast<Int>(res); \
336 } \
337 }
338 CASE_ARITH(Add)
339 CASE_ARITH(Mul)
340 CASE_ARITH(Sub)
341#undef CASE_CHECKED
342 }
343}
344
345template <class Int>
346bool Cmp(Int val, Int max, CmpOp cmp_op) {
347 switch (cmp_op) {
348 case CmpOp::kSignedLessThan:
349 case CmpOp::kUnsignedLessThan:
350 return val < max;
351 case CmpOp::kSignedLessThanOrEqual:
352 case CmpOp::kUnsignedLessThanOrEqual:
353 return val <= max;
354 case CmpOp::kSignedGreaterThan:
355 case CmpOp::kUnsignedGreaterThan:
356 return val > max;
357 case CmpOp::kSignedGreaterThanOrEqual:
358 case CmpOp::kUnsignedGreaterThanOrEqual:
359 return val >= max;
360 case CmpOp::kEqual:
361 return val == max;
362 }
363}
364
365template <class Int>
366bool SubWillOverflow(Int lhs, Int rhs) {
367 if constexpr (std::is_same_v<Int, int32_t> || std::is_same_v<Int, uint32_t>) {
368 int32_t unused;
369 return base::bits::SignedSubOverflow32(lhs, rhs, &unused);
370 } else {
371 static_assert(std::is_same_v<Int, int64_t> ||
372 std::is_same_v<Int, uint64_t>);
373 int64_t unused;
374 return base::bits::SignedSubOverflow64(lhs, rhs, &unused);
375 }
376}
377
378template <class Int>
379bool DivWillOverflow(Int dividend, Int divisor) {
380 if constexpr (std::is_unsigned_v<Int>) {
381 return false;
382 } else {
383 return dividend == std::numeric_limits<Int>::min() && divisor == -1;
384 }
385}
386
387} // namespace
388
389// Returns true if the loop
390// `for (i = init, i cmp_op max; i = i binop_op binop_cst)` has fewer than
391// `max_iter_` iterations.
392template <class Int>
394 Int init, Int max, CmpOp cmp_op, Int binop_cst, BinOp binop_op,
395 WordRepresentation binop_rep, bool loop_if_cond_is) const {
396 static_assert(std::is_integral_v<Int>);
397 DCHECK_EQ(std::is_unsigned_v<Int>,
398 (cmp_op == CmpOp::kUnsignedLessThan ||
399 cmp_op == CmpOp::kUnsignedLessThanOrEqual ||
400 cmp_op == CmpOp::kUnsignedGreaterThan ||
401 cmp_op == CmpOp::kUnsignedGreaterThanOrEqual));
402
403 // It's a bit hard to compute the number of iterations without some kind of
404 // (simple) SMT solver, especially when taking overflows into account. Thus,
405 // we just simulate the evolution of the loop counter: we repeatedly compute
406 // `init binop_op binop_cst`, and compare the result with `max`. This is
407 // somewhat inefficient, so it should only be done if `kMaxExactIter` is
408 // small.
410
411 Int curr = init;
412 size_t iter_count = 0;
413 for (; iter_count < kMaxExactIter; iter_count++) {
414 if (Cmp(curr, max, cmp_op) != loop_if_cond_is) {
415 return IterationCount::Exact(iter_count);
416 }
417 if (auto next = Next(curr, binop_cst, binop_op, binop_rep)) {
418 curr = *next;
419 } else {
420 // There was an overflow, bailing out.
421 break;
422 }
423 }
424
425 if (binop_cst == 0) {
426 // If {binop_cst} is 0, the loop should either execute a single time or loop
427 // infinitely (since the increment is in the form of "i = i op binop_cst"
428 // with op being an arithmetic or bitwise binop). If we didn't detect above
429 // that it executes a single time, then we are in the latter case.
430 return {};
431 }
432
433 // Trying to figure out an approximate number of iterations
434 if (binop_op == StaticCanonicalForLoopMatcher::BinOp::kAdd) {
435 if (cmp_op ==
436 any_of(CmpOp::kUnsignedLessThan, CmpOp::kUnsignedLessThanOrEqual,
437 CmpOp::kSignedLessThan, CmpOp::kSignedLessThanOrEqual) &&
438 init < max && !SubWillOverflow(max, init) && loop_if_cond_is) {
439 // eg, for (int i = 0; i < 42; i += 2)
440 if (binop_cst < 0) {
441 // Will either loop forever or rely on underflow wrap-around to
442 // eventually stop.
443 return {};
444 }
445 DCHECK(!DivWillOverflow(max - init, binop_cst));
446 Int quotient = (max - init) / binop_cst;
447 DCHECK_GE(quotient, 0);
448 return IterationCount::Approx(quotient);
449 }
450 if (cmp_op == any_of(CmpOp::kUnsignedGreaterThan,
451 CmpOp::kUnsignedGreaterThanOrEqual,
452 CmpOp::kSignedGreaterThan,
453 CmpOp::kSignedGreaterThanOrEqual) &&
454 init > max && !SubWillOverflow(max, init) && loop_if_cond_is) {
455 // eg, for (int i = 42; i > 0; i += -2)
456 if (binop_cst > 0) {
457 // Will either loop forever or rely on overflow wrap-around to
458 // eventually stop.
459 return {};
460 }
461 if (DivWillOverflow(max - init, binop_cst)) return {};
462 Int quotient = (max - init) / binop_cst;
463 DCHECK_GE(quotient, 0);
464 return IterationCount::Approx(quotient);
465 }
466 if (cmp_op == CmpOp::kEqual && !SubWillOverflow(max, init) &&
467 !loop_if_cond_is) {
468 // eg, for (int i = 0; i != 42; i += 2)
469 // or, for (int i = 42; i != 0; i += -2)
470 if (init < max && binop_cst < 0) {
471 // Will either loop forever or rely on underflow wrap-around to
472 // eventually stop.
473 return {};
474 }
475 if (init > max && binop_cst > 0) {
476 // Will either loop forever or rely on overflow wrap-around to
477 // eventually stop.
478 return {};
479 }
480
481 Int remainder = (max - init) % binop_cst;
482 if (remainder != 0) {
483 // Will loop forever or rely on over/underflow wrap-around to eventually
484 // stop.
485 return {};
486 }
487
488 Int quotient = (max - init) / binop_cst;
489 DCHECK_GE(quotient, 0);
490 return IterationCount::Approx(quotient);
491 }
492 }
493
494 return {};
495}
496
497// Returns true if the loop
498// `for (i = initial_input, i cmp_op cmp_cst; i = i binop_op binop_cst)` has
499// fewer than `max_iter_` iterations.
501 uint64_t cmp_cst, CmpOp cmp_op, uint64_t initial_input, uint64_t binop_cst,
502 BinOp binop_op, WordRepresentation binop_rep, bool loop_if_cond_is) const {
503 switch (cmp_op) {
504 case CmpOp::kSignedLessThan:
505 case CmpOp::kSignedLessThanOrEqual:
506 case CmpOp::kSignedGreaterThan:
507 case CmpOp::kSignedGreaterThanOrEqual:
508 case CmpOp::kEqual:
509 if (binop_rep == WordRepresentation::Word32()) {
511 static_cast<int32_t>(initial_input), static_cast<int32_t>(cmp_cst),
512 cmp_op, static_cast<int32_t>(binop_cst), binop_op, binop_rep,
513 loop_if_cond_is);
514 } else {
517 static_cast<int64_t>(initial_input), static_cast<int64_t>(cmp_cst),
518 cmp_op, static_cast<int64_t>(binop_cst), binop_op, binop_rep,
519 loop_if_cond_is);
520 }
521 case CmpOp::kUnsignedLessThan:
522 case CmpOp::kUnsignedLessThanOrEqual:
523 case CmpOp::kUnsignedGreaterThan:
524 case CmpOp::kUnsignedGreaterThanOrEqual:
525 if (binop_rep == WordRepresentation::Word32()) {
527 static_cast<uint32_t>(initial_input),
528 static_cast<uint32_t>(cmp_cst), cmp_op,
529 static_cast<uint32_t>(binop_cst), binop_op, binop_rep,
530 loop_if_cond_is);
531 } else {
533 return CountIterationsImpl<uint64_t>(initial_input, cmp_cst, cmp_op,
534 binop_cst, binop_op, binop_rep,
535 loop_if_cond_is);
536 }
537 }
538}
539
542 switch (kind) {
544 return CmpOp::kEqual;
546 return CmpOp::kSignedLessThan;
548 return CmpOp::kSignedLessThanOrEqual;
550 return CmpOp::kUnsignedLessThan;
552 return CmpOp::kUnsignedLessThanOrEqual;
553 }
554}
557 switch (op) {
558 case CmpOp::kEqual:
559 return CmpOp::kEqual;
560 case CmpOp::kSignedLessThan:
561 return CmpOp::kSignedGreaterThan;
562 case CmpOp::kSignedLessThanOrEqual:
563 return CmpOp::kSignedGreaterThanOrEqual;
564 case CmpOp::kUnsignedLessThan:
565 return CmpOp::kUnsignedGreaterThan;
566 case CmpOp::kUnsignedLessThanOrEqual:
567 return CmpOp::kUnsignedGreaterThanOrEqual;
568 case CmpOp::kSignedGreaterThan:
569 return CmpOp::kSignedLessThan;
570 case CmpOp::kSignedGreaterThanOrEqual:
571 return CmpOp::kSignedLessThanOrEqual;
572 case CmpOp::kUnsignedGreaterThan:
573 return CmpOp::kUnsignedLessThan;
574 case CmpOp::kUnsignedGreaterThanOrEqual:
575 return CmpOp::kUnsignedLessThanOrEqual;
576 }
577}
578
579} // namespace v8::internal::compiler::turboshaft
Builtins::Kind kind
Definition builtins.cc:40
bool Contains(OpIndex op_idx) const
Definition graph.h:322
const Block * GetLoopHeader(const Block *block) const
Definition loop-finder.h:71
const ZoneUnorderedMap< const Block *, LoopInfo > & LoopHeaders() const
Definition loop-finder.h:68
IterationCount GetLoopIterationCount(const LoopFinder::LoopInfo &info) const
ZoneUnorderedMap< const Block *, IterationCount > loop_iteration_count_
IterationCount GetIterCountIfStaticCanonicalForLoop(const Block *header, OpIndex cond_idx, bool loop_if_cond_is) const
static constexpr bool BinopKindIsSupported(WordBinopOp::Kind binop_kind)
bool MatchPhiCompareCst(OpIndex cond_idx, StaticCanonicalForLoopMatcher::CmpOp *cmp_op, OpIndex *phi, uint64_t *cst) const
bool MatchCheckedOverflowBinop(OpIndex idx, V< Word > *left, V< Word > *right, BinOp *binop_op, WordRepresentation *binop_rep) const
IterationCount CountIterationsImpl(Int init, Int max, CmpOp cmp_op, Int binop_cst, StaticCanonicalForLoopMatcher::BinOp binop_op, WordRepresentation binop_rep, bool loop_if_cond_is) const
IterationCount CountIterations(uint64_t equal_cst, CmpOp cmp_op, uint64_t initial_input, uint64_t binop_cst, BinOp binop_op, WordRepresentation binop_rep, bool loop_if_cond_is) const
static constexpr CmpOp ComparisonKindToCmpOp(ComparisonOp::Kind kind)
static constexpr BinOp BinopFromOverflowCheckedBinopKind(OverflowCheckedBinopOp::Kind kind)
bool MatchWordBinop(OpIndex idx, V< Word > *left, V< Word > *right, BinOp *binop_op, WordRepresentation *binop_rep) const
static constexpr BinOp BinopFromWordBinopKind(WordBinopOp::Kind kind)
int start
double remainder
#define TRACE(...)
#define CASE_ARITH(op)
int int32_t
Definition unicode.cc:40
bool SignedSubOverflow64(int64_t lhs, int64_t rhs, int64_t *val)
Definition bits.h:352
bool SignedSubOverflow32(int32_t lhs, int32_t rhs, int32_t *val)
Definition bits.h:310
std::ostream & operator<<(std::ostream &os, PaddingSpace padding)
any_of(const Args &...) -> any_of< Args... >
StaticCanonicalForLoopMatcher::CmpOp CmpOp
#define DCHECK_LE(v1, v2)
Definition logging.h:490
#define DCHECK_GE(v1, v2)
Definition logging.h:488
#define DCHECK(condition)
Definition logging.h:482
#define DCHECK_EQ(v1, v2)
Definition logging.h:485
V8_INLINE OpIndex input(size_t i) const
Definition operations.h:959
const underlying_operation_t< Op > * TryCast() const
Definition operations.h:990