[turbofan] Induction variable bound analysis.
The new phase will detect loop variable, infer bounds and bake them into the type. Review-Url: https://codereview.chromium.org/2164263003 Cr-Commit-Position: refs/heads/master@{#38077}
This commit is contained in:
parent
3deb71fbad
commit
398a114357
3
BUILD.gn
3
BUILD.gn
@ -1057,6 +1057,9 @@ v8_source_set("v8_base") {
|
||||
"src/compiler/loop-analysis.cc",
|
||||
"src/compiler/loop-analysis.h",
|
||||
"src/compiler/loop-peeling.cc",
|
||||
"src/compiler/loop-peeling.h",
|
||||
"src/compiler/loop-variable-optimizer.cc",
|
||||
"src/compiler/loop-variable-optimizer.h",
|
||||
"src/compiler/machine-operator-reducer.cc",
|
||||
"src/compiler/machine-operator-reducer.h",
|
||||
"src/compiler/machine-operator.cc",
|
||||
|
@ -276,6 +276,11 @@ std::ostream& operator<<(std::ostream& os,
|
||||
V(5) \
|
||||
V(6)
|
||||
|
||||
#define CACHED_INDUCTION_VARIABLE_PHI_LIST(V) \
|
||||
V(4) \
|
||||
V(5) \
|
||||
V(6) \
|
||||
V(7)
|
||||
|
||||
#define CACHED_LOOP_LIST(V) \
|
||||
V(1) \
|
||||
@ -473,6 +478,20 @@ struct CommonOperatorGlobalCache final {
|
||||
CACHED_PHI_LIST(CACHED_PHI)
|
||||
#undef CACHED_PHI
|
||||
|
||||
template <int kInputCount>
|
||||
struct InductionVariablePhiOperator final : public Operator {
|
||||
InductionVariablePhiOperator()
|
||||
: Operator( //--
|
||||
IrOpcode::kInductionVariablePhi, Operator::kPure, // opcode
|
||||
"InductionVariablePhi", // name
|
||||
kInputCount, 0, 1, 1, 0, 0) {} // counts
|
||||
};
|
||||
#define CACHED_INDUCTION_VARIABLE_PHI(input_count) \
|
||||
InductionVariablePhiOperator<input_count> \
|
||||
kInductionVariablePhi##input_count##Operator;
|
||||
CACHED_INDUCTION_VARIABLE_PHI_LIST(CACHED_INDUCTION_VARIABLE_PHI)
|
||||
#undef CACHED_INDUCTION_VARIABLE_PHI
|
||||
|
||||
template <int kIndex>
|
||||
struct ParameterOperator final : public Operator1<ParameterInfo> {
|
||||
ParameterOperator()
|
||||
@ -853,6 +872,25 @@ const Operator* CommonOperatorBuilder::EffectPhi(int effect_input_count) {
|
||||
0, effect_input_count, 1, 0, 1, 0); // counts
|
||||
}
|
||||
|
||||
const Operator* CommonOperatorBuilder::InductionVariablePhi(int input_count) {
|
||||
DCHECK(input_count >= 4); // There must be always the entry, backedge,
|
||||
// increment and at least one bound.
|
||||
switch (input_count) {
|
||||
#define CACHED_INDUCTION_VARIABLE_PHI(input_count) \
|
||||
case input_count: \
|
||||
return &cache_.kInductionVariablePhi##input_count##Operator;
|
||||
CACHED_INDUCTION_VARIABLE_PHI_LIST(CACHED_INDUCTION_VARIABLE_PHI)
|
||||
#undef CACHED_INDUCTION_VARIABLE_PHI
|
||||
default:
|
||||
break;
|
||||
}
|
||||
// Uncached.
|
||||
return new (zone()) Operator( // --
|
||||
IrOpcode::kInductionVariablePhi, Operator::kPure, // opcode
|
||||
"InductionVariablePhi", // name
|
||||
input_count, 0, 1, 1, 0, 0); // counts
|
||||
}
|
||||
|
||||
const Operator* CommonOperatorBuilder::BeginRegion(
|
||||
RegionObservability region_observability) {
|
||||
switch (region_observability) {
|
||||
|
@ -232,6 +232,7 @@ class CommonOperatorBuilder final : public ZoneObject {
|
||||
const Operator* Phi(MachineRepresentation representation,
|
||||
int value_input_count);
|
||||
const Operator* EffectPhi(int effect_input_count);
|
||||
const Operator* InductionVariablePhi(int value_input_count);
|
||||
const Operator* LoopExit();
|
||||
const Operator* LoopExitValue();
|
||||
const Operator* LoopExitEffect();
|
||||
|
@ -509,7 +509,7 @@ LoopTree* LoopFinder::BuildLoopTree(Graph* graph, Zone* zone) {
|
||||
new (graph->zone()) LoopTree(graph->NodeCount(), graph->zone());
|
||||
LoopFinderImpl finder(graph, loop_tree, zone);
|
||||
finder.Run();
|
||||
if (FLAG_trace_turbo_graph) {
|
||||
if (FLAG_trace_turbo_loop) {
|
||||
finder.Print();
|
||||
}
|
||||
return loop_tree;
|
||||
|
@ -186,7 +186,7 @@ bool LoopPeeler::CanPeel(LoopTree* loop_tree, LoopTree::Loop* loop) {
|
||||
unmarked_exit = (use->opcode() != IrOpcode::kTerminate);
|
||||
}
|
||||
if (unmarked_exit) {
|
||||
if (FLAG_trace_turbo_graph) {
|
||||
if (FLAG_trace_turbo_loop) {
|
||||
Node* loop_node = loop_tree->GetLoopControl(loop);
|
||||
PrintF(
|
||||
"Cannot peel loop %i. Loop exit without explicit mark: Node %i "
|
||||
@ -312,11 +312,12 @@ void PeelInnerLoops(Graph* graph, CommonOperatorBuilder* common,
|
||||
}
|
||||
// Only peel small-enough loops.
|
||||
if (loop->TotalSize() > LoopPeeler::kMaxPeeledNodes) return;
|
||||
if (FLAG_trace_turbo_graph) {
|
||||
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);
|
||||
|
390
src/compiler/loop-variable-optimizer.cc
Normal file
390
src/compiler/loop-variable-optimizer.cc
Normal file
@ -0,0 +1,390 @@
|
||||
// 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-containers.h"
|
||||
#include "src/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_(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(limits_.find(node->id()) == limits_.end());
|
||||
bool all_inputs_visited = true;
|
||||
int inputs_end = (node->opcode() == IrOpcode::kLoop)
|
||||
? kFirstBackedge
|
||||
: node->op()->ControlInputCount();
|
||||
for (int i = 0; i < inputs_end; i++) {
|
||||
auto input = limits_.find(NodeProperties::GetControlInput(node, i)->id());
|
||||
if (input == limits_.end()) {
|
||||
all_inputs_visited = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!all_inputs_visited) continue;
|
||||
|
||||
VisitNode(node);
|
||||
DCHECK(limits_.find(node->id()) != limits_.end());
|
||||
|
||||
// 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,
|
||||
Zone* graph_zone) {
|
||||
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,
|
||||
Zone* graph_zone) {
|
||||
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(),
|
||||
graph()->zone());
|
||||
}
|
||||
}
|
||||
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->AddUpperBound(constraint->left(), constraint->kind(),
|
||||
graph()->zone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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(0)->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);
|
||||
// TODO(jarin) Support subtraction.
|
||||
if (arith->opcode() != IrOpcode::kJSAdd) {
|
||||
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());
|
||||
}
|
||||
|
||||
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::ChangeFromInductionVariablePhis() {
|
||||
for (auto entry : induction_vars_) {
|
||||
InductionVariable* induction_var = entry.second;
|
||||
if (induction_var->phi()->opcode() == IrOpcode::kInductionVariablePhi) {
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace compiler
|
||||
} // namespace internal
|
||||
} // namespace v8
|
112
src/compiler/loop-variable-optimizer.h
Normal file
112
src/compiler/loop-variable-optimizer.h
Normal file
@ -0,0 +1,112 @@
|
||||
// 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.
|
||||
|
||||
#ifndef V8_COMPILER_LOOP_VARIABLE_OPTIMIZER_H_
|
||||
#define V8_COMPILER_LOOP_VARIABLE_OPTIMIZER_H_
|
||||
|
||||
#include "src/zone-containers.h"
|
||||
|
||||
namespace v8 {
|
||||
namespace internal {
|
||||
namespace compiler {
|
||||
|
||||
class CommonOperatorBuilder;
|
||||
class Graph;
|
||||
class Node;
|
||||
|
||||
class InductionVariable : public ZoneObject {
|
||||
public:
|
||||
Node* phi() const { return phi_; }
|
||||
Node* arith() const { return arith_; }
|
||||
Node* increment() const { return increment_; }
|
||||
Node* init_value() const { return init_value_; }
|
||||
|
||||
enum ConstraintKind { kStrict, kNonStrict };
|
||||
struct Bound {
|
||||
Bound(Node* bound, ConstraintKind kind) : bound(bound), kind(kind) {}
|
||||
|
||||
Node* bound;
|
||||
ConstraintKind kind;
|
||||
};
|
||||
|
||||
const ZoneVector<Bound>& lower_bounds() { return lower_bounds_; }
|
||||
const ZoneVector<Bound>& upper_bounds() { return upper_bounds_; }
|
||||
|
||||
private:
|
||||
friend class LoopVariableOptimizer;
|
||||
|
||||
InductionVariable(Node* phi, Node* arith, Node* increment, Node* init_value,
|
||||
Zone* zone)
|
||||
: phi_(phi),
|
||||
arith_(arith),
|
||||
increment_(increment),
|
||||
init_value_(init_value),
|
||||
lower_bounds_(zone),
|
||||
upper_bounds_(zone) {}
|
||||
|
||||
void AddUpperBound(Node* bound, ConstraintKind kind, Zone* graph_zone);
|
||||
void AddLowerBound(Node* bound, ConstraintKind kind, Zone* graph_zone);
|
||||
|
||||
Node* phi_;
|
||||
Node* arith_;
|
||||
Node* increment_;
|
||||
Node* init_value_;
|
||||
ZoneVector<Bound> lower_bounds_;
|
||||
ZoneVector<Bound> upper_bounds_;
|
||||
};
|
||||
|
||||
class LoopVariableOptimizer {
|
||||
public:
|
||||
void Run();
|
||||
|
||||
LoopVariableOptimizer(Graph* graph, CommonOperatorBuilder* common,
|
||||
Zone* zone);
|
||||
|
||||
const ZoneMap<int, InductionVariable*>& induction_variables() {
|
||||
return induction_vars_;
|
||||
}
|
||||
|
||||
void ChangeToInductionVariablePhis();
|
||||
void ChangeFromInductionVariablePhis();
|
||||
|
||||
private:
|
||||
const int kAssumedLoopEntryIndex = 0;
|
||||
const int kFirstBackedge = 1;
|
||||
|
||||
class Constraint;
|
||||
class VariableLimits;
|
||||
|
||||
void VisitBackedge(Node* from, Node* loop);
|
||||
void VisitNode(Node* node);
|
||||
void VisitMerge(Node* node);
|
||||
void VisitLoop(Node* node);
|
||||
void VisitIf(Node* node, bool polarity);
|
||||
void VisitStart(Node* node);
|
||||
void VisitLoopExit(Node* node);
|
||||
void VisitOtherControl(Node* node);
|
||||
|
||||
void AddCmpToLimits(VariableLimits* limits, Node* node,
|
||||
InductionVariable::ConstraintKind kind, bool polarity);
|
||||
|
||||
void TakeConditionsFromFirstControl(Node* node);
|
||||
const InductionVariable* FindInductionVariable(Node* node);
|
||||
InductionVariable* TryGetInductionVariable(Node* phi);
|
||||
void DetectInductionVariables(Node* loop);
|
||||
|
||||
Graph* graph() { return graph_; }
|
||||
CommonOperatorBuilder* common() { return common_; }
|
||||
Zone* zone() { return zone_; }
|
||||
|
||||
Graph* graph_;
|
||||
CommonOperatorBuilder* common_;
|
||||
Zone* zone_;
|
||||
ZoneMap<int, const VariableLimits*> limits_;
|
||||
ZoneMap<int, InductionVariable*> induction_vars_;
|
||||
};
|
||||
|
||||
} // namespace compiler
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
||||
#endif // V8_COMPILER_LOOP_VARIABLE_OPTIMIZER_H_
|
@ -43,23 +43,24 @@
|
||||
V(RelocatableInt32Constant) \
|
||||
V(RelocatableInt64Constant)
|
||||
|
||||
#define INNER_OP_LIST(V) \
|
||||
V(Select) \
|
||||
V(Phi) \
|
||||
V(EffectPhi) \
|
||||
V(Checkpoint) \
|
||||
V(BeginRegion) \
|
||||
V(FinishRegion) \
|
||||
V(FrameState) \
|
||||
V(StateValues) \
|
||||
V(TypedStateValues) \
|
||||
V(ObjectState) \
|
||||
V(Call) \
|
||||
V(Parameter) \
|
||||
V(OsrValue) \
|
||||
V(LoopExit) \
|
||||
V(LoopExitValue) \
|
||||
V(LoopExitEffect) \
|
||||
#define INNER_OP_LIST(V) \
|
||||
V(Select) \
|
||||
V(Phi) \
|
||||
V(EffectPhi) \
|
||||
V(InductionVariablePhi) \
|
||||
V(Checkpoint) \
|
||||
V(BeginRegion) \
|
||||
V(FinishRegion) \
|
||||
V(FrameState) \
|
||||
V(StateValues) \
|
||||
V(TypedStateValues) \
|
||||
V(ObjectState) \
|
||||
V(Call) \
|
||||
V(Parameter) \
|
||||
V(OsrValue) \
|
||||
V(LoopExit) \
|
||||
V(LoopExitValue) \
|
||||
V(LoopExitEffect) \
|
||||
V(Projection)
|
||||
|
||||
#define COMMON_OP_LIST(V) \
|
||||
|
@ -45,6 +45,7 @@
|
||||
#include "src/compiler/load-elimination.h"
|
||||
#include "src/compiler/loop-analysis.h"
|
||||
#include "src/compiler/loop-peeling.h"
|
||||
#include "src/compiler/loop-variable-optimizer.h"
|
||||
#include "src/compiler/machine-operator-reducer.h"
|
||||
#include "src/compiler/memory-optimizer.h"
|
||||
#include "src/compiler/move-optimizer.h"
|
||||
@ -834,7 +835,10 @@ struct TyperPhase {
|
||||
void Run(PipelineData* data, Zone* temp_zone, Typer* typer) {
|
||||
NodeVector roots(temp_zone);
|
||||
data->jsgraph()->GetCachedNodes(&roots);
|
||||
typer->Run(roots);
|
||||
LoopVariableOptimizer induction_vars(data->jsgraph()->graph(),
|
||||
data->common(), temp_zone);
|
||||
if (FLAG_turbo_loop_variable) induction_vars.Run();
|
||||
typer->Run(roots, &induction_vars);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include "src/compiler/common-operator.h"
|
||||
#include "src/compiler/graph-reducer.h"
|
||||
#include "src/compiler/js-operator.h"
|
||||
#include "src/compiler/loop-variable-optimizer.h"
|
||||
#include "src/compiler/node-properties.h"
|
||||
#include "src/compiler/node.h"
|
||||
#include "src/compiler/operation-typer.h"
|
||||
@ -77,8 +78,10 @@ Typer::~Typer() {
|
||||
|
||||
class Typer::Visitor : public Reducer {
|
||||
public:
|
||||
explicit Visitor(Typer* typer)
|
||||
: typer_(typer), weakened_nodes_(typer->zone()) {}
|
||||
explicit Visitor(Typer* typer, LoopVariableOptimizer* induction_vars)
|
||||
: typer_(typer),
|
||||
induction_vars_(induction_vars),
|
||||
weakened_nodes_(typer->zone()) {}
|
||||
|
||||
Reduction Reduce(Node* node) override {
|
||||
if (node->op()->ValueOutputCount() == 0) return NoChange();
|
||||
@ -189,6 +192,7 @@ class Typer::Visitor : public Reducer {
|
||||
|
||||
private:
|
||||
Typer* typer_;
|
||||
LoopVariableOptimizer* induction_vars_;
|
||||
ZoneSet<NodeId> weakened_nodes_;
|
||||
|
||||
#define DECLARE_METHOD(x) inline Type* Type##x(Node* node);
|
||||
@ -308,18 +312,23 @@ class Typer::Visitor : public Reducer {
|
||||
}
|
||||
};
|
||||
|
||||
void Typer::Run() { Run(NodeVector(zone()), nullptr); }
|
||||
|
||||
void Typer::Run() { Run(NodeVector(zone())); }
|
||||
|
||||
|
||||
void Typer::Run(const NodeVector& roots) {
|
||||
Visitor visitor(this);
|
||||
void Typer::Run(const NodeVector& roots,
|
||||
LoopVariableOptimizer* induction_vars) {
|
||||
if (induction_vars != nullptr) {
|
||||
induction_vars->ChangeToInductionVariablePhis();
|
||||
}
|
||||
Visitor visitor(this, induction_vars);
|
||||
GraphReducer graph_reducer(zone(), graph());
|
||||
graph_reducer.AddReducer(&visitor);
|
||||
for (Node* const root : roots) graph_reducer.ReduceNode(root);
|
||||
graph_reducer.ReduceGraph();
|
||||
}
|
||||
|
||||
if (induction_vars != nullptr) {
|
||||
induction_vars->ChangeFromInductionVariablePhis();
|
||||
}
|
||||
}
|
||||
|
||||
void Typer::Decorator::Decorate(Node* node) {
|
||||
if (node->op()->ValueOutputCount() > 0) {
|
||||
@ -327,7 +336,7 @@ void Typer::Decorator::Decorate(Node* node) {
|
||||
// Other cases will generally require a proper fixpoint iteration with Run.
|
||||
bool is_typed = NodeProperties::IsTyped(node);
|
||||
if (is_typed || NodeProperties::AllValueInputsAreTyped(node)) {
|
||||
Visitor typing(typer_);
|
||||
Visitor typing(typer_, nullptr);
|
||||
Type* type = typing.TypeNode(node);
|
||||
if (is_typed) {
|
||||
type = Type::Intersect(type, NodeProperties::GetType(node),
|
||||
@ -736,7 +745,6 @@ Type* Typer::Visitor::TypeSelect(Node* node) {
|
||||
return Type::Union(Operand(node, 1), Operand(node, 2), zone());
|
||||
}
|
||||
|
||||
|
||||
Type* Typer::Visitor::TypePhi(Node* node) {
|
||||
int arity = node->op()->ValueInputCount();
|
||||
Type* type = Operand(node, 0);
|
||||
@ -746,6 +754,89 @@ Type* Typer::Visitor::TypePhi(Node* node) {
|
||||
return type;
|
||||
}
|
||||
|
||||
Type* Typer::Visitor::TypeInductionVariablePhi(Node* node) {
|
||||
int arity = NodeProperties::GetControlInput(node)->op()->ControlInputCount();
|
||||
DCHECK_EQ(IrOpcode::kLoop, NodeProperties::GetControlInput(node)->opcode());
|
||||
DCHECK_EQ(2, NodeProperties::GetControlInput(node)->InputCount());
|
||||
|
||||
Type* initial_type = Operand(node, 0);
|
||||
Type* increment_type = Operand(node, 2);
|
||||
|
||||
// We only handle integer induction variables (otherwise ranges
|
||||
// do not apply and we cannot do anything).
|
||||
if (!initial_type->Is(typer_->cache_.kInteger) ||
|
||||
!increment_type->Is(typer_->cache_.kInteger)) {
|
||||
// Fallback to normal phi typing.
|
||||
Type* type = Operand(node, 0);
|
||||
for (int i = 1; i < arity; ++i) {
|
||||
type = Type::Union(type, Operand(node, i), zone());
|
||||
}
|
||||
return type;
|
||||
}
|
||||
// If we do not have enough type information for the initial value or
|
||||
// the increment, just return the initial value's type.
|
||||
if (!initial_type->IsInhabited() || !increment_type->IsInhabited()) {
|
||||
return initial_type;
|
||||
}
|
||||
|
||||
// Now process the bounds.
|
||||
auto res = induction_vars_->induction_variables().find(node->id());
|
||||
DCHECK(res != induction_vars_->induction_variables().end());
|
||||
InductionVariable* induction_var = res->second;
|
||||
|
||||
double min = -V8_INFINITY;
|
||||
double max = V8_INFINITY;
|
||||
if (increment_type->Min() >= 0) {
|
||||
min = initial_type->Min();
|
||||
for (auto bound : induction_var->upper_bounds()) {
|
||||
Type* bound_type = TypeOrNone(bound.bound);
|
||||
// If the type is not an integer, just skip the bound.
|
||||
if (!bound_type->Is(typer_->cache_.kInteger)) continue;
|
||||
// If the type is not inhabited, then we can take the initial value.
|
||||
if (!bound_type->IsInhabited()) {
|
||||
max = initial_type->Max();
|
||||
break;
|
||||
}
|
||||
double bound_max = bound_type->Max();
|
||||
if (bound.kind == InductionVariable::kStrict) {
|
||||
bound_max -= 1;
|
||||
}
|
||||
max = std::min(max, bound_max + increment_type->Max());
|
||||
}
|
||||
// The upper bound must be at least the initial value's upper bound.
|
||||
max = std::max(max, initial_type->Max());
|
||||
} else if (increment_type->Max() <= 0) {
|
||||
max = initial_type->Max();
|
||||
for (auto bound : induction_var->lower_bounds()) {
|
||||
Type* bound_type = TypeOrNone(bound.bound);
|
||||
// If the type is not an integer, just skip the bound.
|
||||
if (!bound_type->Is(typer_->cache_.kInteger)) continue;
|
||||
// If the type is not inhabited, then we can take the initial value.
|
||||
if (!bound_type->IsInhabited()) {
|
||||
min = initial_type->Min();
|
||||
break;
|
||||
}
|
||||
double bound_min = bound_type->Min();
|
||||
if (bound.kind == InductionVariable::kStrict) {
|
||||
bound_min += 1;
|
||||
}
|
||||
min = std::max(min, bound_min + increment_type->Min());
|
||||
}
|
||||
// The lower bound must be at most the initial value's lower bound.
|
||||
min = std::min(min, initial_type->Min());
|
||||
} else {
|
||||
// Shortcut: If the increment can be both positive and negative,
|
||||
// the variable can go arbitrarily far, so just return integer.
|
||||
return typer_->cache_.kInteger;
|
||||
}
|
||||
if (FLAG_trace_turbo_loop) {
|
||||
OFStream os(stdout);
|
||||
os << "Loop (" << NodeProperties::GetControlInput(node)->id()
|
||||
<< ") variable bounds for phi " << node->id() << ": (" << min << ", "
|
||||
<< max << ")\n";
|
||||
}
|
||||
return Type::Range(min, max, typer_->zone());
|
||||
}
|
||||
|
||||
Type* Typer::Visitor::TypeEffectPhi(Node* node) {
|
||||
UNREACHABLE();
|
||||
|
@ -19,6 +19,7 @@ class TypeCache;
|
||||
|
||||
namespace compiler {
|
||||
|
||||
class LoopVariableOptimizer;
|
||||
class OperationTyper;
|
||||
|
||||
class Typer {
|
||||
@ -36,7 +37,8 @@ class Typer {
|
||||
|
||||
void Run();
|
||||
// TODO(bmeurer,jarin): Remove this once we have a notion of "roots" on Graph.
|
||||
void Run(const ZoneVector<Node*>& roots);
|
||||
void Run(const ZoneVector<Node*>& roots,
|
||||
LoopVariableOptimizer* induction_vars);
|
||||
|
||||
private:
|
||||
class Visitor;
|
||||
|
@ -386,6 +386,11 @@ void Verifier::Visitor::Check(Node* node) {
|
||||
*/
|
||||
break;
|
||||
}
|
||||
case IrOpcode::kInductionVariablePhi: {
|
||||
// This is only a temporary node for the typer.
|
||||
UNREACHABLE();
|
||||
break;
|
||||
}
|
||||
case IrOpcode::kEffectPhi: {
|
||||
// EffectPhi input count matches parent control node.
|
||||
CHECK_EQ(0, value_count);
|
||||
|
@ -446,6 +446,7 @@ DEFINE_BOOL(trace_turbo_reduction, false, "trace TurboFan's various reducers")
|
||||
DEFINE_BOOL(trace_turbo_trimming, false, "trace TurboFan's graph trimmer")
|
||||
DEFINE_BOOL(trace_turbo_jt, false, "trace TurboFan's jump threading")
|
||||
DEFINE_BOOL(trace_turbo_ceq, false, "trace TurboFan's control equivalence")
|
||||
DEFINE_BOOL(trace_turbo_loop, false, "trace TurboFan's loop optimizations")
|
||||
DEFINE_BOOL(turbo_asm, true, "enable TurboFan for asm.js code")
|
||||
DEFINE_BOOL(turbo_asm_deoptimization, false,
|
||||
"enable deoptimization in TurboFan for asm.js code")
|
||||
@ -477,6 +478,7 @@ DEFINE_BOOL(turbo_jt, true, "enable jump threading in TurboFan")
|
||||
DEFINE_BOOL(turbo_stress_loop_peeling, false,
|
||||
"stress loop peeling optimization")
|
||||
DEFINE_BOOL(turbo_loop_peeling, false, "Turbofan loop peeling")
|
||||
DEFINE_BOOL(turbo_loop_variable, false, "Turbofan loop variable optimization")
|
||||
DEFINE_BOOL(turbo_cf_optimization, true, "optimize control flow in TurboFan")
|
||||
DEFINE_BOOL(turbo_frame_elision, true, "elide frames in TurboFan")
|
||||
DEFINE_BOOL(turbo_cache_shared_code, true, "cache context-independent code")
|
||||
|
@ -657,6 +657,8 @@
|
||||
'compiler/loop-analysis.h',
|
||||
'compiler/loop-peeling.cc',
|
||||
'compiler/loop-peeling.h',
|
||||
'compiler/loop-variable-optimizer.cc',
|
||||
'compiler/loop-variable-optimizer.h',
|
||||
'compiler/machine-operator-reducer.cc',
|
||||
'compiler/machine-operator-reducer.h',
|
||||
'compiler/machine-operator.cc',
|
||||
|
Loading…
Reference in New Issue
Block a user