// Copyright 2015 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-peeling.h" #include "src/compiler/common-operator.h" #include "src/compiler/graph.h" #include "src/compiler/node-marker.h" #include "src/compiler/node-properties.h" #include "src/compiler/node.h" #include "src/zone/zone.h" // Loop peeling is an optimization that copies the body of a loop, creating // a new copy of the body called the "peeled iteration" that represents the // first iteration. Beginning with a loop as follows: // E // | A // | | (backedges) // | +---------------|---------------------------------+ // | | +-------------|-------------------------------+ | // | | | | +--------+ | | // | | | | | +----+ | | | // | | | | | | | | | | // ( Loop )<-------- ( phiA ) | | | | // | | | | | | // ((======P=================U=======|=|=====)) | | // (( | | )) | | // (( X <---------------------+ | )) | | // (( | )) | | // (( body | )) | | // (( | )) | | // (( Y <-----------------------+ )) | | // (( )) | | // ((===K====L====M==========================)) | | // | | | | | // | | +-----------------------------------------+ | // | +------------------------------------------------+ // | // exit // The body of the loop is duplicated so that all nodes considered "inside" // the loop (e.g. {P, U, X, Y, K, L, M}) have a corresponding copies in the // peeled iteration (e.g. {P', U', X', Y', K', L', M'}). What were considered // backedges of the loop correspond to edges from the peeled iteration to // the main loop body, with multiple backedges requiring a merge. // Similarly, any exits from the loop body need to be merged with "exits" // from the peeled iteration, resulting in the graph as follows: // E // | A // | | // ((=====P'================U'===============)) // (( )) // (( X'<-------------+ )) // (( | )) // (( peeled iteration | )) // (( | )) // (( Y'<-----------+ | )) // (( | | )) // ((===K'===L'====M'======|=|===============)) // | | | | | // +--------+ +-+ +-+ | | // | | | | | // | Merge <------phi // | | | // | +-----+ | // | | | (backedges) // | | +---------------|---------------------------------+ // | | | +-------------|-------------------------------+ | // | | | | | +--------+ | | // | | | | | | +----+ | | | // | | | | | | | | | | | // | ( Loop )<-------- ( phiA ) | | | | // | | | | | | | // | ((======P=================U=======|=|=====)) | | // | (( | | )) | | // | (( X <---------------------+ | )) | | // | (( | )) | | // | (( body | )) | | // | (( | )) | | // | (( Y <-----------------------+ )) | | // | (( )) | | // | ((===K====L====M==========================)) | | // | | | | | | // | | | +-----------------------------------------+ | // | | +------------------------------------------------+ // | | // | | // +----+ +-+ // | | // Merge // | // exit // Note that the boxes ((===)) above are not explicitly represented in the // graph, but are instead computed by the {LoopFinder}. namespace v8 { namespace internal { namespace compiler { struct Peeling { // Maps a node to its index in the {pairs} vector. NodeMarker<size_t> node_map; // The vector which contains the mapped nodes. NodeVector* pairs; Peeling(Graph* graph, Zone* tmp_zone, size_t max, NodeVector* p) : node_map(graph, static_cast<uint32_t>(max)), pairs(p) {} Node* map(Node* node) { if (node_map.Get(node) == 0) return node; return pairs->at(node_map.Get(node)); } void Insert(Node* original, Node* copy) { node_map.Set(original, 1 + pairs->size()); pairs->push_back(original); pairs->push_back(copy); } void CopyNodes(Graph* graph, Zone* tmp_zone, Node* dead, NodeRange nodes) { NodeVector inputs(tmp_zone); // Copy all the nodes first. for (Node* node : nodes) { inputs.clear(); for (Node* input : node->inputs()) { inputs.push_back(map(input)); } Node* copy = graph->NewNode(node->op(), node->InputCount(), &inputs[0]); if (NodeProperties::IsTyped(node)) { NodeProperties::SetType(copy, NodeProperties::GetType(node)); } Insert(node, copy); } // Fix remaining inputs of the copies. for (Node* original : nodes) { Node* copy = pairs->at(node_map.Get(original)); for (int i = 0; i < copy->InputCount(); i++) { copy->ReplaceInput(i, map(original->InputAt(i))); } } } bool Marked(Node* node) { return node_map.Get(node) > 0; } }; class PeeledIterationImpl : public PeeledIteration { public: NodeVector node_pairs_; explicit PeeledIterationImpl(Zone* zone) : node_pairs_(zone) {} }; Node* PeeledIteration::map(Node* node) { // TODO(turbofan): we use a simple linear search, since the peeled iteration // is really only used in testing. PeeledIterationImpl* impl = static_cast<PeeledIterationImpl*>(this); for (size_t i = 0; i < impl->node_pairs_.size(); i += 2) { if (impl->node_pairs_[i] == node) return impl->node_pairs_[i + 1]; } return node; } bool LoopPeeler::CanPeel(LoopTree* loop_tree, LoopTree::Loop* loop) { // Look for returns and if projections that are outside the loop but whose // control input is inside the loop. Node* loop_node = loop_tree->GetLoopControl(loop); for (Node* node : loop_tree->LoopNodes(loop)) { for (Node* use : node->uses()) { if (!loop_tree->Contains(loop, use)) { bool unmarked_exit; switch (node->opcode()) { case IrOpcode::kLoopExit: unmarked_exit = (node->InputAt(1) != loop_node); break; case IrOpcode::kLoopExitValue: case IrOpcode::kLoopExitEffect: unmarked_exit = (node->InputAt(1)->InputAt(1) != loop_node); break; default: unmarked_exit = (use->opcode() != IrOpcode::kTerminate); } if (unmarked_exit) { if (FLAG_trace_turbo_loop) { Node* loop_node = loop_tree->GetLoopControl(loop); PrintF( "Cannot peel loop %i. Loop exit without explicit mark: Node %i " "(%s) is inside " "loop, but its use %i (%s) is outside.\n", loop_node->id(), node->id(), node->op()->mnemonic(), use->id(), use->op()->mnemonic()); } return false; } } } } return true; } PeeledIteration* LoopPeeler::Peel(Graph* graph, CommonOperatorBuilder* common, LoopTree* loop_tree, LoopTree::Loop* loop, Zone* tmp_zone) { if (!CanPeel(loop_tree, loop)) return nullptr; //============================================================================ // Construct the peeled iteration. //============================================================================ PeeledIterationImpl* iter = new (tmp_zone) PeeledIterationImpl(tmp_zone); size_t estimated_peeled_size = 5 + (loop->TotalSize()) * 2; Peeling peeling(graph, tmp_zone, estimated_peeled_size, &iter->node_pairs_); Node* dead = graph->NewNode(common->Dead()); // Map the loop header nodes to their entry values. for (Node* node : loop_tree->HeaderNodes(loop)) { peeling.Insert(node, node->InputAt(kAssumedLoopEntryIndex)); } // Copy all the nodes of loop body for the peeled iteration. peeling.CopyNodes(graph, tmp_zone, dead, loop_tree->BodyNodes(loop)); //============================================================================ // Replace the entry to the loop with the output of the peeled iteration. //============================================================================ Node* loop_node = loop_tree->GetLoopControl(loop); Node* new_entry; int backedges = loop_node->InputCount() - 1; if (backedges > 1) { // Multiple backedges from original loop, therefore multiple output edges // from the peeled iteration. NodeVector inputs(tmp_zone); for (int i = 1; i < loop_node->InputCount(); i++) { inputs.push_back(peeling.map(loop_node->InputAt(i))); } Node* merge = graph->NewNode(common->Merge(backedges), backedges, &inputs[0]); // Merge values from the multiple output edges of the peeled iteration. for (Node* node : loop_tree->HeaderNodes(loop)) { if (node->opcode() == IrOpcode::kLoop) continue; // already done. inputs.clear(); for (int i = 0; i < backedges; i++) { inputs.push_back(peeling.map(node->InputAt(1 + i))); } for (Node* input : inputs) { if (input != inputs[0]) { // Non-redundant phi. inputs.push_back(merge); const Operator* op = common->ResizeMergeOrPhi(node->op(), backedges); Node* phi = graph->NewNode(op, backedges + 1, &inputs[0]); node->ReplaceInput(0, phi); break; } } } new_entry = merge; } else { // Only one backedge, simply replace the input to loop with output of // peeling. for (Node* node : loop_tree->HeaderNodes(loop)) { node->ReplaceInput(0, peeling.map(node->InputAt(1))); } new_entry = peeling.map(loop_node->InputAt(1)); } loop_node->ReplaceInput(0, new_entry); //============================================================================ // Change the exit and exit markers to merge/phi/effect-phi. //============================================================================ for (Node* exit : loop_tree->ExitNodes(loop)) { switch (exit->opcode()) { case IrOpcode::kLoopExit: // Change the loop exit node to a merge node. exit->ReplaceInput(1, peeling.map(exit->InputAt(0))); NodeProperties::ChangeOp(exit, common->Merge(2)); break; case IrOpcode::kLoopExitValue: // Change exit marker to phi. exit->InsertInput(graph->zone(), 1, peeling.map(exit->InputAt(0))); NodeProperties::ChangeOp( exit, common->Phi(MachineRepresentation::kTagged, 2)); break; case IrOpcode::kLoopExitEffect: // Change effect exit marker to effect phi. exit->InsertInput(graph->zone(), 1, peeling.map(exit->InputAt(0))); NodeProperties::ChangeOp(exit, common->EffectPhi(2)); break; default: break; } } return iter; } namespace { void PeelInnerLoops(Graph* graph, CommonOperatorBuilder* common, LoopTree* loop_tree, LoopTree::Loop* loop, Zone* temp_zone) { // If the loop has nested loops, peel inside those. if (!loop->children().empty()) { for (LoopTree::Loop* inner_loop : loop->children()) { PeelInnerLoops(graph, common, loop_tree, inner_loop, temp_zone); } return; } // Only peel small-enough loops. if (loop->TotalSize() > LoopPeeler::kMaxPeeledNodes) return; if (FLAG_trace_turbo_loop) { PrintF("Peeling loop with header: "); for (Node* node : loop_tree->HeaderNodes(loop)) { PrintF("%i ", node->id()); } PrintF("\n"); } LoopPeeler::Peel(graph, common, loop_tree, loop, temp_zone); } void EliminateLoopExit(Node* node) { DCHECK_EQ(IrOpcode::kLoopExit, node->opcode()); // The exit markers take the loop exit as input. We iterate over uses // and remove all the markers from the graph. for (Edge edge : node->use_edges()) { if (NodeProperties::IsControlEdge(edge)) { Node* marker = edge.from(); if (marker->opcode() == IrOpcode::kLoopExitValue) { NodeProperties::ReplaceUses(marker, marker->InputAt(0)); marker->Kill(); } else if (marker->opcode() == IrOpcode::kLoopExitEffect) { NodeProperties::ReplaceUses(marker, nullptr, NodeProperties::GetEffectInput(marker)); marker->Kill(); } } } NodeProperties::ReplaceUses(node, nullptr, nullptr, NodeProperties::GetControlInput(node, 0)); node->Kill(); } } // namespace // static void LoopPeeler::PeelInnerLoopsOfTree(Graph* graph, CommonOperatorBuilder* common, LoopTree* loop_tree, Zone* temp_zone) { for (LoopTree::Loop* loop : loop_tree->outer_loops()) { PeelInnerLoops(graph, common, loop_tree, loop, temp_zone); } EliminateLoopExits(graph, temp_zone); } // static void LoopPeeler::EliminateLoopExits(Graph* graph, Zone* temp_zone) { ZoneQueue<Node*> queue(temp_zone); ZoneVector<bool> visited(graph->NodeCount(), false, temp_zone); queue.push(graph->end()); while (!queue.empty()) { Node* node = queue.front(); queue.pop(); if (node->opcode() == IrOpcode::kLoopExit) { Node* control = NodeProperties::GetControlInput(node); EliminateLoopExit(node); if (!visited[control->id()]) { visited[control->id()] = true; queue.push(control); } } else { for (int i = 0; i < node->op()->ControlInputCount(); i++) { Node* control = NodeProperties::GetControlInput(node, i); if (!visited[control->id()]) { visited[control->id()] = true; queue.push(control); } } } } } } // namespace compiler } // namespace internal } // namespace v8