Commit a2ad4c8f authored by bmeurer's avatar bmeurer Committed by Commit bot

[turbofan] New GraphReducer based LoadElimination.

Turn the LoadElimination into a proper graph Reducer so that it can run
together with ValueNumbering and RedundancyElimination to a fixpoint
for maximum load/check elimination. This also adds initial support for
eliminating redundant LoadElement/StoreElement nodes.

BUG=v8:4930,v8:5141

Review-Url: https://codereview.chromium.org/2164253002
Cr-Commit-Position: refs/heads/master@{#38015}
parent 480f155e
......@@ -4,42 +4,15 @@
#include "src/compiler/load-elimination.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/compiler/simplified-operator.h"
namespace v8 {
namespace internal {
namespace compiler {
#ifdef DEBUG
#define TRACE(...) \
do { \
if (FLAG_trace_turbo_load_elimination) PrintF(__VA_ARGS__); \
} while (false)
#else
#define TRACE(...)
#endif
namespace {
const size_t kMaxTrackedFields = 16;
Node* ActualValue(Node* node) {
switch (node->opcode()) {
case IrOpcode::kCheckBounds:
case IrOpcode::kCheckNumber:
case IrOpcode::kCheckTaggedPointer:
case IrOpcode::kCheckTaggedSigned:
case IrOpcode::kFinishRegion:
return ActualValue(NodeProperties::GetValueInput(node, 0));
default:
return node;
}
}
enum Aliasing { kNoAlias, kMayAlias, kMustAlias };
Aliasing QueryAlias(Node* a, Node* b) {
......@@ -50,6 +23,8 @@ Aliasing QueryAlias(Node* a, Node* b) {
case IrOpcode::kHeapConstant:
case IrOpcode::kParameter:
return kNoAlias;
case IrOpcode::kFinishRegion:
return QueryAlias(a->InputAt(0), b);
default:
break;
}
......@@ -59,6 +34,8 @@ Aliasing QueryAlias(Node* a, Node* b) {
case IrOpcode::kHeapConstant:
case IrOpcode::kParameter:
return kNoAlias;
case IrOpcode::kFinishRegion:
return QueryAlias(a, b->InputAt(0));
default:
break;
}
......@@ -70,373 +47,503 @@ bool MayAlias(Node* a, Node* b) { return QueryAlias(a, b) != kNoAlias; }
bool MustAlias(Node* a, Node* b) { return QueryAlias(a, b) == kMustAlias; }
// Abstract state to approximate the current state of a certain field along the
// effect paths through the graph.
class AbstractField final : public ZoneObject {
public:
explicit AbstractField(Zone* zone) : info_for_node_(zone) {}
AbstractField(Node* object, Node* value, Zone* zone) : info_for_node_(zone) {
info_for_node_.insert(std::make_pair(object, value));
}
} // namespace
AbstractField const* Extend(Node* object, Node* value, Zone* zone) const {
AbstractField* that = new (zone) AbstractField(zone);
that->info_for_node_ = this->info_for_node_;
that->info_for_node_.insert(std::make_pair(object, value));
return that;
Reduction LoadElimination::Reduce(Node* node) {
switch (node->opcode()) {
case IrOpcode::kLoadField:
return ReduceLoadField(node);
case IrOpcode::kStoreField:
return ReduceStoreField(node);
case IrOpcode::kLoadElement:
return ReduceLoadElement(node);
case IrOpcode::kStoreElement:
return ReduceStoreElement(node);
case IrOpcode::kEffectPhi:
return ReduceEffectPhi(node);
case IrOpcode::kDead:
break;
case IrOpcode::kStart:
return ReduceStart(node);
default:
return ReduceOtherNode(node);
}
Node* Lookup(Node* object) const {
for (auto pair : info_for_node_) {
if (MustAlias(object, pair.first)) return pair.second;
return NoChange();
}
Node* LoadElimination::AbstractElements::Lookup(Node* object,
Node* index) const {
for (Element const element : elements_) {
if (element.object == nullptr) continue;
DCHECK_NOT_NULL(element.index);
DCHECK_NOT_NULL(element.value);
if (MustAlias(object, element.object) && MustAlias(index, element.index)) {
return element.value;
}
return nullptr;
}
AbstractField const* Kill(Node* object, Zone* zone) const {
for (auto pair : this->info_for_node_) {
if (MayAlias(object, pair.first)) {
AbstractField* that = new (zone) AbstractField(zone);
for (auto pair : this->info_for_node_) {
if (!MayAlias(object, pair.first)) that->info_for_node_.insert(pair);
}
return nullptr;
}
LoadElimination::AbstractElements const*
LoadElimination::AbstractElements::Kill(Node* object, Node* index,
Zone* zone) const {
for (Element const element : this->elements_) {
if (element.object == nullptr) continue;
if (MayAlias(object, element.object)) {
AbstractElements* that = new (zone) AbstractElements(zone);
for (Element const element : this->elements_) {
if (element.object == nullptr) continue;
DCHECK_NOT_NULL(element.index);
DCHECK_NOT_NULL(element.value);
if (!MayAlias(object, element.object) ||
!MayAlias(index, element.index)) {
that->elements_[that->next_index_++] = element;
}
return that;
}
}
return this;
}
bool Equals(AbstractField const* that) const {
return this == that || this->info_for_node_ == that->info_for_node_;
}
AbstractField const* Merge(AbstractField const* that, Zone* zone) const {
if (this->Equals(that)) return this;
AbstractField* copy = new (zone) AbstractField(zone);
for (auto this_it : this->info_for_node_) {
Node* this_object = this_it.first;
Node* this_value = this_it.second;
auto that_it = that->info_for_node_.find(this_object);
if (that_it != that->info_for_node_.end() &&
that_it->second == this_value) {
copy->info_for_node_.insert(this_it);
}
return that;
}
return copy;
}
private:
ZoneMap<Node*, Node*> info_for_node_;
};
// Abstract state to track the state of all fields along the effect paths
// through the graph.
class AbstractState final : public ZoneObject {
public:
AbstractState() {
for (size_t i = 0; i < kMaxTrackedFields; ++i) fields_[i] = nullptr;
}
return this;
}
AbstractState const* Extend(Node* object, size_t index, Node* value,
Zone* zone) const {
AbstractState* that = new (zone) AbstractState(*this);
AbstractField const* that_field = that->fields_[index];
if (that_field) {
that_field = that_field->Extend(object, value, zone);
} else {
that_field = new (zone) AbstractField(object, value, zone);
}
that->fields_[index] = that_field;
return that;
}
AbstractState const* Kill(Node* object, size_t index, Zone* zone) const {
if (!this->fields_[index]) return this;
AbstractState* that = new (zone) AbstractState(*this);
that->fields_[index] = nullptr;
return that;
}
AbstractState const* Merge(AbstractState const* that, Zone* zone) const {
if (this->Equals(that)) return this;
AbstractState* copy = new (zone) AbstractState();
for (size_t i = 0; i < kMaxTrackedFields; ++i) {
AbstractField const* this_field = this->fields_[i];
AbstractField const* that_field = that->fields_[i];
if (this_field && that_field) {
copy->fields_[i] = this_field->Merge(that_field, zone);
bool LoadElimination::AbstractElements::Equals(
AbstractElements const* that) const {
if (this == that) return true;
for (size_t i = 0; i < arraysize(elements_); ++i) {
Element this_element = this->elements_[i];
if (this_element.object == nullptr) continue;
for (size_t j = 0;; ++j) {
if (j == arraysize(elements_)) return false;
Element that_element = that->elements_[j];
if (this_element.object == that_element.object &&
this_element.index == that_element.index &&
this_element.value == that_element.value) {
break;
}
}
return copy;
}
Node* Lookup(Node* object, size_t index) const {
AbstractField const* this_field = this->fields_[index];
if (this_field) return this_field->Lookup(object);
return nullptr;
}
bool Equals(AbstractState const* that) const {
if (this == that) return true;
for (size_t i = 0; i < kMaxTrackedFields; ++i) {
AbstractField const* this_field = this->fields_[i];
AbstractField const* that_field = that->fields_[i];
if (this_field) {
if (!that_field || !this_field->Equals(that_field)) return false;
} else if (that_field) {
return false;
}
for (size_t i = 0; i < arraysize(elements_); ++i) {
Element that_element = that->elements_[i];
if (that_element.object == nullptr) continue;
for (size_t j = 0;; ++j) {
if (j == arraysize(elements_)) return false;
Element this_element = this->elements_[j];
if (that_element.object == this_element.object &&
that_element.index == this_element.index &&
that_element.value == this_element.value) {
break;
}
DCHECK(this_field == that_field || this_field->Equals(that_field));
}
return true;
}
private:
AbstractField const* fields_[kMaxTrackedFields];
};
class LoadEliminationAnalysis final {
public:
LoadEliminationAnalysis(Graph* graph, Zone* zone)
: candidates_(zone),
empty_state_(),
queue_(zone),
node_states_(graph->NodeCount(), zone) {}
void Run(Node* start) {
TRACE("--{Analysis phase}--\n");
UpdateState(start, empty_state());
while (!queue_.empty()) {
Node* const node = queue_.top();
queue_.pop();
VisitNode(node);
}
TRACE("--{Replacement phase}--\n");
ZoneMap<Node*, Node*> replacements(zone());
for (Node* const node : candidates_) {
switch (node->opcode()) {
case IrOpcode::kLoadField: {
FieldAccess const& access = FieldAccessOf(node->op());
Node* const object =
ActualValue(NodeProperties::GetValueInput(node, 0));
Node* const effect = NodeProperties::GetEffectInput(node);
AbstractState const* state = GetState(effect);
int field_index = FieldIndexOf(access);
DCHECK_LE(0, field_index);
if (Node* value = state->Lookup(object, field_index)) {
auto it = replacements.find(value);
if (it != replacements.end()) value = it->second;
Type* const value_type = NodeProperties::GetType(value);
if (value_type->Is(access.type)) {
replacements.insert(std::make_pair(node, value));
TRACE(" - Replacing redundant #%d:LoadField with #%d:%s\n",
node->id(), value->id(), value->op()->mnemonic());
NodeProperties::ReplaceUses(node, value, effect);
node->Kill();
} else {
TRACE(
" - Cannot replace redundant #%d:LoadField with #%d:%s,"
" because types don't agree",
node->id(), value->id(), value->op()->mnemonic());
}
}
break;
}
case IrOpcode::kStoreField: {
FieldAccess const& access = FieldAccessOf(node->op());
Node* const object =
ActualValue(NodeProperties::GetValueInput(node, 0));
Node* const value =
ActualValue(NodeProperties::GetValueInput(node, 1));
Node* const effect = NodeProperties::GetEffectInput(node);
AbstractState const* state = GetState(effect);
int field_index = FieldIndexOf(access);
DCHECK_LE(0, field_index);
if (value == state->Lookup(object, field_index)) {
TRACE(" - Killing redundant #%d:StoreField\n", node->id());
NodeProperties::ReplaceUses(node, value, effect);
node->Kill();
}
break;
}
default:
UNREACHABLE();
}
return true;
}
LoadElimination::AbstractElements const*
LoadElimination::AbstractElements::Merge(AbstractElements const* that,
Zone* zone) const {
if (this->Equals(that)) return this;
AbstractElements* copy = new (zone) AbstractElements(zone);
for (Element const this_element : this->elements_) {
if (this_element.object == nullptr) continue;
for (Element const that_element : that->elements_) {
if (this_element.object == that_element.object &&
this_element.index == that_element.index &&
this_element.value == that_element.value) {
copy->elements_[copy->next_index_++] = this_element;
}
}
}
return copy;
}
private:
void VisitNode(Node* node) {
TRACE(" - Visiting node #%d:%s\n", node->id(), node->op()->mnemonic());
switch (node->opcode()) {
case IrOpcode::kEffectPhi:
return VisitEffectPhi(node);
case IrOpcode::kLoadField:
return VisitLoadField(node);
case IrOpcode::kStoreElement:
return VisitStoreElement(node);
case IrOpcode::kStoreField:
return VisitStoreField(node);
case IrOpcode::kDeoptimize:
case IrOpcode::kReturn:
case IrOpcode::kTerminate:
case IrOpcode::kThrow:
break;
default:
return VisitOtherNode(node);
}
Node* LoadElimination::AbstractField::Lookup(Node* object) const {
for (auto pair : info_for_node_) {
if (MustAlias(object, pair.first)) return pair.second;
}
return nullptr;
}
void VisitEffectPhi(Node* node) {
int const input_count = node->InputCount() - 1;
DCHECK_LT(0, input_count);
Node* const control = NodeProperties::GetControlInput(node);
Node* const effect0 = NodeProperties::GetEffectInput(node, 0);
AbstractState const* state = GetState(effect0);
if (state == nullptr) return;
if (control->opcode() == IrOpcode::kMerge) {
for (int i = 1; i < input_count; ++i) {
Node* const effecti = NodeProperties::GetEffectInput(node, i);
if (GetState(effecti) == nullptr) return;
LoadElimination::AbstractField const* LoadElimination::AbstractField::Kill(
Node* object, Zone* zone) const {
for (auto pair : this->info_for_node_) {
if (MayAlias(object, pair.first)) {
AbstractField* that = new (zone) AbstractField(zone);
for (auto pair : this->info_for_node_) {
if (!MayAlias(object, pair.first)) that->info_for_node_.insert(pair);
}
return that;
}
for (int i = 1; i < input_count; ++i) {
Node* const effecti = NodeProperties::GetEffectInput(node, i);
if (AbstractState const* statei = GetState(effecti)) {
state = state->Merge(statei, zone());
}
}
return this;
}
bool LoadElimination::AbstractState::Equals(AbstractState const* that) const {
if (this->elements_) {
if (!that->elements_ || !that->elements_->Equals(this->elements_)) {
return false;
}
UpdateState(node, state);
}
void VisitLoadField(Node* node) {
FieldAccess const& access = FieldAccessOf(node->op());
Node* const object = ActualValue(NodeProperties::GetValueInput(node, 0));
Node* const effect = NodeProperties::GetEffectInput(node);
AbstractState const* state = GetState(effect);
int field_index = FieldIndexOf(access);
if (field_index >= 0) {
Node* const value = state->Lookup(object, field_index);
if (!value) {
TRACE(" Node #%d:LoadField is not redundant\n", node->id());
state = state->Extend(object, field_index, node, zone());
} else if (!NodeProperties::GetType(value)->Is(access.type)) {
TRACE(
" Node #%d:LoadField is redundant for #%d:%s, but"
" types don't agree\n",
node->id(), value->id(), value->op()->mnemonic());
state = state->Extend(object, field_index, node, zone());
} else if (value) {
TRACE(" Node #%d:LoadField is fully redundant for #%d:%s\n",
node->id(), value->id(), value->op()->mnemonic());
candidates_.insert(node);
}
} else {
TRACE(" Node #%d:LoadField is unsupported\n", node->id());
} else if (that->elements_) {
return false;
}
for (size_t i = 0u; i < arraysize(fields_); ++i) {
AbstractField const* this_field = this->fields_[i];
AbstractField const* that_field = that->fields_[i];
if (this_field) {
if (!that_field || !that_field->Equals(this_field)) return false;
} else if (that_field) {
return false;
}
UpdateState(node, state);
}
void VisitStoreField(Node* node) {
FieldAccess const& access = FieldAccessOf(node->op());
Node* const object = ActualValue(NodeProperties::GetValueInput(node, 0));
Node* const new_value = NodeProperties::GetValueInput(node, 1);
Node* const effect = NodeProperties::GetEffectInput(node);
AbstractState const* state = GetState(effect);
int field_index = FieldIndexOf(access);
if (field_index >= 0) {
Node* const old_value = state->Lookup(object, field_index);
if (old_value == new_value) {
TRACE(" Node #%d:StoreField is fully redundant, storing #%d:%s\n",
node->id(), new_value->id(), new_value->op()->mnemonic());
candidates_.insert(node);
}
return true;
}
void LoadElimination::AbstractState::Merge(AbstractState const* that,
Zone* zone) {
// Merge the information we have about the elements.
if (this->elements_) {
this->elements_ = that->elements_
? that->elements_->Merge(this->elements_, zone)
: that->elements_;
} else {
this->elements_ = that->elements_;
}
// Merge the information we have about the fields.
for (size_t i = 0; i < arraysize(fields_); ++i) {
if (this->fields_[i]) {
if (that->fields_[i]) {
this->fields_[i] = this->fields_[i]->Merge(that->fields_[i], zone);
} else {
this->fields_[i] = nullptr;
}
TRACE(" Killing all potentially aliasing stores for %d on #%d:%s\n",
field_index, object->id(), object->op()->mnemonic());
state = state->Kill(object, field_index, zone());
TRACE(" Node #%d:StoreField provides #%d:%s for %d on #%d:%s\n",
node->id(), new_value->id(), new_value->op()->mnemonic(),
field_index, object->id(), object->op()->mnemonic());
state = state->Extend(object, field_index, new_value, zone());
} else {
TRACE(" Node #%d:StoreField is unsupported\n", node->id());
state = empty_state();
}
UpdateState(node, state);
}
}
void VisitStoreElement(Node* node) {
Node* const effect = NodeProperties::GetEffectInput(node);
AbstractState const* state = GetState(effect);
UpdateState(node, state);
Node* LoadElimination::AbstractState::LookupElement(Node* object,
Node* index) const {
if (this->elements_) {
return this->elements_->Lookup(object, index);
}
return nullptr;
}
void VisitOtherNode(Node* node) {
DCHECK_EQ(1, node->op()->EffectInputCount());
DCHECK_EQ(1, node->op()->EffectOutputCount());
Node* const effect = NodeProperties::GetEffectInput(node);
AbstractState const* state = node->op()->HasProperty(Operator::kNoWrite)
? GetState(effect)
: empty_state();
UpdateState(node, state);
LoadElimination::AbstractState const*
LoadElimination::AbstractState::AddElement(Node* object, Node* index,
Node* value, Zone* zone) const {
AbstractState* that = new (zone) AbstractState(*this);
if (that->elements_) {
that->elements_ = that->elements_->Extend(object, index, value, zone);
} else {
that->elements_ = new (zone) AbstractElements(object, index, value, zone);
}
return that;
}
int FieldIndexOf(FieldAccess const& access) const {
switch (access.machine_type.representation()) {
case MachineRepresentation::kNone:
case MachineRepresentation::kBit:
UNREACHABLE();
break;
case MachineRepresentation::kWord8:
case MachineRepresentation::kWord16:
case MachineRepresentation::kWord32:
case MachineRepresentation::kWord64:
case MachineRepresentation::kFloat32:
return -1; // Currently untracked.
case MachineRepresentation::kFloat64:
case MachineRepresentation::kSimd128:
case MachineRepresentation::kTagged:
// TODO(bmeurer): Check that we never do overlapping load/stores of
// individual parts of Float64/Simd128 values.
break;
LoadElimination::AbstractState const*
LoadElimination::AbstractState::KillElement(Node* object, Node* index,
Zone* zone) const {
if (this->elements_) {
AbstractElements const* that_elements =
this->elements_->Kill(object, index, zone);
if (this->elements_ != that_elements) {
AbstractState* that = new (zone) AbstractState(*this);
that->elements_ = that_elements;
return that;
}
DCHECK_EQ(kTaggedBase, access.base_is_tagged);
DCHECK_EQ(0, access.offset % kPointerSize);
int field_index = access.offset / kPointerSize;
if (field_index >= static_cast<int>(kMaxTrackedFields)) return -1;
return field_index;
}
return this;
}
AbstractState const* GetState(Node* node) const {
return node_states_[node->id()];
LoadElimination::AbstractState const* LoadElimination::AbstractState::AddField(
Node* object, size_t index, Node* value, Zone* zone) const {
AbstractState* that = new (zone) AbstractState(*this);
if (that->fields_[index]) {
that->fields_[index] = that->fields_[index]->Extend(object, value, zone);
} else {
that->fields_[index] = new (zone) AbstractField(object, value, zone);
}
void SetState(Node* node, AbstractState const* state) {
node_states_[node->id()] = state;
return that;
}
LoadElimination::AbstractState const* LoadElimination::AbstractState::KillField(
Node* object, size_t index, Zone* zone) const {
if (AbstractField const* this_field = this->fields_[index]) {
this_field = this_field->Kill(object, zone);
if (this->fields_[index] != this_field) {
AbstractState* that = new (zone) AbstractState(*this);
that->fields_[index] = this_field;
return that;
}
}
return this;
}
void UpdateState(Node* node, AbstractState const* new_state) {
AbstractState const* old_state = GetState(node);
if (old_state && old_state->Equals(new_state)) return;
SetState(node, new_state);
EnqueueUses(node);
Node* LoadElimination::AbstractState::LookupField(Node* object,
size_t index) const {
if (AbstractField const* this_field = this->fields_[index]) {
return this_field->Lookup(object);
}
return nullptr;
}
LoadElimination::AbstractState const*
LoadElimination::AbstractStateForEffectNodes::Get(Node* node) const {
size_t const id = node->id();
if (id < info_for_node_.size()) return info_for_node_[id];
return nullptr;
}
void LoadElimination::AbstractStateForEffectNodes::Set(
Node* node, AbstractState const* state) {
size_t const id = node->id();
if (id >= info_for_node_.size()) info_for_node_.resize(id + 1, nullptr);
info_for_node_[id] = state;
}
void EnqueueUses(Node* node) {
for (Edge const edge : node->use_edges()) {
if (NodeProperties::IsEffectEdge(edge)) {
queue_.push(edge.from());
Reduction LoadElimination::ReduceLoadField(Node* node) {
FieldAccess const& access = FieldAccessOf(node->op());
Node* const object = NodeProperties::GetValueInput(node, 0);
Node* const effect = NodeProperties::GetEffectInput(node);
AbstractState const* state = node_states_.Get(effect);
if (state == nullptr) return NoChange();
int field_index = FieldIndexOf(access);
if (field_index >= 0) {
if (Node* const replacement = state->LookupField(object, field_index)) {
// Make sure the {replacement} has at least as good type
// as the original {node}.
if (NodeProperties::GetType(replacement)
->Is(NodeProperties::GetType(node))) {
ReplaceWithValue(node, replacement, effect);
DCHECK(!replacement->IsDead());
return Replace(replacement);
}
}
state = state->AddField(object, field_index, node, zone());
}
return UpdateState(node, state);
}
AbstractState const* empty_state() const { return &empty_state_; }
Zone* zone() const { return node_states_.get_allocator().zone(); }
Reduction LoadElimination::ReduceStoreField(Node* node) {
FieldAccess const& access = FieldAccessOf(node->op());
Node* const object = NodeProperties::GetValueInput(node, 0);
Node* const new_value = NodeProperties::GetValueInput(node, 1);
Node* const effect = NodeProperties::GetEffectInput(node);
AbstractState const* state = node_states_.Get(effect);
if (state == nullptr) return NoChange();
int field_index = FieldIndexOf(access);
if (field_index >= 0) {
Node* const old_value = state->LookupField(object, field_index);
if (old_value == new_value) {
// This store is fully redundant.
return Replace(effect);
}
// Kill all potentially aliasing fields and record the new value.
state = state->KillField(object, field_index, zone());
state = state->AddField(object, field_index, new_value, zone());
} else {
// Unsupported StoreField operator.
state = empty_state();
}
return UpdateState(node, state);
}
ZoneSet<Node*> candidates_;
AbstractState const empty_state_;
ZoneStack<Node*> queue_;
ZoneVector<AbstractState const*> node_states_;
Reduction LoadElimination::ReduceLoadElement(Node* node) {
Node* const object = NodeProperties::GetValueInput(node, 0);
Node* const index = NodeProperties::GetValueInput(node, 1);
Node* const effect = NodeProperties::GetEffectInput(node);
AbstractState const* state = node_states_.Get(effect);
if (state == nullptr) return NoChange();
if (Node* const replacement = state->LookupElement(object, index)) {
// Make sure the {replacement} has at least as good type
// as the original {node}.
if (NodeProperties::GetType(replacement)
->Is(NodeProperties::GetType(node))) {
ReplaceWithValue(node, replacement, effect);
DCHECK(!replacement->IsDead());
return Replace(replacement);
}
}
state = state->AddElement(object, index, node, zone());
return UpdateState(node, state);
}
DISALLOW_COPY_AND_ASSIGN(LoadEliminationAnalysis);
};
Reduction LoadElimination::ReduceStoreElement(Node* node) {
ElementAccess const& access = ElementAccessOf(node->op());
Node* const object = NodeProperties::GetValueInput(node, 0);
Node* const index = NodeProperties::GetValueInput(node, 1);
Node* const new_value = NodeProperties::GetValueInput(node, 2);
Node* const effect = NodeProperties::GetEffectInput(node);
AbstractState const* state = node_states_.Get(effect);
if (state == nullptr) return NoChange();
Node* const old_value = state->LookupElement(object, index);
if (old_value == new_value) {
// This store is fully redundant.
return Replace(effect);
}
// Kill all potentially aliasing elements.
state = state->KillElement(object, index, zone());
// Only record the new value if the store doesn't have an implicit truncation.
switch (access.machine_type.representation()) {
case MachineRepresentation::kNone:
case MachineRepresentation::kBit:
UNREACHABLE();
break;
case MachineRepresentation::kWord8:
case MachineRepresentation::kWord16:
case MachineRepresentation::kWord32:
case MachineRepresentation::kWord64:
case MachineRepresentation::kFloat32:
// TODO(turbofan): Add support for doing the truncations.
break;
case MachineRepresentation::kFloat64:
case MachineRepresentation::kSimd128:
case MachineRepresentation::kTagged:
state = state->AddElement(object, index, new_value, zone());
break;
}
return UpdateState(node, state);
}
} // namespace
Reduction LoadElimination::ReduceEffectPhi(Node* node) {
Node* const effect0 = NodeProperties::GetEffectInput(node, 0);
Node* const control = NodeProperties::GetControlInput(node);
AbstractState const* state0 = node_states_.Get(effect0);
if (state0 == nullptr) return NoChange();
if (control->opcode() == IrOpcode::kLoop) {
// Here we rely on having only reducible loops:
// The loop entry edge always dominates the header, so we can just take
// the state from the first input, and compute the loop state based on it.
AbstractState const* state = ComputeLoopState(node, state0);
return UpdateState(node, state);
}
DCHECK_EQ(IrOpcode::kMerge, control->opcode());
// Shortcut for the case when we do not know anything about some input.
int const input_count = node->op()->EffectInputCount();
for (int i = 1; i < input_count; ++i) {
Node* const effect = NodeProperties::GetEffectInput(node, i);
if (node_states_.Get(effect) == nullptr) return NoChange();
}
// Make a copy of the first input's state and merge with the state
// from other inputs.
AbstractState* state = new (zone()) AbstractState(*state0);
for (int i = 1; i < input_count; ++i) {
Node* const input = NodeProperties::GetEffectInput(node, i);
state->Merge(node_states_.Get(input), zone());
}
return UpdateState(node, state);
}
Reduction LoadElimination::ReduceStart(Node* node) {
return UpdateState(node, empty_state());
}
Reduction LoadElimination::ReduceOtherNode(Node* node) {
if (node->op()->EffectInputCount() == 1) {
if (node->op()->EffectOutputCount() == 1) {
Node* const effect = NodeProperties::GetEffectInput(node);
AbstractState const* state = node_states_.Get(effect);
// If we do not know anything about the predecessor, do not propagate
// just yet because we will have to recompute anyway once we compute
// the predecessor.
if (state == nullptr) return NoChange();
// Check if this {node} has some uncontrolled side effects.
if (!node->op()->HasProperty(Operator::kNoWrite)) {
state = empty_state();
}
return UpdateState(node, state);
} else {
// Effect terminators should be handled specially.
return NoChange();
}
}
DCHECK_EQ(0, node->op()->EffectInputCount());
DCHECK_EQ(0, node->op()->EffectOutputCount());
return NoChange();
}
void LoadElimination::Run() {
LoadEliminationAnalysis analysis(graph(), zone());
analysis.Run(graph()->start());
Reduction LoadElimination::UpdateState(Node* node, AbstractState const* state) {
AbstractState const* original = node_states_.Get(node);
// Only signal that the {node} has Changed, if the information about {state}
// has changed wrt. the {original}.
if (state != original) {
if (original == nullptr || !state->Equals(original)) {
node_states_.Set(node, state);
return Changed(node);
}
}
return NoChange();
}
LoadElimination::AbstractState const* LoadElimination::ComputeLoopState(
Node* node, AbstractState const* state) const {
Node* const control = NodeProperties::GetControlInput(node);
ZoneQueue<Node*> queue(zone());
ZoneSet<Node*> visited(zone());
visited.insert(node);
for (int i = 1; i < control->InputCount(); ++i) {
queue.push(node->InputAt(i));
}
while (!queue.empty()) {
Node* const current = queue.front();
queue.pop();
if (visited.find(current) == visited.end()) {
visited.insert(current);
if (!current->op()->HasProperty(Operator::kNoWrite)) {
switch (current->opcode()) {
case IrOpcode::kStoreField: {
FieldAccess const& access = FieldAccessOf(current->op());
Node* const object = NodeProperties::GetValueInput(current, 0);
int field_index = FieldIndexOf(access);
if (field_index < 0) return empty_state();
state = state->KillField(object, field_index, zone());
break;
}
case IrOpcode::kStoreElement: {
Node* const object = NodeProperties::GetValueInput(current, 0);
Node* const index = NodeProperties::GetValueInput(current, 1);
state = state->KillElement(object, index, zone());
break;
}
default:
return empty_state();
}
}
for (int i = 0; i < current->op()->EffectInputCount(); ++i) {
queue.push(NodeProperties::GetEffectInput(current, i));
}
}
}
return state;
}
// static
int LoadElimination::FieldIndexOf(FieldAccess const& access) {
switch (access.machine_type.representation()) {
case MachineRepresentation::kNone:
case MachineRepresentation::kBit:
UNREACHABLE();
break;
case MachineRepresentation::kWord8:
case MachineRepresentation::kWord16:
case MachineRepresentation::kWord32:
case MachineRepresentation::kWord64:
case MachineRepresentation::kFloat32:
return -1; // Currently untracked.
case MachineRepresentation::kFloat64:
case MachineRepresentation::kSimd128:
case MachineRepresentation::kTagged:
// TODO(bmeurer): Check that we never do overlapping load/stores of
// individual parts of Float64/Simd128 values.
break;
}
DCHECK_EQ(kTaggedBase, access.base_is_tagged);
DCHECK_EQ(0, access.offset % kPointerSize);
int field_index = access.offset / kPointerSize;
if (field_index >= static_cast<int>(kMaxTrackedFields)) return -1;
return field_index;
}
} // namespace compiler
......
......@@ -5,30 +5,172 @@
#ifndef V8_COMPILER_LOAD_ELIMINATION_H_
#define V8_COMPILER_LOAD_ELIMINATION_H_
#include "src/compiler/graph-reducer.h"
namespace v8 {
namespace internal {
// Forward declarations.
class Zone;
namespace compiler {
// Forward declarations.
class Graph;
// Foward declarations.
struct FieldAccess;
// Eliminates redundant loads via scalar replacement of aggregates.
class LoadElimination final {
class LoadElimination final : public AdvancedReducer {
public:
LoadElimination(Graph* graph, Zone* zone) : graph_(graph), zone_(zone) {}
LoadElimination(Editor* editor, Zone* zone)
: AdvancedReducer(editor), node_states_(zone) {}
~LoadElimination() final {}
void Run();
Reduction Reduce(Node* node) final;
private:
Graph* graph() const { return graph_; }
Zone* zone() const { return zone_; }
static const size_t kMaxTrackedElements = 8;
// Abstract state to approximate the current state of an element along the
// effect paths through the graph.
class AbstractElements final : public ZoneObject {
public:
explicit AbstractElements(Zone* zone) {
for (size_t i = 0; i < arraysize(elements_); ++i) {
elements_[i] = Element();
}
}
AbstractElements(Node* object, Node* index, Node* value, Zone* zone)
: AbstractElements(zone) {
elements_[next_index_++] = Element(object, index, value);
}
AbstractElements const* Extend(Node* object, Node* index, Node* value,
Zone* zone) const {
AbstractElements* that = new (zone) AbstractElements(*this);
that->elements_[that->next_index_] = Element(object, index, value);
that->next_index_ = (that->next_index_ + 1) % arraysize(elements_);
return that;
}
Node* Lookup(Node* object, Node* index) const;
AbstractElements const* Kill(Node* object, Node* index, Zone* zone) const;
bool Equals(AbstractElements const* that) const;
AbstractElements const* Merge(AbstractElements const* that,
Zone* zone) const;
private:
struct Element {
Element() {}
Element(Node* object, Node* index, Node* value)
: object(object), index(index), value(value) {}
Node* object = nullptr;
Node* index = nullptr;
Node* value = nullptr;
};
Element elements_[kMaxTrackedElements];
size_t next_index_ = 0;
};
// Abstract state to approximate the current state of a certain field along
// the effect paths through the graph.
class AbstractField final : public ZoneObject {
public:
explicit AbstractField(Zone* zone) : info_for_node_(zone) {}
AbstractField(Node* object, Node* value, Zone* zone)
: info_for_node_(zone) {
info_for_node_.insert(std::make_pair(object, value));
}
AbstractField const* Extend(Node* object, Node* value, Zone* zone) const {
AbstractField* that = new (zone) AbstractField(zone);
that->info_for_node_ = this->info_for_node_;
that->info_for_node_.insert(std::make_pair(object, value));
return that;
}
Node* Lookup(Node* object) const;
AbstractField const* Kill(Node* object, Zone* zone) const;
bool Equals(AbstractField const* that) const {
return this == that || this->info_for_node_ == that->info_for_node_;
}
AbstractField const* Merge(AbstractField const* that, Zone* zone) const {
if (this->Equals(that)) return this;
AbstractField* copy = new (zone) AbstractField(zone);
for (auto this_it : this->info_for_node_) {
Node* this_object = this_it.first;
Node* this_value = this_it.second;
auto that_it = that->info_for_node_.find(this_object);
if (that_it != that->info_for_node_.end() &&
that_it->second == this_value) {
copy->info_for_node_.insert(this_it);
}
}
return copy;
}
private:
ZoneMap<Node*, Node*> info_for_node_;
};
static size_t const kMaxTrackedFields = 16;
class AbstractState final : public ZoneObject {
public:
AbstractState() {
for (size_t i = 0; i < arraysize(fields_); ++i) {
fields_[i] = nullptr;
}
}
bool Equals(AbstractState const* that) const;
void Merge(AbstractState const* that, Zone* zone);
AbstractState const* AddField(Node* object, size_t index, Node* value,
Zone* zone) const;
AbstractState const* KillField(Node* object, size_t index,
Zone* zone) const;
Node* LookupField(Node* object, size_t index) const;
AbstractState const* AddElement(Node* object, Node* index, Node* value,
Zone* zone) const;
AbstractState const* KillElement(Node* object, Node* index,
Zone* zone) const;
Node* LookupElement(Node* object, Node* index) const;
private:
AbstractElements const* elements_ = nullptr;
AbstractField const* fields_[kMaxTrackedFields];
};
class AbstractStateForEffectNodes final : public ZoneObject {
public:
explicit AbstractStateForEffectNodes(Zone* zone) : info_for_node_(zone) {}
AbstractState const* Get(Node* node) const;
void Set(Node* node, AbstractState const* state);
Zone* zone() const { return info_for_node_.get_allocator().zone(); }
private:
ZoneVector<AbstractState const*> info_for_node_;
};
Reduction ReduceLoadField(Node* node);
Reduction ReduceStoreField(Node* node);
Reduction ReduceLoadElement(Node* node);
Reduction ReduceStoreElement(Node* node);
Reduction ReduceEffectPhi(Node* node);
Reduction ReduceStart(Node* node);
Reduction ReduceOtherNode(Node* node);
Reduction UpdateState(Node* node, AbstractState const* state);
AbstractState const* ComputeLoopState(Node* node,
AbstractState const* state) const;
static int FieldIndexOf(FieldAccess const& access);
AbstractState const* empty_state() const { return &empty_state_; }
Zone* zone() const { return node_states_.zone(); }
AbstractState const empty_state_;
AbstractStateForEffectNodes node_states_;
Graph* const graph_;
Zone* const zone_;
DISALLOW_COPY_AND_ASSIGN(LoadElimination);
};
} // namespace compiler
......
......@@ -904,7 +904,6 @@ struct TypedLoweringPhase {
data->info()->is_deoptimization_enabled()
? JSIntrinsicLowering::kDeoptimizationEnabled
: JSIntrinsicLowering::kDeoptimizationDisabled);
ValueNumberingReducer value_numbering(temp_zone, data->graph()->zone());
SimplifiedOperatorReducer simple_reducer(&graph_reducer, data->jsgraph());
CheckpointElimination checkpoint_elimination(&graph_reducer);
CommonOperatorReducer common_reducer(&graph_reducer, data->graph(),
......@@ -916,7 +915,6 @@ struct TypedLoweringPhase {
}
AddReducer(data, &graph_reducer, &typed_lowering);
AddReducer(data, &graph_reducer, &intrinsic_lowering);
AddReducer(data, &graph_reducer, &value_numbering);
AddReducer(data, &graph_reducer, &simple_reducer);
AddReducer(data, &graph_reducer, &checkpoint_elimination);
AddReducer(data, &graph_reducer, &common_reducer);
......@@ -1054,15 +1052,14 @@ struct LoadEliminationPhase {
static const char* phase_name() { return "load elimination"; }
void Run(PipelineData* data, Zone* temp_zone) {
// The memory optimizer requires the graphs to be trimmed, so trim now.
GraphTrimmer trimmer(temp_zone, data->graph());
NodeVector roots(temp_zone);
data->jsgraph()->GetCachedNodes(&roots);
trimmer.TrimGraph(roots.begin(), roots.end());
// Eliminate redundant loads.
LoadElimination load_elimination(data->graph(), temp_zone);
load_elimination.Run();
JSGraphReducer graph_reducer(data->jsgraph(), temp_zone);
RedundancyElimination redundancy_elimination(&graph_reducer, temp_zone);
LoadElimination load_elimination(&graph_reducer, temp_zone);
ValueNumberingReducer value_numbering(temp_zone, data->graph()->zone());
AddReducer(data, &graph_reducer, &redundancy_elimination);
AddReducer(data, &graph_reducer, &load_elimination);
AddReducer(data, &graph_reducer, &value_numbering);
graph_reducer.ReduceGraph();
}
};
......
......@@ -6,8 +6,13 @@
#include "src/compiler/access-builder.h"
#include "src/compiler/node.h"
#include "src/compiler/simplified-operator.h"
#include "test/unittests/compiler/graph-reducer-unittest.h"
#include "test/unittests/compiler/graph-unittest.h"
#include "test/unittests/compiler/node-test-utils.h"
#include "testing/gmock-support.h"
using testing::_;
using testing::StrictMock;
namespace v8 {
namespace internal {
......@@ -19,37 +24,122 @@ class LoadEliminationTest : public TypedGraphTest {
~LoadEliminationTest() override {}
protected:
void Run() {
LoadElimination load_elimination(graph(), zone());
load_elimination.Run();
}
SimplifiedOperatorBuilder* simplified() { return &simplified_; }
private:
SimplifiedOperatorBuilder simplified_;
};
TEST_F(LoadEliminationTest, LoadElementAndLoadElement) {
Node* object = Parameter(Type::Any(), 0);
Node* effect = graph()->start();
Node* control = graph()->start();
Node* index = Parameter(Type::UnsignedSmall(), 1);
ElementAccess const access = {kTaggedBase, kPointerSize, Type::Any(),
MachineType::AnyTagged(), kNoWriteBarrier};
StrictMock<MockAdvancedReducerEditor> editor;
LoadElimination load_elimination(&editor, zone());
load_elimination.Reduce(graph()->start());
Node* load1 = effect = graph()->NewNode(simplified()->LoadElement(access),
object, index, effect, control);
load_elimination.Reduce(load1);
Node* load2 = effect = graph()->NewNode(simplified()->LoadElement(access),
object, index, effect, control);
EXPECT_CALL(editor, ReplaceWithValue(load2, load1, load1, _));
Reduction r = load_elimination.Reduce(load2);
ASSERT_TRUE(r.Changed());
EXPECT_EQ(load1, r.replacement());
}
TEST_F(LoadEliminationTest, StoreElementAndLoadElement) {
Node* object = Parameter(Type::Any(), 0);
Node* effect = graph()->start();
Node* control = graph()->start();
Node* index = Parameter(Type::UnsignedSmall(), 1);
Node* value = Parameter(Type::Any(), 2);
ElementAccess const access = {kTaggedBase, kPointerSize, Type::Any(),
MachineType::AnyTagged(), kNoWriteBarrier};
StrictMock<MockAdvancedReducerEditor> editor;
LoadElimination load_elimination(&editor, zone());
load_elimination.Reduce(graph()->start());
Node* store = effect =
graph()->NewNode(simplified()->StoreElement(access), object, index, value,
effect, control);
load_elimination.Reduce(store);
Node* load = effect = graph()->NewNode(simplified()->LoadElement(access),
object, index, effect, control);
EXPECT_CALL(editor, ReplaceWithValue(load, value, store, _));
Reduction r = load_elimination.Reduce(load);
ASSERT_TRUE(r.Changed());
EXPECT_EQ(value, r.replacement());
}
TEST_F(LoadEliminationTest, StoreElementAndStoreFieldAndLoadElement) {
Node* object = Parameter(Type::Any(), 0);
Node* effect = graph()->start();
Node* control = graph()->start();
Node* index = Parameter(Type::UnsignedSmall(), 1);
Node* value = Parameter(Type::Any(), 2);
ElementAccess const access = {kTaggedBase, kPointerSize, Type::Any(),
MachineType::AnyTagged(), kNoWriteBarrier};
StrictMock<MockAdvancedReducerEditor> editor;
LoadElimination load_elimination(&editor, zone());
load_elimination.Reduce(graph()->start());
Node* store1 = effect =
graph()->NewNode(simplified()->StoreElement(access), object, index, value,
effect, control);
load_elimination.Reduce(store1);
Node* store2 = effect =
graph()->NewNode(simplified()->StoreField(AccessBuilder::ForMap()),
object, value, effect, control);
load_elimination.Reduce(store2);
Node* load = effect = graph()->NewNode(simplified()->LoadElement(access),
object, index, effect, control);
EXPECT_CALL(editor, ReplaceWithValue(load, value, store2, _));
Reduction r = load_elimination.Reduce(load);
ASSERT_TRUE(r.Changed());
EXPECT_EQ(value, r.replacement());
}
TEST_F(LoadEliminationTest, LoadFieldAndLoadField) {
Node* object = Parameter(Type::Any(), 0);
Node* effect = graph()->start();
Node* control = graph()->start();
FieldAccess access = {kTaggedBase,
kPointerSize,
MaybeHandle<Name>(),
Type::Any(),
MachineType::AnyTagged(),
kNoWriteBarrier};
FieldAccess const access = {kTaggedBase,
kPointerSize,
MaybeHandle<Name>(),
Type::Any(),
MachineType::AnyTagged(),
kNoWriteBarrier};
StrictMock<MockAdvancedReducerEditor> editor;
LoadElimination load_elimination(&editor, zone());
load_elimination.Reduce(graph()->start());
Node* load1 = effect = graph()->NewNode(simplified()->LoadField(access),
object, effect, control);
load_elimination.Reduce(load1);
Node* load2 = effect = graph()->NewNode(simplified()->LoadField(access),
object, effect, control);
control = graph()->NewNode(common()->Return(), load2, effect, control);
graph()->end()->ReplaceInput(0, control);
Run();
EXPECT_THAT(graph()->end(), IsEnd(IsReturn(load1, load1, graph()->start())));
EXPECT_CALL(editor, ReplaceWithValue(load2, load1, load1, _));
Reduction r = load_elimination.Reduce(load2);
ASSERT_TRUE(r.Changed());
EXPECT_EQ(load1, r.replacement());
}
TEST_F(LoadEliminationTest, StoreFieldAndLoadField) {
......@@ -63,16 +153,57 @@ TEST_F(LoadEliminationTest, StoreFieldAndLoadField) {
Type::Any(),
MachineType::AnyTagged(),
kNoWriteBarrier};
StrictMock<MockAdvancedReducerEditor> editor;
LoadElimination load_elimination(&editor, zone());
load_elimination.Reduce(graph()->start());
Node* store = effect = graph()->NewNode(simplified()->StoreField(access),
object, value, effect, control);
load_elimination.Reduce(store);
Node* load = effect = graph()->NewNode(simplified()->LoadField(access),
object, effect, control);
control = graph()->NewNode(common()->Return(), load, effect, control);
graph()->end()->ReplaceInput(0, control);
EXPECT_CALL(editor, ReplaceWithValue(load, value, store, _));
Reduction r = load_elimination.Reduce(load);
ASSERT_TRUE(r.Changed());
EXPECT_EQ(value, r.replacement());
}
Run();
TEST_F(LoadEliminationTest, StoreFieldAndStoreElementAndLoadField) {
Node* object = Parameter(Type::Any(), 0);
Node* value = Parameter(Type::Any(), 1);
Node* index = Parameter(Type::UnsignedSmall(), 2);
Node* effect = graph()->start();
Node* control = graph()->start();
FieldAccess access = {kTaggedBase,
kPointerSize,
MaybeHandle<Name>(),
Type::Any(),
MachineType::AnyTagged(),
kNoWriteBarrier};
StrictMock<MockAdvancedReducerEditor> editor;
LoadElimination load_elimination(&editor, zone());
load_elimination.Reduce(graph()->start());
Node* store1 = effect = graph()->NewNode(simplified()->StoreField(access),
object, value, effect, control);
load_elimination.Reduce(store1);
EXPECT_THAT(graph()->end(), IsEnd(IsReturn(value, store, graph()->start())));
Node* store2 = effect = graph()->NewNode(
simplified()->StoreElement(AccessBuilder::ForFixedArrayElement()), object,
index, object, effect, control);
load_elimination.Reduce(store2);
Node* load = effect = graph()->NewNode(simplified()->LoadField(access),
object, effect, control);
EXPECT_CALL(editor, ReplaceWithValue(load, value, store2, _));
Reduction r = load_elimination.Reduce(load);
ASSERT_TRUE(r.Changed());
EXPECT_EQ(value, r.replacement());
}
} // namespace compiler
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment