// 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),
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_NULL(limits_[node->id()]);
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 (limits_[NodeProperties::GetControlInput(node, i)->id()] == nullptr) {
all_inputs_visited = false;
break;
}
}
if (!all_inputs_visited) continue;
VisitNode(node);
DCHECK_NOT_NULL(limits_[node->id()]);
// 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);
}
}
}
}
}
class LoopVariableOptimizer::Constraint : public ZoneObject {
public:
InductionVariable::ConstraintKind kind() const { return kind_; }
Node* left() const { return left_; }
Node* right() const { return right_; }
const Constraint* next() const { return next_; }
Constraint(Node* left, InductionVariable::ConstraintKind kind, Node* right,
const Constraint* next)
: left_(left), right_(right), kind_(kind), next_(next) {}
private:
Node* left_;
Node* right_;
InductionVariable::ConstraintKind kind_;
const Constraint* next_;
};
class LoopVariableOptimizer::VariableLimits : public ZoneObject {
public:
static VariableLimits* Empty(Zone* zone) {
return new (zone) VariableLimits();
}
VariableLimits* Copy(Zone* zone) const {
return new (zone) VariableLimits(this);
}
void Add(Node* left, InductionVariable::ConstraintKind kind, Node* right,
Zone* zone) {
head_ = new (zone) Constraint(left, kind, right, head_);
limit_count_++;
}
void Merge(const VariableLimits* other) {
// Change the current condition list to a longest common tail
// of this condition list and the other list. (The common tail
// should correspond to the list from the common dominator.)
// First, we throw away the prefix of the longer list, so that
// we have lists of the same length.
size_t other_size = other->limit_count_;
const Constraint* other_limit = other->head_;
while (other_size > limit_count_) {
other_limit = other_limit->next();
other_size--;
}
while (limit_count_ > other_size) {
head_ = head_->next();
limit_count_--;
}
// Then we go through both lists in lock-step until we find
// the common tail.
while (head_ != other_limit) {
DCHECK(limit_count_ > 0);
limit_count_--;
other_limit = other_limit->next();
head_ = head_->next();
}
}
const Constraint* head() const { return head_; }
private:
VariableLimits() {}
explicit VariableLimits(const VariableLimits* other)
: head_(other->head_), limit_count_(other->limit_count_) {}
const Constraint* head_ = nullptr;
size_t limit_count_ = 0;
};
void InductionVariable::AddUpperBound(Node* bound,
InductionVariable::ConstraintKind kind) {
if (FLAG_trace_turbo_loop) {
OFStream os(stdout);
os << "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) {
OFStream os(stdout);
os << "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.
const VariableLimits* limits = limits_[from->id()];
for (const Constraint* constraint = limits->head(); constraint != nullptr;
constraint = constraint->next()) {
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_[node->InputAt(0)->id()]->Copy(zone());
for (int i = 1; i < node->InputCount(); i++) {
merged->Merge(limits_[node->InputAt(i)->id()]);
}
limits_[node->id()] = 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_[branch->id()]->Copy(zone());
// Normalize to less than comparison.
switch (cond->opcode()) {
case IrOpcode::kJSLessThan:
AddCmpToLimits(limits, cond, InductionVariable::kStrict, polarity);
break;
case IrOpcode::kJSGreaterThan:
AddCmpToLimits(limits, cond, InductionVariable::kNonStrict, !polarity);
break;
case IrOpcode::kJSLessThanOrEqual:
AddCmpToLimits(limits, cond, InductionVariable::kNonStrict, polarity);
break;
case IrOpcode::kJSGreaterThanOrEqual:
AddCmpToLimits(limits, cond, InductionVariable::kStrict, !polarity);
break;
default:
break;
}
limits_[node->id()] = 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->Add(left, kind, right, zone());
} else {
kind = (kind == InductionVariable::kStrict)
? InductionVariable::kNonStrict
: InductionVariable::kStrict;
limits->Add(right, kind, left, zone());
}
}
}
void LoopVariableOptimizer::VisitStart(Node* node) {
limits_[node->id()] = VariableLimits::Empty(zone());
}
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) {
const VariableLimits* limits =
limits_[NodeProperties::GetControlInput(node, 0)->id()];
DCHECK_NOT_NULL(limits);
limits_[node->id()] = limits;
}
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());
DCHECK_EQ(IrOpcode::kLoop, NodeProperties::GetControlInput(phi)->opcode());
Node* initial = phi->InputAt(0);
Node* arith = phi->InputAt(1);
InductionVariable::ArithmeticType arithmeticType;
if (arith->opcode() == IrOpcode::kJSAdd) {
arithmeticType = InductionVariable::ArithmeticType::kAddition;
} else if (arith->opcode() == IrOpcode::kJSSubtract) {
arithmeticType = InductionVariable::ArithmeticType::kSubtraction;
} else {
return nullptr;
}
// TODO(jarin) Support both sides.
if (arith->InputAt(0) != phi) {
if (arith->InputAt(0)->opcode() != IrOpcode::kJSToNumber ||
arith->InputAt(0)->InputAt(0) != phi) {
return nullptr;
}
}
Node* incr = arith->InputAt(1);
return new (zone())
InductionVariable(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* backedge_control =
NodeProperties::GetControlInput(induction_var->phi())->InputAt(1);
Node* rename = graph()->NewNode(common()->TypeGuard(phi_type),
backedge_value, backedge_control);
induction_var->phi()->ReplaceInput(1, rename);
}
}
}
}
} // namespace compiler
} // namespace internal
} // namespace v8