// Copyright 2016 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-variable-optimizer.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-containers.h"
|
#include "src/zone/zone.h"
|
|
namespace v8 {
|
namespace internal {
|
namespace compiler {
|
|
// Macro for outputting trace information from representation inference.
|
#define TRACE(...) \
|
do { \
|
if (FLAG_trace_turbo_loop) PrintF(__VA_ARGS__); \
|
} while (false)
|
|
LoopVariableOptimizer::LoopVariableOptimizer(Graph* graph,
|
CommonOperatorBuilder* common,
|
Zone* zone)
|
: graph_(graph),
|
common_(common),
|
zone_(zone),
|
limits_(graph->NodeCount(), zone),
|
reduced_(graph->NodeCount(), zone),
|
induction_vars_(zone) {}
|
|
void LoopVariableOptimizer::Run() {
|
ZoneQueue<Node*> queue(zone());
|
queue.push(graph()->start());
|
NodeMarker<bool> queued(graph(), 2);
|
while (!queue.empty()) {
|
Node* node = queue.front();
|
queue.pop();
|
queued.Set(node, false);
|
|
DCHECK(!reduced_.Get(node));
|
bool all_inputs_visited = true;
|
int inputs_end = (node->opcode() == IrOpcode::kLoop)
|
? kFirstBackedge
|
: node->op()->ControlInputCount();
|
for (int i = 0; i < inputs_end; i++) {
|
if (!reduced_.Get(NodeProperties::GetControlInput(node, i))) {
|
all_inputs_visited = false;
|
break;
|
}
|
}
|
if (!all_inputs_visited) continue;
|
|
VisitNode(node);
|
reduced_.Set(node, true);
|
|
// Queue control outputs.
|
for (Edge edge : node->use_edges()) {
|
if (NodeProperties::IsControlEdge(edge) &&
|
edge.from()->op()->ControlOutputCount() > 0) {
|
Node* use = edge.from();
|
if (use->opcode() == IrOpcode::kLoop &&
|
edge.index() != kAssumedLoopEntryIndex) {
|
VisitBackedge(node, use);
|
} else if (!queued.Get(use)) {
|
queue.push(use);
|
queued.Set(use, true);
|
}
|
}
|
}
|
}
|
}
|
|
void InductionVariable::AddUpperBound(Node* bound,
|
InductionVariable::ConstraintKind kind) {
|
if (FLAG_trace_turbo_loop) {
|
StdoutStream{} << "New upper bound for " << phi()->id() << " (loop "
|
<< NodeProperties::GetControlInput(phi())->id()
|
<< "): " << *bound << std::endl;
|
}
|
upper_bounds_.push_back(Bound(bound, kind));
|
}
|
|
void InductionVariable::AddLowerBound(Node* bound,
|
InductionVariable::ConstraintKind kind) {
|
if (FLAG_trace_turbo_loop) {
|
StdoutStream{} << "New lower bound for " << phi()->id() << " (loop "
|
<< NodeProperties::GetControlInput(phi())->id()
|
<< "): " << *bound;
|
}
|
lower_bounds_.push_back(Bound(bound, kind));
|
}
|
|
void LoopVariableOptimizer::VisitBackedge(Node* from, Node* loop) {
|
if (loop->op()->ControlInputCount() != 2) return;
|
|
// Go through the constraints, and update the induction variables in
|
// this loop if they are involved in the constraint.
|
for (Constraint constraint : limits_.Get(from)) {
|
if (constraint.left->opcode() == IrOpcode::kPhi &&
|
NodeProperties::GetControlInput(constraint.left) == loop) {
|
auto var = induction_vars_.find(constraint.left->id());
|
if (var != induction_vars_.end()) {
|
var->second->AddUpperBound(constraint.right, constraint.kind);
|
}
|
}
|
if (constraint.right->opcode() == IrOpcode::kPhi &&
|
NodeProperties::GetControlInput(constraint.right) == loop) {
|
auto var = induction_vars_.find(constraint.right->id());
|
if (var != induction_vars_.end()) {
|
var->second->AddLowerBound(constraint.left, constraint.kind);
|
}
|
}
|
}
|
}
|
|
void LoopVariableOptimizer::VisitNode(Node* node) {
|
switch (node->opcode()) {
|
case IrOpcode::kMerge:
|
return VisitMerge(node);
|
case IrOpcode::kLoop:
|
return VisitLoop(node);
|
case IrOpcode::kIfFalse:
|
return VisitIf(node, false);
|
case IrOpcode::kIfTrue:
|
return VisitIf(node, true);
|
case IrOpcode::kStart:
|
return VisitStart(node);
|
case IrOpcode::kLoopExit:
|
return VisitLoopExit(node);
|
default:
|
return VisitOtherControl(node);
|
}
|
}
|
|
void LoopVariableOptimizer::VisitMerge(Node* node) {
|
// Merge the limits of all incoming edges.
|
VariableLimits merged = limits_.Get(node->InputAt(0));
|
for (int i = 1; i < node->InputCount(); i++) {
|
merged.ResetToCommonAncestor(limits_.Get(node->InputAt(i)));
|
}
|
limits_.Set(node, merged);
|
}
|
|
void LoopVariableOptimizer::VisitLoop(Node* node) {
|
DetectInductionVariables(node);
|
// Conservatively take the limits from the loop entry here.
|
return TakeConditionsFromFirstControl(node);
|
}
|
|
void LoopVariableOptimizer::VisitIf(Node* node, bool polarity) {
|
Node* branch = node->InputAt(0);
|
Node* cond = branch->InputAt(0);
|
VariableLimits limits = limits_.Get(branch);
|
// Normalize to less than comparison.
|
switch (cond->opcode()) {
|
case IrOpcode::kJSLessThan:
|
case IrOpcode::kSpeculativeNumberLessThan:
|
AddCmpToLimits(&limits, cond, InductionVariable::kStrict, polarity);
|
break;
|
case IrOpcode::kJSGreaterThan:
|
AddCmpToLimits(&limits, cond, InductionVariable::kNonStrict, !polarity);
|
break;
|
case IrOpcode::kJSLessThanOrEqual:
|
case IrOpcode::kSpeculativeNumberLessThanOrEqual:
|
AddCmpToLimits(&limits, cond, InductionVariable::kNonStrict, polarity);
|
break;
|
case IrOpcode::kJSGreaterThanOrEqual:
|
AddCmpToLimits(&limits, cond, InductionVariable::kStrict, !polarity);
|
break;
|
default:
|
break;
|
}
|
limits_.Set(node, limits);
|
}
|
|
void LoopVariableOptimizer::AddCmpToLimits(
|
VariableLimits* limits, Node* node, InductionVariable::ConstraintKind kind,
|
bool polarity) {
|
Node* left = node->InputAt(0);
|
Node* right = node->InputAt(1);
|
if (FindInductionVariable(left) || FindInductionVariable(right)) {
|
if (polarity) {
|
limits->PushFront(Constraint{left, kind, right}, zone());
|
} else {
|
kind = (kind == InductionVariable::kStrict)
|
? InductionVariable::kNonStrict
|
: InductionVariable::kStrict;
|
limits->PushFront(Constraint{right, kind, left}, zone());
|
}
|
}
|
}
|
|
void LoopVariableOptimizer::VisitStart(Node* node) { limits_.Set(node, {}); }
|
|
void LoopVariableOptimizer::VisitLoopExit(Node* node) {
|
return TakeConditionsFromFirstControl(node);
|
}
|
|
void LoopVariableOptimizer::VisitOtherControl(Node* node) {
|
DCHECK_EQ(1, node->op()->ControlInputCount());
|
return TakeConditionsFromFirstControl(node);
|
}
|
|
void LoopVariableOptimizer::TakeConditionsFromFirstControl(Node* node) {
|
limits_.Set(node, limits_.Get(NodeProperties::GetControlInput(node, 0)));
|
}
|
|
const InductionVariable* LoopVariableOptimizer::FindInductionVariable(
|
Node* node) {
|
auto var = induction_vars_.find(node->id());
|
if (var != induction_vars_.end()) {
|
return var->second;
|
}
|
return nullptr;
|
}
|
|
InductionVariable* LoopVariableOptimizer::TryGetInductionVariable(Node* phi) {
|
DCHECK_EQ(2, phi->op()->ValueInputCount());
|
Node* loop = NodeProperties::GetControlInput(phi);
|
DCHECK_EQ(IrOpcode::kLoop, loop->opcode());
|
Node* initial = phi->InputAt(0);
|
Node* arith = phi->InputAt(1);
|
InductionVariable::ArithmeticType arithmeticType;
|
if (arith->opcode() == IrOpcode::kJSAdd ||
|
arith->opcode() == IrOpcode::kSpeculativeNumberAdd ||
|
arith->opcode() == IrOpcode::kSpeculativeSafeIntegerAdd) {
|
arithmeticType = InductionVariable::ArithmeticType::kAddition;
|
} else if (arith->opcode() == IrOpcode::kJSSubtract ||
|
arith->opcode() == IrOpcode::kSpeculativeNumberSubtract ||
|
arith->opcode() == IrOpcode::kSpeculativeSafeIntegerSubtract) {
|
arithmeticType = InductionVariable::ArithmeticType::kSubtraction;
|
} else {
|
return nullptr;
|
}
|
|
// TODO(jarin) Support both sides.
|
Node* input = arith->InputAt(0);
|
if (input->opcode() == IrOpcode::kSpeculativeToNumber ||
|
input->opcode() == IrOpcode::kJSToNumber ||
|
input->opcode() == IrOpcode::kJSToNumberConvertBigInt) {
|
input = input->InputAt(0);
|
}
|
if (input != phi) return nullptr;
|
|
Node* effect_phi = nullptr;
|
for (Node* use : loop->uses()) {
|
if (use->opcode() == IrOpcode::kEffectPhi) {
|
DCHECK_NULL(effect_phi);
|
effect_phi = use;
|
}
|
}
|
if (!effect_phi) return nullptr;
|
|
Node* incr = arith->InputAt(1);
|
return new (zone()) InductionVariable(phi, effect_phi, arith, incr, initial,
|
zone(), arithmeticType);
|
}
|
|
void LoopVariableOptimizer::DetectInductionVariables(Node* loop) {
|
if (loop->op()->ControlInputCount() != 2) return;
|
TRACE("Loop variables for loop %i:", loop->id());
|
for (Edge edge : loop->use_edges()) {
|
if (NodeProperties::IsControlEdge(edge) &&
|
edge.from()->opcode() == IrOpcode::kPhi) {
|
Node* phi = edge.from();
|
InductionVariable* induction_var = TryGetInductionVariable(phi);
|
if (induction_var) {
|
induction_vars_[phi->id()] = induction_var;
|
TRACE(" %i", induction_var->phi()->id());
|
}
|
}
|
}
|
TRACE("\n");
|
}
|
|
void LoopVariableOptimizer::ChangeToInductionVariablePhis() {
|
for (auto entry : induction_vars_) {
|
// It only make sense to analyze the induction variables if
|
// there is a bound.
|
InductionVariable* induction_var = entry.second;
|
DCHECK_EQ(MachineRepresentation::kTagged,
|
PhiRepresentationOf(induction_var->phi()->op()));
|
if (induction_var->upper_bounds().size() == 0 &&
|
induction_var->lower_bounds().size() == 0) {
|
continue;
|
}
|
// Insert the increment value to the value inputs.
|
induction_var->phi()->InsertInput(graph()->zone(),
|
induction_var->phi()->InputCount() - 1,
|
induction_var->increment());
|
// Insert the bound inputs to the value inputs.
|
for (auto bound : induction_var->lower_bounds()) {
|
induction_var->phi()->InsertInput(
|
graph()->zone(), induction_var->phi()->InputCount() - 1, bound.bound);
|
}
|
for (auto bound : induction_var->upper_bounds()) {
|
induction_var->phi()->InsertInput(
|
graph()->zone(), induction_var->phi()->InputCount() - 1, bound.bound);
|
}
|
NodeProperties::ChangeOp(
|
induction_var->phi(),
|
common()->InductionVariablePhi(induction_var->phi()->InputCount() - 1));
|
}
|
}
|
|
void LoopVariableOptimizer::ChangeToPhisAndInsertGuards() {
|
for (auto entry : induction_vars_) {
|
InductionVariable* induction_var = entry.second;
|
if (induction_var->phi()->opcode() == IrOpcode::kInductionVariablePhi) {
|
// Turn the induction variable phi back to normal phi.
|
int value_count = 2;
|
Node* control = NodeProperties::GetControlInput(induction_var->phi());
|
DCHECK_EQ(value_count, control->op()->ControlInputCount());
|
induction_var->phi()->TrimInputCount(value_count + 1);
|
induction_var->phi()->ReplaceInput(value_count, control);
|
NodeProperties::ChangeOp(
|
induction_var->phi(),
|
common()->Phi(MachineRepresentation::kTagged, value_count));
|
|
// If the backedge is not a subtype of the phi's type, we insert a sigma
|
// to get the typing right.
|
Node* backedge_value = induction_var->phi()->InputAt(1);
|
Type backedge_type = NodeProperties::GetType(backedge_value);
|
Type phi_type = NodeProperties::GetType(induction_var->phi());
|
if (!backedge_type.Is(phi_type)) {
|
Node* loop = NodeProperties::GetControlInput(induction_var->phi());
|
Node* backedge_control = loop->InputAt(1);
|
Node* backedge_effect =
|
NodeProperties::GetEffectInput(induction_var->effect_phi(), 1);
|
Node* rename =
|
graph()->NewNode(common()->TypeGuard(phi_type), backedge_value,
|
backedge_effect, backedge_control);
|
induction_var->effect_phi()->ReplaceInput(1, rename);
|
induction_var->phi()->ReplaceInput(1, rename);
|
}
|
}
|
}
|
}
|
|
#undef TRACE
|
|
} // namespace compiler
|
} // namespace internal
|
} // namespace v8
|