| // Copyright 2021 the V8 project authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/compiler/loop-unrolling.h" |
| |
| #include "src/base/small-vector.h" |
| #include "src/codegen/tick-counter.h" |
| #include "src/compiler/common-operator.h" |
| #include "src/compiler/loop-analysis.h" |
| #include "src/compiler/loop-peeling.h" |
| |
| namespace v8 { |
| namespace internal { |
| namespace compiler { |
| |
| void UnrollLoop(Node* loop_node, ZoneUnorderedSet<Node*>* loop, uint32_t depth, |
| Graph* graph, CommonOperatorBuilder* common, Zone* tmp_zone, |
| SourcePositionTable* source_positions, |
| NodeOriginTable* node_origins) { |
| DCHECK_EQ(loop_node->opcode(), IrOpcode::kLoop); |
| |
| if (loop == nullptr) return; |
| // No back-jump to the loop header means this is not really a loop. |
| if (loop_node->InputCount() < 2) return; |
| |
| uint32_t unrolling_count = |
| unrolling_count_heuristic(static_cast<uint32_t>(loop->size()), depth); |
| if (unrolling_count == 0) return; |
| |
| uint32_t iteration_count = unrolling_count + 1; |
| |
| uint32_t copied_size = static_cast<uint32_t>(loop->size()) * iteration_count; |
| |
| NodeVector copies(tmp_zone); |
| |
| NodeCopier copier(graph, copied_size, &copies, unrolling_count); |
| { |
| std::vector<Node*> loop_nodes(loop->begin(), loop->end()); |
| copier.CopyNodes( |
| graph, tmp_zone, graph->NewNode(common->Dead()), |
| NodeRange(loop_nodes.data(), loop_nodes.data() + loop_nodes.size()), |
| source_positions, node_origins); |
| } |
| |
| #define COPY(node, n) copier.map(node, n) |
| #define FOREACH_COPY_INDEX(i) for (uint32_t i = 0; i < unrolling_count; i++) |
| |
| for (Node* node : *loop) { |
| switch (node->opcode()) { |
| case IrOpcode::kStackPointerGreaterThan: { |
| /*** Step 1: Remove stack checks from all but the first iteration of the |
| loop. ***/ |
| for (Edge edge : node->use_edges()) { |
| if (edge.from()->opcode() == IrOpcode::kBranch) { |
| FOREACH_COPY_INDEX(i) { |
| COPY(edge.from(), i) |
| ->ReplaceInput(0, graph->NewNode(common->Int32Constant(1))); |
| } |
| } else if (edge.from()->opcode() == IrOpcode::kEffectPhi) { |
| // We now need to remove stack check and the related function call |
| // from the effect chain. |
| // The effect chain looks like this (* stand for irrelevant nodes): |
| // |
| // replacing effect (effect before stack check) |
| // * * | * |
| // | | | | |
| // ( Load ) |
| // * * | * |
| // | | | | |
| // ( Load ) |
| // | | |
| // stack check |
| // | * | * |
| // | | | | |
| // | (call) |
| // | | * |
| // | | | |
| // stack check effect (that we need to replace) |
| Node* stack_check_effect = edge.from(); |
| DCHECK_EQ(edge.index(), 0); |
| DCHECK_EQ(stack_check_effect->InputAt(1)->opcode(), |
| IrOpcode::kCall); |
| DCHECK_EQ(stack_check_effect->InputAt(1)->InputAt(1), node); |
| DCHECK_EQ(node->InputAt(1)->opcode(), IrOpcode::kLoad); |
| DCHECK_EQ(node->InputAt(1)->InputAt(2)->opcode(), IrOpcode::kLoad); |
| Node* replacing_effect = node->InputAt(1)->InputAt(2)->InputAt(2); |
| FOREACH_COPY_INDEX(i) { |
| COPY(stack_check_effect, i) |
| ->ReplaceUses(COPY(replacing_effect, i)); |
| } |
| } |
| } |
| break; |
| } |
| |
| case IrOpcode::kLoopExit: { |
| /*** Step 2: Create merges for loop exits. ***/ |
| if (node->InputAt(1) == loop_node) { |
| // Create a merge node from all iteration exits. |
| Node** merge_inputs = tmp_zone->NewArray<Node*>(iteration_count); |
| merge_inputs[0] = node; |
| for (uint32_t i = 1; i < iteration_count; i++) { |
| merge_inputs[i] = COPY(node, i - 1); |
| } |
| Node* merge_node = graph->NewNode(common->Merge(iteration_count), |
| iteration_count, merge_inputs); |
| // Replace all uses of the loop exit with the merge node. |
| for (Edge use_edge : node->use_edges()) { |
| Node* use = use_edge.from(); |
| if (loop->count(use) == 1) { |
| // Uses within the loop will be LoopExitEffects and |
| // LoopExitValues. We need to create a phi from all loop |
| // iterations. Its merge will be the merge node for LoopExits. |
| const Operator* phi_operator; |
| if (use->opcode() == IrOpcode::kLoopExitEffect) { |
| phi_operator = common->EffectPhi(iteration_count); |
| } else { |
| DCHECK(use->opcode() == IrOpcode::kLoopExitValue); |
| phi_operator = common->Phi( |
| LoopExitValueRepresentationOf(use->op()), iteration_count); |
| } |
| Node** phi_inputs = |
| tmp_zone->NewArray<Node*>(iteration_count + 1); |
| phi_inputs[0] = use; |
| for (uint32_t i = 1; i < iteration_count; i++) { |
| phi_inputs[i] = COPY(use, i - 1); |
| } |
| phi_inputs[iteration_count] = merge_node; |
| Node* phi = |
| graph->NewNode(phi_operator, iteration_count + 1, phi_inputs); |
| use->ReplaceUses(phi); |
| // Repair phi which we just broke. |
| phi->ReplaceInput(0, use); |
| } else if (use != merge_node) { |
| // For uses outside the loop, simply redirect them to the merge. |
| use->ReplaceInput(use_edge.index(), merge_node); |
| } |
| } |
| } |
| break; |
| } |
| |
| default: |
| break; |
| } |
| } |
| |
| /*** Step 3: Rewire the iterations of the loop. Each iteration should flow |
| into the next one, and the last should flow into the first. ***/ |
| |
| // 3a) Rewire control. |
| |
| // We start at index=1 assuming that index=0 is the (non-recursive) loop |
| // entry. |
| for (int input_index = 1; input_index < loop_node->InputCount(); |
| input_index++) { |
| Node* last_iteration_input = |
| COPY(loop_node, unrolling_count - 1)->InputAt(input_index); |
| for (uint32_t copy_index = unrolling_count - 1; copy_index > 0; |
| copy_index--) { |
| COPY(loop_node, copy_index) |
| ->ReplaceInput(input_index, |
| COPY(loop_node, copy_index - 1)->InputAt(input_index)); |
| } |
| COPY(loop_node, 0) |
| ->ReplaceInput(input_index, loop_node->InputAt(input_index)); |
| loop_node->ReplaceInput(input_index, last_iteration_input); |
| } |
| // The loop of each following iteration will become a merge. We need to remove |
| // its non-recursive input. |
| FOREACH_COPY_INDEX(i) { |
| COPY(loop_node, i)->RemoveInput(0); |
| NodeProperties::ChangeOp(COPY(loop_node, i), |
| common->Merge(loop_node->InputCount() - 1)); |
| } |
| |
| // 3b) Rewire phis and loop exits. |
| for (Node* use : loop_node->uses()) { |
| if (NodeProperties::IsPhi(use)) { |
| int count = use->opcode() == IrOpcode::kPhi |
| ? use->op()->ValueInputCount() |
| : use->op()->EffectInputCount(); |
| // Phis depending on the loop header should take their input from the |
| // previous iteration instead. |
| for (int input_index = 1; input_index < count; input_index++) { |
| Node* last_iteration_input = |
| COPY(use, unrolling_count - 1)->InputAt(input_index); |
| for (uint32_t copy_index = unrolling_count - 1; copy_index > 0; |
| copy_index--) { |
| COPY(use, copy_index) |
| ->ReplaceInput(input_index, |
| COPY(use, copy_index - 1)->InputAt(input_index)); |
| } |
| COPY(use, 0)->ReplaceInput(input_index, use->InputAt(input_index)); |
| use->ReplaceInput(input_index, last_iteration_input); |
| } |
| |
| // Phis in each following iteration should not depend on the |
| // (non-recursive) entry to the loop. Remove their first input. |
| FOREACH_COPY_INDEX(i) { |
| COPY(use, i)->RemoveInput(0); |
| NodeProperties::ChangeOp( |
| COPY(use, i), common->ResizeMergeOrPhi(use->op(), count - 1)); |
| } |
| } |
| |
| // Loop exits should point to the loop header. |
| if (use->opcode() == IrOpcode::kLoopExit) { |
| FOREACH_COPY_INDEX(i) { COPY(use, i)->ReplaceInput(1, loop_node); } |
| } |
| } |
| } |
| |
| #undef COPY |
| #undef FOREACH_COPY_INDEX |
| |
| } // namespace compiler |
| } // namespace internal |
| } // namespace v8 |