v8
V8 is Google’s open source high-performance JavaScript and WebAssembly engine, written in C++.
Loading...
Searching...
No Matches
copying-phase.h
Go to the documentation of this file.
1// Copyright 2022 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_COPYING_PHASE_H_
6#define V8_COMPILER_TURBOSHAFT_COPYING_PHASE_H_
7
8#include <algorithm>
9#include <cstddef>
10#include <cstdint>
11#include <optional>
12#include <utility>
13
14#include "src/base/iterator.h"
15#include "src/base/logging.h"
17#include "src/base/vector.h"
31
33
34using MaybeVariable = std::optional<Variable>;
35
36V8_EXPORT_PRIVATE int CountDecimalDigits(uint32_t value);
38 int spaces;
39};
40V8_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& os,
41 PaddingSpace padding);
42
43template <typename Next>
45template <typename Next>
47
48template <typename Derived, typename Base>
49class OutputGraphAssembler : public Base {
50#define FRIEND(op) friend struct op##Op;
52#undef FRIEND
53 template <size_t I, class D>
54 friend struct FixedArityOperationT;
55
56 OpIndex Map(OpIndex index) { return derived_this()->MapToNewGraph(index); }
57
59 return derived_this()->MapToNewGraph(index);
60 }
61
62 template <size_t N>
64 return derived_this()->template MapToNewGraph<N>(indices);
65 }
66
67 public:
68#define ASSEMBLE(operation) \
69 OpIndex AssembleOutputGraph##operation(const operation##Op& op) { \
70 return op.Explode( \
71 [a = assembler()](auto... args) { \
72 return a->Reduce##operation(args...); \
73 }, \
74 *this); \
75 }
77#undef ASSEMBLE
78
79 private:
80 Derived* derived_this() { return static_cast<Derived*>(this); }
84};
85
86template <class AfterNext>
87class GraphVisitor : public OutputGraphAssembler<GraphVisitor<AfterNext>,
88 VariableReducer<AfterNext>> {
89 template <typename N>
91 template <typename N>
92 friend class WasmRevecReducer;
93
94 public:
97
99 : input_graph_(Asm().modifiable_input_graph()),
100 current_input_block_(nullptr),
101 op_mapping_(Asm().input_graph().op_id_count(), OpIndex::Invalid(),
102 Asm().phase_zone(), &Asm().input_graph()),
103 block_mapping_(Asm().input_graph().block_count(), nullptr,
104 Asm().phase_zone()),
105 blocks_needing_variables_(Asm().input_graph().block_count(),
106 Asm().phase_zone()),
107 old_opindex_to_variables(Asm().input_graph().op_id_count(),
108 Asm().phase_zone(), &Asm().input_graph()),
110 Asm().output_graph().Reset();
111 }
112
113 // `trace_reduction` is a template parameter to avoid paying for tracing at
114 // runtime.
115 template <bool trace_reduction>
116 void VisitGraph() {
117 Asm().Analyze();
118
119 // Creating initial old-to-new Block mapping.
120 for (Block& input_block : Asm().modifiable_input_graph().blocks()) {
121 block_mapping_[input_block.index()] = Asm().output_graph().NewBlock(
122 input_block.IsLoop() ? Block::Kind::kLoopHeader : Block::Kind::kMerge,
123 &input_block);
124 }
125
126 // Visiting the graph.
128
129 Finalize();
130 }
131
132 void Bind(Block* block) {
133 Next::Bind(block);
134 block->SetOrigin(current_input_block());
135 }
136
137 void Finalize() {
138 // Updating the source_positions.
139 if (!Asm().input_graph().source_positions().empty()) {
140 for (OpIndex index : Asm().output_graph().AllOperationIndices()) {
141 OpIndex origin = Asm().output_graph().operation_origins()[index];
142 Asm().output_graph().source_positions()[index] =
143 origin.valid() ? Asm().input_graph().source_positions()[origin]
145 }
146 }
147 // Updating the operation origins.
148 NodeOriginTable* origins = Asm().data()->node_origins();
149 if (origins) {
150 for (OpIndex index : Asm().output_graph().AllOperationIndices()) {
151 OpIndex origin = Asm().output_graph().operation_origins()[index];
152 if (origin.valid()) {
153 origins->SetNodeOrigin(index.id(), origin.id());
154 }
155 }
156 }
157
159 }
160
162
166
167 // Emits a Goto to a cloned version of {input_block}, assuming that the only
168 // predecessor of this cloned copy will be the current block. {input_block} is
169 // not cloned right away (because this would recursively call VisitBlockBody,
170 // which could cause stack overflows), and is instead added to the
171 // {blocks_to_clone_} stack, whose blocks will be cloned once the current
172 // block has been fully visited.
173 void CloneBlockAndGoto(const Block* input_block) {
174 Block* new_block =
175 Asm().output_graph().NewBlock(input_block->kind(), input_block);
176
177 // Computing which input of Phi operations to use when visiting
178 // {input_block} (since {input_block} doesn't really have predecessors
179 // anymore).
180 int added_block_phi_input = input_block->GetPredecessorIndex(
181 Asm().current_block()->OriginForBlockEnd());
182
183 // There is no guarantees that {input_block} will be entirely removed just
184 // because it's cloned/inlined, since it's possible that it has predecessors
185 // for which this optimization didn't apply. As a result, we add it to
186 // {blocks_needing_variables_}, so that if it's ever generated
187 // normally, Variables are used when emitting its content, so that
188 // they can later be merged when control flow merges with the current
189 // version of {input_block} that we just cloned.
190 blocks_needing_variables_.Add(input_block->index().id());
191
192 Asm().Goto(new_block);
193
194 blocks_to_clone_.push_back({input_block, added_block_phi_input, new_block});
195 }
196
197 // Visits and emits {input_block} right now (ie, in the current block). This
198 // should not be called recursively in order to avoid stack overflow (ie,
199 // processing {input_block} should never lead to calling CloneAndInlingBlock).
200 void CloneAndInlineBlock(const Block* input_block) {
201 if (Asm().generating_unreachable_operations()) return;
202
203#ifdef DEBUG
204 // Making sure that we didn't call CloneAndInlineBlock recursively.
205 DCHECK(!is_in_recursive_inlining_);
206 ScopedModification<bool> recursive_guard(&is_in_recursive_inlining_, true);
207#endif
208
209 // Computing which input of Phi operations to use when visiting
210 // {input_block} (since {input_block} doesn't really have predecessors
211 // anymore).
212 int added_block_phi_input = input_block->GetPredecessorIndex(
213 Asm().current_block()->OriginForBlockEnd());
214
215 // There is no guarantees that {input_block} will be entirely removed just
216 // because it's cloned/inlined, since it's possible that it has predecessors
217 // for which this optimization didn't apply. As a result, we add it to
218 // {blocks_needing_variables_}, so that if it's ever generated
219 // normally, Variables are used when emitting its content, so that
220 // they can later be merged when control flow merges with the current
221 // version of {input_block} that we just cloned.
222 blocks_needing_variables_.Add(input_block->index().id());
223
226 input_block, added_block_phi_input);
227 }
228
229 // {InlineOp} introduces two limitations unlike {CloneAndInlineBlock}:
230 // 1. The input operation must not be emitted anymore as part of its
231 // regular input block;
232 // 2. {InlineOp} must not be used multiple times for the same input op.
233 bool InlineOp(OpIndex index, const Block* input_block) {
234 return VisitOpAndUpdateMapping<false>(index, input_block);
235 }
236
237 template <bool can_be_invalid = false>
238 OpIndex MapToNewGraph(OpIndex old_index, int predecessor_index = -1) {
239 DCHECK(old_index.valid());
240 OpIndex result = op_mapping_[old_index];
241
242 if (!result.valid()) {
243 // {op_mapping} doesn't have a mapping for {old_index}. The
244 // VariableReducer should provide the mapping.
245 MaybeVariable var = GetVariableFor(old_index);
246 if constexpr (can_be_invalid) {
247 if (!var.has_value()) {
248 return OpIndex::Invalid();
249 }
250 }
251 DCHECK(var.has_value());
252 if (predecessor_index == -1) {
253 result = Asm().GetVariable(var.value());
254 } else {
255 result = Asm().GetPredecessorValue(var.value(), predecessor_index);
256 }
257 }
258
259 DCHECK_IMPLIES(!can_be_invalid, result.valid());
260 return result;
261 }
262
263 template <bool can_be_invalid = false, typename T>
264 V<T> MapToNewGraph(V<T> old_index, int predecessor_index = -1) {
266 static_cast<OpIndex>(old_index), predecessor_index));
267 }
268
269 Block* MapToNewGraph(const Block* block) const {
270 Block* new_block = block_mapping_[block->index()];
271 DCHECK_NOT_NULL(new_block);
272 return new_block;
273 }
274
275 template <typename FunctionType>
278 if (op.input_count == 1) {
279 // If, in the previous CopyingPhase, a loop header was turned into a
280 // regular blocks, its PendingLoopPhis became Phis with a single input. We
281 // can now just get rid of these Phis.
282 return map(op.input(0), -1);
283 }
284
285 OpIndex ig_index = Asm().input_graph().Index(op);
286 if (Asm().current_block()->IsLoop()) {
287 DCHECK_EQ(op.input_count, 2);
288 OpIndex og_index = map(op.input(0), -1);
289 if (ig_index == op.input(PhiOp::kLoopPhiBackEdgeIndex)) {
290 // Avoid emitting a Loop Phi which points to itself, instead
291 // emit it's 0'th input.
292 return og_index;
293 }
294 return Asm().PendingLoopPhi(og_index, rep);
295 }
296
297 base::Vector<const OpIndex> old_inputs = op.inputs();
299 int predecessor_count = Asm().current_block()->PredecessorCount();
300 Block* old_pred = current_input_block_->LastPredecessor();
301 Block* new_pred = Asm().current_block()->LastPredecessor();
302 // Control predecessors might be missing after the optimization phase. So we
303 // need to skip phi inputs that belong to control predecessors that have no
304 // equivalent in the new graph.
305
306 // We first assume that the order if the predecessors of the current block
307 // did not change. If it did, {new_pred} won't be nullptr at the end of this
308 // loop, and we'll instead fall back to the slower code below to compute the
309 // inputs of the Phi.
310 int predecessor_index = predecessor_count - 1;
311 int old_index = static_cast<int>(old_inputs.size()) - 1;
312 for (OpIndex input : base::Reversed(old_inputs)) {
313 if (new_pred && new_pred->OriginForBlockEnd() == old_pred) {
314 // Phis inputs have to come from predecessors. We thus have to
315 // MapToNewGraph with {predecessor_index} so that we get an OpIndex that
316 // is from a predecessor rather than one that comes from a Variable
317 // merged in the current block.
318 new_inputs.push_back(map(input, predecessor_index, old_index));
319 new_pred = new_pred->NeighboringPredecessor();
320 predecessor_index--;
321 }
322 old_pred = old_pred->NeighboringPredecessor();
323 old_index--;
324 }
325 DCHECK_IMPLIES(new_pred == nullptr, old_pred == nullptr);
326
327 if (new_pred != nullptr) {
328 // If {new_pred} is not nullptr, then the order of the predecessors
329 // changed. This should only happen with blocks that were introduced in
330 // the previous graph. For instance, consider this (partial) dominator
331 // tree:
332 //
333 // â•  7
334 // â•‘ â•  8
335 // ║ ╚ 10
336 // â•  9
337 // ╚ 11
338 //
339 // Where the predecessors of block 11 are blocks 9 and 10 (in that order).
340 // In dominator visit order, block 10 will be visited before block 9.
341 // Since blocks are added to predecessors when the predecessors are
342 // visited, it means that in the new graph, the predecessors of block 11
343 // are [10, 9] rather than [9, 10].
344 // To account for this, we reorder the inputs of the Phi, and get rid of
345 // inputs from blocks that vanished.
346
347#ifdef DEBUG
348 // To check that indices are set properly, we zap them in debug builds.
349 for (auto& block : Asm().modifiable_input_graph().blocks()) {
350 block.clear_custom_data();
351 }
352#endif
353 uint32_t pos = current_input_block_->PredecessorCount() - 1;
354 for (old_pred = current_input_block_->LastPredecessor();
355 old_pred != nullptr; old_pred = old_pred->NeighboringPredecessor()) {
356 // Store the current index of the {old_pred}.
358 }
359
360 // Filling {new_inputs}: we iterate the new predecessors, and, for each
361 // predecessor, we check the index of the input corresponding to the old
362 // predecessor, and we put it next in {new_inputs}.
363 new_inputs.clear();
364 predecessor_index = predecessor_count - 1;
365 for (new_pred = Asm().current_block()->LastPredecessor();
366 new_pred != nullptr; new_pred = new_pred->NeighboringPredecessor()) {
367 const Block* origin = new_pred->OriginForBlockEnd();
368 DCHECK_NOT_NULL(origin);
369 old_index =
371 OpIndex input = old_inputs[old_index];
372 // Phis inputs have to come from predecessors. We thus have to
373 // MapToNewGraph with {predecessor_index} so that we get an OpIndex that
374 // is from a predecessor rather than one that comes from a Variable
375 // merged in the current block.
376 new_inputs.push_back(map(input, predecessor_index, old_index));
377 predecessor_index--;
378 }
379 }
380
381 DCHECK_EQ(new_inputs.size(), Asm().current_block()->PredecessorCount());
382
383 if (new_inputs.size() == 1) {
384 // This Operation used to be a Phi in a Merge, but since one (or more) of
385 // the inputs of the merge have been removed, there is no need for a Phi
386 // anymore.
387 return new_inputs[0];
388 }
389
390 std::reverse(new_inputs.begin(), new_inputs.end());
391 return Asm().ReducePhi(base::VectorOf(new_inputs), rep);
392 }
393
394 // The block from the input graph that corresponds to the current block as a
395 // branch destination. Such a block might not exist, and this function uses a
396 // trick to compute such a block in almost all cases, but might rarely fail
397 // and return `nullptr` instead.
398 const Block* OriginForBlockStart(Block* block) const {
399 // Check that `block->origin_` is still valid as a block start and was not
400 // changed to a semantically different block when inlining blocks.
401 const Block* origin = block->origin_;
402 if (origin && MapToNewGraph(origin) == block) return origin;
403 return nullptr;
404 }
405
406 // Clone all of the blocks in {sub_graph} (which should be Blocks of the input
407 // graph). If `keep_loop_kinds` is true, the loop headers are preserved, and
408 // otherwise they are marked as Merge. An initial GotoOp jumping to the 1st
409 // block of `sub_graph` is always emitted. The output Block corresponding to
410 // the 1st block of `sub_graph` is returned.
411 template <class Set>
412 Block* CloneSubGraph(Set sub_graph, bool keep_loop_kinds,
413 bool is_loop_after_peeling = false) {
414 // The BlockIndex of the blocks of `sub_graph` should be sorted so that
415 // visiting them in order is correct (all of the predecessors of a block
416 // should always be visited before the block itself).
417 DCHECK(std::is_sorted(sub_graph.begin(), sub_graph.end(),
418 [](const Block* a, const Block* b) {
419 return a->index().id() <= b->index().id();
420 }));
421
422 // 1. Create new blocks, and update old->new mapping. This is required to
423 // emit multiple times the blocks of {sub_graph}: if a block `B1` in
424 // {sub_graph} ends with a Branch/Goto to a block `B2` that is also in
425 // {sub_graph}, then this Branch/Goto should go to the version of `B2` that
426 // this CloneSubGraph will insert, rather than to a version inserted by a
427 // previous call to CloneSubGraph or the version that the regular
428 // VisitAllBlock function will emit.
429 ZoneVector<Block*> old_mappings(sub_graph.size(), Asm().phase_zone());
430 for (auto&& [input_block, old_mapping] :
431 base::zip(sub_graph, old_mappings)) {
432 old_mapping = block_mapping_[input_block->index()];
433 Block::Kind kind = keep_loop_kinds && input_block->IsLoop()
436 block_mapping_[input_block->index()] =
437 Asm().output_graph().NewBlock(kind, input_block);
438 }
439
440 // 2. Visit block in correct order (begin to end)
441
442 // Emit a goto to 1st block.
443 Block* start = block_mapping_[(*sub_graph.begin())->index()];
444#ifdef DEBUG
445 if (is_loop_after_peeling) start->set_has_peeled_iteration();
446#endif
447 Asm().Goto(start);
448 // Visiting `sub_graph`.
449 for (const Block* block : sub_graph) {
450 blocks_needing_variables_.Add(block->index().id());
451 VisitBlock<false>(block);
453 }
454
455 // 3. Restore initial old->new mapping
456 for (auto&& [input_block, old_mapping] :
457 base::zip(sub_graph, old_mappings)) {
458 block_mapping_[input_block->index()] = old_mapping;
459 }
460
461 return start;
462 }
463
464 template <bool can_be_invalid = false>
466 int predecessor_index = -1) {
467 if (!old_index.has_value()) return OptionalOpIndex::Nullopt();
468 return MapToNewGraph<can_be_invalid>(old_index.value(), predecessor_index);
469 }
470
471 template <size_t expected_size>
480
481 private:
482 template <bool trace_reduction>
485
486 visit_stack.push_back(&Asm().input_graph().StartBlock());
487 while (!visit_stack.empty()) {
488 const Block* block = visit_stack.back();
489 visit_stack.pop_back();
492
493 for (Block* child = block->LastChild(); child != nullptr;
494 child = child->NeighboringChild()) {
495 visit_stack.push_back(child);
496 }
497 }
498 }
499
500 template <bool trace_reduction>
501 void VisitBlock(const Block* input_block) {
503 Asm().SetCurrentOrigin(OpIndex::Invalid());
505 blocks_needing_variables_.Contains(input_block->index().id());
506 if constexpr (trace_reduction) {
507 std::cout << "\nold " << PrintAsBlockHeader{*input_block} << "\n";
508 std::cout << "new "
509 << PrintAsBlockHeader{*MapToNewGraph(input_block),
510 Asm().output_graph().next_block_index()}
511 << "\n";
512 }
513 Block* new_block = MapToNewGraph(input_block);
514 if (Asm().Bind(new_block)) {
516 input_block);
517 if constexpr (trace_reduction) TraceBlockFinished();
518 } else {
519 if constexpr (trace_reduction) TraceBlockUnreachable();
520 }
521
522 // If we eliminate a loop backedge, we need to turn the loop into a
523 // single-predecessor merge block.
525 const Operation& last_op = input_block->LastOperation(Asm().input_graph());
526 if (auto* final_goto = last_op.TryCast<GotoOp>()) {
527 if (final_goto->destination->IsLoop()) {
528 if (input_block->index() >= final_goto->destination->index()) {
529 Asm().FinalizeLoop(MapToNewGraph(final_goto->destination));
530 } else {
531 // We have a forward jump to a loop, rather than a backedge. We
532 // don't need to do anything.
533 }
534 }
535 }
536 }
537
538 enum class CanHavePhis { kNo, kYes };
539 enum class ForCloning { kNo, kYes };
540
541 template <CanHavePhis can_have_phis, ForCloning for_cloning,
542 bool trace_reduction>
543 void VisitBlockBody(const Block* input_block,
544 int added_block_phi_input = -1) {
545 DCHECK_NOT_NULL(Asm().current_block());
546 current_input_block_ = input_block;
547
548 // Phis could be mutually recursive, for instance (in a loop header):
549 //
550 // p1 = phi(a, p2)
551 // p2 = phi(b, p1)
552 //
553 // In this case, if we are currently unrolling the loop and visiting this
554 // loop header that is now part of the loop body, then if we visit Phis
555 // and emit new mapping (with CreateOldToNewMapping) as we go along, we
556 // would visit p1 and emit a mapping saying "p1 = p2", and use this
557 // mapping when visiting p2, then we'd map p2 to p2 instead of p1. To
558 // overcome this issue, we first visit the Phis of the loop, emit the new
559 // phis, and record the new mapping in a side-table ({new_phi_values}).
560 // Then, we visit all of the operations of the loop and commit the new
561 // mappings: phis were emitted before using the old mapping, and all of
562 // the other operations will use the new mapping (as they should).
563 //
564 // Note that Phis are not always at the begining of blocks, but when they
565 // aren't, they can't have inputs from the current block (except on their
566 // backedge for loop phis, but they start as PendingLoopPhis without
567 // backedge input), so visiting all Phis first is safe.
568
569 // Visiting Phis and collecting their new OpIndices.
570 base::SmallVector<OpIndex, 64> new_phi_values;
571 if constexpr (can_have_phis == CanHavePhis::kYes) {
572 for (OpIndex index : Asm().input_graph().OperationIndices(*input_block)) {
573 if (ShouldSkipOperation(Asm().input_graph().Get(index))) continue;
574 DCHECK_NOT_NULL(Asm().current_block());
575 if (Asm().input_graph().Get(index).template Is<PhiOp>()) {
576 OpIndex new_index;
577 if constexpr (for_cloning == ForCloning::kYes) {
578 // When cloning a block, it only has a single predecessor, and Phis
579 // should therefore be removed and be replaced by the input
580 // corresponding to this predecessor.
581 DCHECK_NE(added_block_phi_input, -1);
582 // This Phi has been cloned/inlined, and has thus now a single
583 // predecessor, and shouldn't be a Phi anymore.
584 new_index = MapToNewGraph(
585 Asm().input_graph().Get(index).input(added_block_phi_input));
586 } else {
587 new_index =
588 VisitOpNoMappingUpdate<trace_reduction>(index, input_block);
589 }
590 new_phi_values.push_back(new_index);
591 if (!Asm().current_block()) {
592 // A reducer has detected, based on the Phis of the block that were
593 // visited so far, that we are in unreachable code (or, less likely,
594 // decided, based on some Phis only, to jump away from this block?).
595 return;
596 }
597 }
598 }
599 }
600 DCHECK_NOT_NULL(Asm().current_block());
601
602 // Visiting everything, updating Phi mappings, and emitting non-phi
603 // operations.
604 int phi_num = 0;
605 bool stopped_early = false;
607 Asm().input_graph().OperationIndices(*input_block))) {
608 if (ShouldSkipOperation(Asm().input_graph().Get(index))) continue;
609 const Operation& op = Asm().input_graph().Get(index);
610 if constexpr (can_have_phis == CanHavePhis::kYes) {
611 if (op.Is<PhiOp>()) {
612 CreateOldToNewMapping(index, new_phi_values[phi_num++]);
613 continue;
614 }
615 }
616 // Blocks with a single predecessor (for which CanHavePhis might be kNo)
617 // can still have phis if they used to be loop header that were turned
618 // into regular blocks.
619 DCHECK_IMPLIES(op.Is<PhiOp>(), op.input_count == 1);
620
621 if (!VisitOpAndUpdateMapping<trace_reduction>(index, input_block)) {
622 stopped_early = true;
623 break;
624 }
625 }
626 // If the last operation of the loop above (= the one-before-last operation
627 // of the block) was lowered to an unconditional deopt/trap/something like
628 // that, then current_block will now be null, and there is no need visit the
629 // last operation of the block.
630 if (stopped_early || Asm().current_block() == nullptr) return;
631
632 // The final operation (which should be a block terminator) of the block
633 // is processed separately, because if it's a Goto to a block with a
634 // single predecessor, we'll inline it. (we could have had a check `if (op
635 // is a Goto)` in the loop above, but since this can only be true for the
636 // last operation, we instead extracted it here to make things faster).
637 const Operation& terminator =
638 input_block->LastOperation(Asm().input_graph());
639 DCHECK(terminator.IsBlockTerminator());
640 VisitBlockTerminator<trace_reduction>(terminator, input_block);
641 }
642
643 template <bool trace_reduction>
644 bool VisitOpAndUpdateMapping(OpIndex index, const Block* input_block) {
645 if (Asm().current_block() == nullptr) return false;
646 OpIndex new_index =
647 VisitOpNoMappingUpdate<trace_reduction>(index, input_block);
648 const Operation& op = Asm().input_graph().Get(index);
649 if (CanBeUsedAsInput(op) && new_index.valid()) {
650 CreateOldToNewMapping(index, new_index);
651 }
652 return true;
653 }
654
655 template <bool trace_reduction>
656 OpIndex VisitOpNoMappingUpdate(OpIndex index, const Block* input_block) {
657 Block* current_block = Asm().current_block();
658 DCHECK_NOT_NULL(current_block);
659 Asm().SetCurrentOrigin(index);
660 current_block->SetOrigin(input_block);
661 OpIndex first_output_index = Asm().output_graph().next_operation_index();
662 USE(first_output_index);
663 const Operation& op = Asm().input_graph().Get(index);
664 if constexpr (trace_reduction) TraceReductionStart(index);
665 if (ShouldSkipOperation(op)) {
666 if constexpr (trace_reduction) TraceOperationSkipped();
667 return OpIndex::Invalid();
668 }
669 OpIndex new_index;
670 switch (op.opcode) {
671#define EMIT_INSTR_CASE(Name) \
672 case Opcode::k##Name: \
673 if (MayThrow(Opcode::k##Name)) return OpIndex::Invalid(); \
674 new_index = Asm().ReduceInputGraph##Name(index, op.Cast<Name##Op>()); \
675 break;
677#undef EMIT_INSTR_CASE
678 }
679 if constexpr (trace_reduction) {
680 if (CanBeUsedAsInput(op) && !new_index.valid()) {
682 } else {
683 TraceReductionResult(current_block, first_output_index, new_index);
684 }
685 }
686#ifdef DEBUG
687 DCHECK_IMPLIES(new_index.valid(),
688 Asm().output_graph().BelongsToThisGraph(new_index));
689 if (V8_UNLIKELY(v8_flags.turboshaft_verify_reductions)) {
690 if (new_index.valid()) {
691 const Operation& new_op = Asm().output_graph().Get(new_index);
692 if (!new_op.Is<TupleOp>()) {
693 // Checking that the outputs_rep of the new operation are the same as
694 // the old operation. (except for tuples, since they don't have
695 // outputs_rep)
696 DCHECK_EQ(new_op.outputs_rep().size(), op.outputs_rep().size());
697 for (size_t i = 0; i < new_op.outputs_rep().size(); ++i) {
698 DCHECK(new_op.outputs_rep()[i].AllowImplicitRepresentationChangeTo(
699 op.outputs_rep()[i],
700 Asm().output_graph().IsCreatedFromTurbofan()));
701 }
702 }
703 Asm().Verify(index, new_index);
704 }
705 }
706#endif // DEBUG
707 return new_index;
708 }
709
710 template <bool trace_reduction>
711 void VisitBlockTerminator(const Operation& terminator,
712 const Block* input_block) {
713 if (Asm().CanAutoInlineBlocksWithSinglePredecessor() &&
714 terminator.Is<GotoOp>()) {
715 Block* destination = terminator.Cast<GotoOp>().destination;
716 if (destination->PredecessorCount() == 1) {
718 return;
719 }
720 }
721 // Just going through the regular VisitOp function.
722 OpIndex index = Asm().input_graph().Index(terminator);
724 }
725
726 template <bool trace_reduction>
737
738 template <bool trace_reduction>
740 while (block_to_inline_now_) {
741 Block* input_block = block_to_inline_now_;
742 block_to_inline_now_ = nullptr;
744 if constexpr (trace_reduction) {
745 std::cout << "Inlining " << PrintAsBlockHeader{*input_block} << "\n";
746 }
748 input_block);
749 }
750 }
751
752 template <bool trace_reduction>
753 void DoCloneBlock(const Block* input_block, int added_block_phi_input,
754 Block* output_block) {
755 DCHECK_EQ(output_block->PredecessorCount(), 1);
756 if constexpr (trace_reduction) {
757 std::cout << "\nCloning old " << PrintAsBlockHeader{*input_block} << "\n";
758 std::cout << "As new "
759 << PrintAsBlockHeader{*output_block,
760 Asm().output_graph().next_block_index()}
761 << "\n";
762 }
763
765
766 Asm().BindReachable(output_block);
768 input_block, added_block_phi_input);
769
770 if constexpr (trace_reduction) TraceBlockFinished();
771 }
772
774 std::cout << "╭── o" << index.id() << ": "
775 << PaddingSpace{5 - CountDecimalDigits(index.id())}
776 << OperationPrintStyle{Asm().input_graph().Get(index), "#o"}
777 << "\n";
778 }
779 void TraceOperationSkipped() { std::cout << "╰─> skipped\n\n"; }
780 void TraceBlockUnreachable() { std::cout << "╰─> unreachable\n\n"; }
781 void TraceReductionResult(Block* current_block, OpIndex first_output_index,
782 OpIndex new_index) {
783 if (new_index < first_output_index) {
784 // The operation was replaced with an already existing one.
785 std::cout << "╰─> #n" << new_index.id() << "\n";
786 }
787 bool before_arrow = new_index >= first_output_index;
788 for (const Operation& op : Asm().output_graph().operations(
789 first_output_index, Asm().output_graph().next_operation_index())) {
790 OpIndex index = Asm().output_graph().Index(op);
791 const char* prefix;
792 if (index == new_index) {
793 prefix = "╰─>";
794 before_arrow = false;
795 } else if (before_arrow) {
796 prefix = "│ ";
797 } else {
798 prefix = " ";
799 }
800 std::cout << prefix << " n" << index.id() << ": "
801 << PaddingSpace{5 - CountDecimalDigits(index.id())}
802 << OperationPrintStyle{Asm().output_graph().Get(index), "#n"}
803 << "\n";
804 if (op.IsBlockTerminator() && Asm().current_block() &&
805 Asm().current_block() != current_block) {
806 current_block = &Asm().output_graph().Get(
807 BlockIndex(current_block->index().id() + 1));
808 std::cout << "new " << PrintAsBlockHeader{*current_block} << "\n";
809 }
810 }
811 std::cout << "\n";
812 }
813 void TraceBlockFinished() { std::cout << "\n"; }
814
815 // These functions take an operation from the old graph and use the assembler
816 // to emit a corresponding operation in the new graph, translating inputs and
817 // blocks accordingly.
820 if (op.is_backedge) {
821 DCHECK(destination->IsBound());
822 DCHECK(destination->IsLoop());
824 }
825 // It is important that we first fix loop phis and then reduce the `Goto`,
826 // because reducing the `Goto` can have side effects, in particular, it can
827 // modify affect the SnapshotTable of `VariableReducer`, which is also used
828 // by `FixLoopPhis()`.
829 Asm().ReduceGoto(destination, op.is_backedge);
830 return OpIndex::Invalid();
831 }
833 Block* if_true = MapToNewGraph(op.if_true);
834 Block* if_false = MapToNewGraph(op.if_false);
835 return Asm().ReduceBranch(MapToNewGraph(op.condition()), if_true, if_false,
836 op.hint);
837 }
840 for (SwitchOp::Case c : op.cases) {
842 }
843 return Asm().ReduceSwitch(
844 MapToNewGraph(op.input()),
845 Asm().graph_zone()->CloneVector(base::VectorOf(cases)),
847 }
849 return ResolvePhi(
850 op,
851 [this](OpIndex ind, int predecessor_index, int old_index = 0) {
852 return MapToNewGraph(ind, predecessor_index);
853 },
854 op.rep);
855 }
860 auto inputs = MapToNewGraph<32>(op.inputs());
861 return Asm().ReduceFrameState(base::VectorOf(inputs), op.inlined, op.data);
862 }
864 V<CallTarget> callee = MapToNewGraph(op.callee());
866 auto arguments = MapToNewGraph<16>(op.arguments());
867 return Asm().ReduceCall(callee, frame_state, base::VectorOf(arguments),
868 op.descriptor, op.Effects());
869 }
871 const Operation& throwing_operation =
872 Asm().input_graph().Get(op.throwing_operation());
874 switch (throwing_operation.opcode) {
875#define CASE(Name) \
876 case Opcode::k##Name: \
877 result = Asm().ReduceInputGraph##Name( \
878 op.throwing_operation(), throwing_operation.Cast<Name##Op>()); \
879 break;
881#undef CASE
882 default:
883 UNREACHABLE();
884 }
885 return result;
886 }
887
890 &Asm().input_graph());
892 &Asm().input_graph());
893 // To translate `CheckException` to the new graph, we reduce the throwing
894 // operation (actually it's `DidntThrow` operation, but that triggers the
895 // actual reduction) with a catch scope. If the reduction replaces the
896 // throwing operation with other throwing operations, all of them will be
897 // connected to the provided catch block. The reduction should automatically
898 // bind a block that represents non-throwing control flow of the original
899 // operation, so we can inline the rest of the `didnt_throw` block.
900 {
901 CatchScope scope(Asm(), MapToNewGraph(op.catch_block));
902 DCHECK(Asm().input_graph().Get(*it).template Is<DidntThrowOp>());
903 if (!Asm().InlineOp(*it, op.didnt_throw_block)) {
904 return V<None>::Invalid();
905 }
906 ++it;
907 }
908 for (; it != end; ++it) {
909 // Using `InlineOp` requires that the inlined operation is not emitted
910 // multiple times. This is the case here because we just removed the
911 // single predecessor of `didnt_throw_block`.
912 if (!Asm().InlineOp(*it, op.didnt_throw_block)) {
913 break;
914 }
915 }
916 return V<None>::Invalid();
917 }
918
920 // Calling the AssemblerOpInterface rather than the first Reduce method
921 // in order to make use of the Parameter cache.
922 return Asm().Parameter(param.parameter_index, param.rep, param.debug_name);
923 }
924
925 void CreateOldToNewMapping(OpIndex old_index, OpIndex new_index) {
926 DCHECK(old_index.valid());
927 DCHECK(Asm().input_graph().BelongsToThisGraph(old_index));
928 DCHECK_IMPLIES(new_index.valid(),
929 Asm().output_graph().BelongsToThisGraph(new_index));
930
932 MaybeVariable var = GetVariableFor(old_index);
933 if (!var.has_value()) {
935 Asm().input_graph().Get(old_index).outputs_rep().size() == 1
936 ? static_cast<const MaybeRegisterRepresentation&>(
937 Asm().input_graph().Get(old_index).outputs_rep()[0])
939 var = Asm().NewLoopInvariantVariable(rep);
940 SetVariableFor(old_index, *var);
941 }
942 Asm().SetVariable(*var, new_index);
943 return;
944 }
945
946 DCHECK(!op_mapping_[old_index].valid());
947 op_mapping_[old_index] = new_index;
948 }
949
951 return old_opindex_to_variables[old_index];
952 }
953
954 void SetVariableFor(OpIndex old_index, MaybeVariable var) {
955 DCHECK(!old_opindex_to_variables[old_index].has_value());
956 old_opindex_to_variables[old_index] = var;
957 }
958
959 void FixLoopPhis(Block* input_graph_loop) {
960 DCHECK(input_graph_loop->IsLoop());
961 Block* output_graph_loop = MapToNewGraph(input_graph_loop);
962 DCHECK(output_graph_loop->IsLoop());
963 for (const Operation& op : Asm().input_graph().operations(
964 input_graph_loop->begin(), input_graph_loop->end())) {
965 if (auto* input_phi = op.TryCast<PhiOp>()) {
966 OpIndex phi_index =
967 MapToNewGraph<true>(Asm().input_graph().Index(*input_phi));
968 if (!phi_index.valid() || !output_graph_loop->Contains(phi_index)) {
969 // Unused phis are skipped, so they are not be mapped to anything in
970 // the new graph. If the phi is reduced to an operation from a
971 // different block, then there is no loop phi in the current loop
972 // header to take care of.
973 continue;
974 }
975 Asm().FixLoopPhi(*input_phi, phi_index, output_graph_loop);
976 }
977 }
978 }
979
981 OptimizedCompilationInfo* info_ = Asm().data()->info();
983
985
986 // Mappings from old OpIndices to new OpIndices.
988
989 // Mappings from old blocks to new blocks.
991
992 // {current_block_needs_variables_} is set to true if the current block should
993 // use Variables to map old to new OpIndex rather than just {op_mapping}. This
994 // is typically the case when the block has been cloned.
996
997 // When {turn_loop_without_backedge_into_merge_} is true (the default), when
998 // processing an input block that ended with a loop backedge but doesn't
999 // anymore, the loop header is turned into a regular merge. This can be turned
1000 // off when unrolling a loop for instance.
1002
1003 // Set of Blocks for which Variables should be used rather than
1004 // {op_mapping}.
1006
1007 // Mapping from old OpIndex to Variables.
1009
1010 // When the last operation of a Block is a Goto to a Block with a single
1011 // predecessor, we always inline the destination into the current block. To
1012 // avoid making this process recursive (which could lead to stack overflows),
1013 // we set the variable {block_to_inline_now_} instead. Right after we're done
1014 // visiting a Block, the function ProcessWaitingCloningAndInlining will inline
1015 // {block_to_inline_now_} (if it's set) in a non-recursive way.
1017
1018 // When a Reducer wants to clone a block (for instance,
1019 // BranchEliminationReducer, in order to remove Phis or to replace a Branch by
1020 // a Goto), this block is not cloned right away, in order to avoid recursion
1021 // (which could lead to stack overflows). Instead, we add this block to
1022 // {blocks_to_clone_}. Right after we're done visiting a Block, the function
1023 // ProcessWaitingCloningAndInlining will actually clone the blocks in
1024 // {blocks_to_clone_} in a non-recursive way.
1031
1032#ifdef DEBUG
1033 // Recursively inlining blocks is still allowed (mainly for
1034 // LoopUnrollingReducer), but it shouldn't be actually recursive. This is
1035 // checked by the {is_in_recursive_inlining_}, which is set to true while
1036 // recursively inlining a block. Trying to inline a block while
1037 // {is_in_recursive_inlining_} is true will lead to a DCHECK failure.
1038 bool is_in_recursive_inlining_ = false;
1039#endif
1040};
1041
1042template <template <class> class... Reducers>
1044
1045template <template <class> class... Reducers>
1047 public:
1048 static void Run(PipelineData* data, Graph& input_graph, Zone* phase_zone,
1049 bool trace_reductions = false) {
1050 TSAssembler<GraphVisitor, Reducers...> phase(
1051 data, input_graph, input_graph.GetOrCreateCompanion(), phase_zone);
1052#ifdef DEBUG
1053 if (trace_reductions) {
1054 phase.template VisitGraph<true>();
1055 } else {
1056 phase.template VisitGraph<false>();
1057 }
1058#else
1059 phase.template VisitGraph<false>();
1060#endif // DEBUG
1061 }
1062};
1063
1064template <template <typename> typename... Reducers>
1066 public:
1067 static void Run(PipelineData* data, Zone* phase_zone) {
1068 Graph& input_graph = data->graph();
1070 data, input_graph, phase_zone,
1071 data->info()->turboshaft_trace_reduction());
1072 }
1073};
1074
1075} // namespace v8::internal::compiler::turboshaft
1076
1077#endif // V8_COMPILER_TURBOSHAFT_COPYING_PHASE_H_
Builtins::Kind kind
Definition builtins.cc:40
SourcePosition pos
void pop_back(size_t count=1)
size_t size() const
void emplace_back(Args &&... args)
constexpr size_t size() const
Definition vector.h:70
bool Contains(int i) const
Definition bit-vector.h:180
static SourcePosition Unknown()
const Block * OriginForBlockEnd() const
Definition graph.h:428
void set_custom_data(uint32_t data, CustomDataKind kind_for_debug_check)
Definition graph.h:509
bool Contains(OpIndex op_idx) const
Definition graph.h:322
const Operation & LastOperation(const Graph &graph) const
Definition graph.h:1242
void SetOrigin(const Block *origin)
Definition graph.h:420
uint32_t get_custom_data(CustomDataKind kind_for_debug_check) const
Definition graph.h:516
int GetPredecessorIndex(const Block *target) const
Definition graph.h:368
static void Run(PipelineData *data, Graph &input_graph, Zone *phase_zone, bool trace_reductions=false)
static void Run(PipelineData *data, Zone *phase_zone)
const Block * OriginForBlockStart(Block *block) const
V< T > MapToNewGraph(V< T > old_index, int predecessor_index=-1)
V8_INLINE OpIndex AssembleOutputGraphGoto(const GotoOp &op)
FixedOpIndexSidetable< OpIndex > op_mapping_
void TraceReductionResult(Block *current_block, OpIndex first_output_index, OpIndex new_index)
void VisitBlockBody(const Block *input_block, int added_block_phi_input=-1)
OptionalOpIndex MapToNewGraph(OptionalOpIndex old_index, int predecessor_index=-1)
V8_INLINE OpIndex AssembleOutputGraphFrameState(const FrameStateOp &op)
bool VisitOpAndUpdateMapping(OpIndex index, const Block *input_block)
FixedOpIndexSidetable< MaybeVariable > old_opindex_to_variables
FixedBlockSidetable< Block * > block_mapping_
OpIndex AssembleOutputGraphSwitch(const SwitchOp &op)
Block * MapToNewGraph(const Block *block) const
OpIndex ResolvePhi(const PhiOp &op, FunctionType &&map, RegisterRepresentation rep)
void CloneAndInlineBlock(const Block *input_block)
void CreateOldToNewMapping(OpIndex old_index, OpIndex new_index)
bool InlineOp(OpIndex index, const Block *input_block)
void VisitBlockTerminator(const Operation &terminator, const Block *input_block)
V8_INLINE OpIndex AssembleOutputGraphBranch(const BranchOp &op)
V< None > AssembleOutputGraphCheckException(const CheckExceptionOp &op)
OpIndex AssembleOutputGraphPendingLoopPhi(const PendingLoopPhiOp &op)
base::SmallVector< OpIndex, expected_size > MapToNewGraph(base::Vector< const OpIndex > inputs)
void SetVariableFor(OpIndex old_index, MaybeVariable var)
OpIndex MapToNewGraph(OpIndex old_index, int predecessor_index=-1)
OpIndex VisitOpNoMappingUpdate(OpIndex index, const Block *input_block)
OpIndex AssembleOutputGraphDidntThrow(const DidntThrowOp &op)
OpIndex AssembleOutputGraphParameter(const ParameterOp &param)
void DoCloneBlock(const Block *input_block, int added_block_phi_input, Block *output_block)
void CloneBlockAndGoto(const Block *input_block)
MaybeVariable GetVariableFor(OpIndex old_index) const
Block * CloneSubGraph(Set sub_graph, bool keep_loop_kinds, bool is_loop_after_peeling=false)
static constexpr MaybeRegisterRepresentation None()
static constexpr OpIndex Invalid()
Definition index.h:88
constexpr uint32_t id() const
Definition index.h:61
static constexpr OptionalOpIndex Nullopt()
Definition index.h:171
base::SmallVector< OpIndex, N > Map(base::Vector< const OpIndex > indices)
Assembler< typename Base::ReducerList > * assembler()
OptionalOpIndex Map(OptionalOpIndex index)
static V< T > Cast(V< U > index)
Definition index.h:632
#define TURBOSHAFT_REDUCER_BOILERPLATE(Name)
Definition assembler.h:823
#define FRIEND(op)
#define EMIT_INSTR_CASE(Name)
#define ASSEMBLE(operation)
int start
int end
Zone * graph_zone
NodeOriginTable * origins
SourcePositionTable * source_positions
std::map< const std::string, const std::string > map
ZoneVector< RpoNumber > & result
InstructionOperand destination
auto zip(Containers &... containers)
Definition iterator.h:208
auto IterateWithoutLast(T &t)
Definition iterator.h:130
auto Reversed(T &t)
Definition iterator.h:105
constexpr Vector< T > VectorOf(T *start, size_t size)
Definition vector.h:360
std::ostream & operator<<(std::ostream &os, PaddingSpace padding)
int CountDecimalDigits(uint32_t value)
V8_INLINE const Operation & Get(const Graph &graph, OpIndex index)
Definition graph.h:1231
V8_EXPORT_PRIVATE V8_INLINE bool ShouldSkipOperation(const Operation &op)
std::optional< Variable > MaybeVariable
V8_INLINE bool CanBeUsedAsInput(const Operation &op)
bool(* FunctionType)(const Operation &op, Zone *zone)
Definition use-map.h:12
bool Is(IndirectHandle< U > value)
Definition handles-inl.h:51
V8_EXPORT_PRIVATE FlagValues v8_flags
base::Vector< T > CloneVector(Zone *zone, base::Vector< const T > other)
Definition zone-utils.h:18
#define TURBOSHAFT_THROWING_OPERATIONS_LIST(V)
Definition operations.h:426
#define TURBOSHAFT_OPERATION_LIST(V)
Definition operations.h:362
#define DCHECK_NOT_NULL(val)
Definition logging.h:492
#define DCHECK_IMPLIES(v1, v2)
Definition logging.h:493
#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 USE(...)
Definition macros.h:293
#define V8_EXPORT_PRIVATE
Definition macros.h:460
base::Vector< const OpIndex > arguments() const
OptionalV< FrameState > frame_state() const
const underlying_operation_t< Op > * TryCast() const
Definition operations.h:990
underlying_operation_t< Op > & Cast()
Definition operations.h:980
base::Vector< const RegisterRepresentation > outputs_rep() const
static constexpr size_t kLoopPhiBackEdgeIndex
#define V8_INLINE
Definition v8config.h:500
#define V8_UNLIKELY(condition)
Definition v8config.h:660