Commit fbf351cf authored by Jakob Gruber's avatar Jakob Gruber Committed by Commit Bot

[compiler] Remove implementation details from store-store header

Refactor-only: This moves declaration of implementation details into
the .cc file. No logic changes.

Bug: v8:9574
Change-Id: I985ce17138e65f18dbb629b9f6660009e034fbf0
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1786279
Commit-Queue: Jakob Gruber <jgruber@chromium.org>
Commit-Queue: Maya Lekova <mslekova@chromium.org>
Auto-Submit: Jakob Gruber <jgruber@chromium.org>
Reviewed-by: 's avatarMaya Lekova <mslekova@chromium.org>
Cr-Commit-Position: refs/heads/master@{#63562}
parent 3b0f89d0
......@@ -2,14 +2,15 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <iterator>
#include "src/compiler/store-store-elimination.h"
#include "src/codegen/tick-counter.h"
#include "src/compiler/all-nodes.h"
#include "src/compiler/common-operator.h"
#include "src/compiler/js-graph.h"
#include "src/compiler/node-properties.h"
#include "src/compiler/simplified-operator.h"
#include "src/zone/zone-containers.h"
namespace v8 {
namespace internal {
......@@ -41,7 +42,172 @@ namespace compiler {
#define DCHECK_EXTRA(condition, fmt, ...) ((void)0)
#endif
void StoreStoreElimination::RedundantStoreFinder::Find() {
namespace {
using StoreOffset = uint32_t;
struct UnobservableStore {
NodeId id_;
StoreOffset offset_;
bool operator==(const UnobservableStore other) const {
return (id_ == other.id_) && (offset_ == other.offset_);
}
bool operator<(const UnobservableStore other) const {
return (id_ < other.id_) || (id_ == other.id_ && offset_ < other.offset_);
}
};
// Instances of UnobservablesSet are immutable. They represent either a set of
// UnobservableStores, or the "unvisited empty set".
//
// We apply some sharing to save memory. The class UnobservablesSet is only a
// pointer wide, and a copy does not use any heap (or temp_zone) memory. Most
// changes to an UnobservablesSet might allocate in the temp_zone.
//
// The size of an instance should be the size of a pointer, plus additional
// space in the zone in the case of non-unvisited UnobservablesSets. Copying
// an UnobservablesSet allocates no memory.
class UnobservablesSet final {
public:
// Creates a new UnobservablesSet, with the null set.
static UnobservablesSet Unvisited() { return UnobservablesSet(); }
// Create a new empty UnobservablesSet. This allocates in the zone, and
// can probably be optimized to use a global singleton.
static UnobservablesSet VisitedEmpty(Zone* zone);
UnobservablesSet(const UnobservablesSet& other) V8_NOEXCEPT = default;
// Computes the intersection of two UnobservablesSets. If one of the sets is
// empty, will return empty.
UnobservablesSet Intersect(const UnobservablesSet& other,
const UnobservablesSet& empty, Zone* zone) const;
// Returns a set that it is the current one, plus the observation obs passed
// as parameter. If said obs it's already in the set, we don't have to
// create a new one.
UnobservablesSet Add(UnobservableStore obs, Zone* zone) const;
// Returns a set that it is the current one, except for all of the
// observations with offset off. This is done by creating a new set and
// copying all observations with different offsets.
// This can probably be done better if the observations are stored first by
// offset and then by node.
// We are removing all nodes with offset off since different nodes may
// alias one another, and we currently we don't have the means to know if
// two nodes are definitely the same value.
UnobservablesSet RemoveSameOffset(StoreOffset off, Zone* zone) const;
const ZoneSet<UnobservableStore>* set() const { return set_; }
bool IsUnvisited() const { return set_ == nullptr; }
bool IsEmpty() const { return set_ == nullptr || set_->empty(); }
bool Contains(UnobservableStore obs) const {
return set_ != nullptr && (set_->find(obs) != set_->end());
}
bool operator==(const UnobservablesSet& other) const {
if (IsUnvisited() || other.IsUnvisited()) {
return IsEmpty() && other.IsEmpty();
} else {
// Both pointers guaranteed not to be nullptrs.
return *set() == *(other.set());
}
}
bool operator!=(const UnobservablesSet& other) const {
return !(*this == other);
}
private:
UnobservablesSet();
explicit UnobservablesSet(const ZoneSet<UnobservableStore>* set)
: set_(set) {}
const ZoneSet<UnobservableStore>* set_;
};
class RedundantStoreFinder final {
public:
// Note that we Initialize unobservable_ with js_graph->graph->NodeCount()
// amount of empty sets.
RedundantStoreFinder(JSGraph* js_graph, TickCounter* tick_counter,
Zone* temp_zone)
: jsgraph_(js_graph),
tick_counter_(tick_counter),
temp_zone_(temp_zone),
revisit_(temp_zone),
in_revisit_(js_graph->graph()->NodeCount(), temp_zone),
unobservable_(js_graph->graph()->NodeCount(),
UnobservablesSet::Unvisited(), temp_zone),
to_remove_(temp_zone),
unobservables_visited_empty_(
UnobservablesSet::VisitedEmpty(temp_zone)) {}
// Crawls from the end of the graph to the beginning, with the objective of
// finding redundant stores.
void Find();
// This method is used for const correctness to go through the final list of
// redundant stores that are replaced on the graph.
const ZoneSet<Node*>& to_remove_const() { return to_remove_; }
private:
// Assumption: All effectful nodes are reachable from End via a sequence of
// control, then a sequence of effect edges.
// Visit goes through the control chain, visiting effectful nodes that it
// encounters.
void Visit(Node* node);
// Marks effect inputs for visiting, if we are able to update this path of
// the graph.
void VisitEffectfulNode(Node* node);
// Compute the intersection of the UnobservablesSets of all effect uses and
// return it.
// The result UnobservablesSet will never be null.
UnobservablesSet RecomputeUseIntersection(Node* node);
// Recompute unobservables-set for a node. Will also mark superfluous nodes
// as to be removed.
UnobservablesSet RecomputeSet(Node* node, const UnobservablesSet& uses);
// Returns true if node's opcode cannot observe StoreFields.
static bool CannotObserveStoreField(Node* node);
void MarkForRevisit(Node* node);
bool HasBeenVisited(Node* node);
// To safely cast an offset from a FieldAccess, which has a potentially
// wider range (namely int).
StoreOffset ToOffset(const FieldAccess& access) {
DCHECK_GE(access.offset, 0);
return static_cast<StoreOffset>(access.offset);
}
JSGraph* jsgraph() const { return jsgraph_; }
Isolate* isolate() { return jsgraph()->isolate(); }
Zone* temp_zone() const { return temp_zone_; }
UnobservablesSet& unobservable_for_id(NodeId id) {
DCHECK_LT(id, unobservable_.size());
return unobservable_[id];
}
ZoneSet<Node*>& to_remove() { return to_remove_; }
JSGraph* const jsgraph_;
TickCounter* const tick_counter_;
Zone* const temp_zone_;
ZoneStack<Node*> revisit_;
ZoneVector<bool> in_revisit_;
// Maps node IDs to UnobservableNodeSets.
ZoneVector<UnobservablesSet> unobservable_;
ZoneSet<Node*> to_remove_;
const UnobservablesSet unobservables_visited_empty_;
};
void RedundantStoreFinder::Find() {
Visit(jsgraph()->graph()->end());
while (!revisit_.empty()) {
......@@ -65,7 +231,7 @@ void StoreStoreElimination::RedundantStoreFinder::Find() {
#endif
}
void StoreStoreElimination::RedundantStoreFinder::MarkForRevisit(Node* node) {
void RedundantStoreFinder::MarkForRevisit(Node* node) {
DCHECK_LT(node->id(), in_revisit_.size());
if (!in_revisit_[node->id()]) {
revisit_.push(node);
......@@ -73,32 +239,12 @@ void StoreStoreElimination::RedundantStoreFinder::MarkForRevisit(Node* node) {
}
}
bool StoreStoreElimination::RedundantStoreFinder::HasBeenVisited(Node* node) {
bool RedundantStoreFinder::HasBeenVisited(Node* node) {
return !unobservable_for_id(node->id()).IsUnvisited();
}
void StoreStoreElimination::Run(JSGraph* js_graph, TickCounter* tick_counter,
Zone* temp_zone) {
// Find superfluous nodes
RedundantStoreFinder finder(js_graph, tick_counter, temp_zone);
finder.Find();
// Remove superfluous nodes
for (Node* node : finder.to_remove_const()) {
if (FLAG_trace_store_elimination) {
PrintF("StoreStoreElimination::Run: Eliminating node #%d:%s\n",
node->id(), node->op()->mnemonic());
}
Node* previous_effect = NodeProperties::GetEffectInput(node);
NodeProperties::ReplaceUses(node, nullptr, previous_effect, nullptr,
nullptr);
node->Kill();
}
}
StoreStoreElimination::UnobservablesSet
StoreStoreElimination::RedundantStoreFinder::RecomputeSet(
Node* node, const StoreStoreElimination::UnobservablesSet& uses) {
UnobservablesSet RedundantStoreFinder::RecomputeSet(
Node* node, const UnobservablesSet& uses) {
switch (node->op()->opcode()) {
case IrOpcode::kStoreField: {
Node* stored_to = node->InputAt(0);
......@@ -150,8 +296,7 @@ StoreStoreElimination::RedundantStoreFinder::RecomputeSet(
UNREACHABLE();
}
bool StoreStoreElimination::RedundantStoreFinder::CannotObserveStoreField(
Node* node) {
bool RedundantStoreFinder::CannotObserveStoreField(Node* node) {
IrOpcode::Value opcode = node->opcode();
return opcode == IrOpcode::kLoadElement || opcode == IrOpcode::kLoad ||
opcode == IrOpcode::kStore || opcode == IrOpcode::kEffectPhi ||
......@@ -159,7 +304,7 @@ bool StoreStoreElimination::RedundantStoreFinder::CannotObserveStoreField(
opcode == IrOpcode::kUnsafePointerAdd || opcode == IrOpcode::kRetain;
}
void StoreStoreElimination::RedundantStoreFinder::Visit(Node* node) {
void RedundantStoreFinder::Visit(Node* node) {
if (!HasBeenVisited(node)) {
for (int i = 0; i < node->op()->ControlInputCount(); i++) {
Node* control_input = NodeProperties::GetControlInput(node, i);
......@@ -180,19 +325,15 @@ void StoreStoreElimination::RedundantStoreFinder::Visit(Node* node) {
}
}
void StoreStoreElimination::RedundantStoreFinder::VisitEffectfulNode(
Node* node) {
void RedundantStoreFinder::VisitEffectfulNode(Node* node) {
if (HasBeenVisited(node)) {
TRACE("- Revisiting: #%d:%s", node->id(), node->op()->mnemonic());
}
StoreStoreElimination::UnobservablesSet after_set =
RecomputeUseIntersection(node);
StoreStoreElimination::UnobservablesSet before_set =
RecomputeSet(node, after_set);
UnobservablesSet after_set = RecomputeUseIntersection(node);
UnobservablesSet before_set = RecomputeSet(node, after_set);
DCHECK(!before_set.IsUnvisited());
StoreStoreElimination::UnobservablesSet stores_for_node =
unobservable_for_id(node->id());
UnobservablesSet stores_for_node = unobservable_for_id(node->id());
bool cur_set_changed =
stores_for_node.IsUnvisited() || stores_for_node != before_set;
if (!cur_set_changed) {
......@@ -212,9 +353,7 @@ void StoreStoreElimination::RedundantStoreFinder::VisitEffectfulNode(
}
}
StoreStoreElimination::UnobservablesSet
StoreStoreElimination::RedundantStoreFinder::RecomputeUseIntersection(
Node* node) {
UnobservablesSet RedundantStoreFinder::RecomputeUseIntersection(Node* node) {
// There were no effect uses. Break early.
if (node->op()->EffectOutputCount() == 0) {
IrOpcode::Value opcode = node->opcode();
......@@ -236,8 +375,7 @@ StoreStoreElimination::RedundantStoreFinder::RecomputeUseIntersection(
// {first} == false indicates that cur_set is the intersection of at least one
// thing.
bool first = true;
StoreStoreElimination::UnobservablesSet cur_set =
StoreStoreElimination::UnobservablesSet::Unvisited(); // irrelevant
UnobservablesSet cur_set = UnobservablesSet::Unvisited(); // irrelevant
for (Edge edge : node->use_edges()) {
if (!NodeProperties::IsEffectEdge(edge)) {
continue;
......@@ -245,8 +383,7 @@ StoreStoreElimination::RedundantStoreFinder::RecomputeUseIntersection(
// Intersect with the new use node.
Node* use = edge.from();
StoreStoreElimination::UnobservablesSet new_set =
unobservable_for_id(use->id());
UnobservablesSet new_set = unobservable_for_id(use->id());
if (first) {
first = false;
cur_set = new_set;
......@@ -268,20 +405,18 @@ StoreStoreElimination::RedundantStoreFinder::RecomputeUseIntersection(
return cur_set;
}
StoreStoreElimination::UnobservablesSet::UnobservablesSet() : set_(nullptr) {}
UnobservablesSet::UnobservablesSet() : set_(nullptr) {}
StoreStoreElimination::UnobservablesSet
StoreStoreElimination::UnobservablesSet::VisitedEmpty(Zone* zone) {
UnobservablesSet UnobservablesSet::VisitedEmpty(Zone* zone) {
ZoneSet<UnobservableStore>* empty_set =
new (zone->New(sizeof(ZoneSet<UnobservableStore>)))
ZoneSet<UnobservableStore>(zone);
return StoreStoreElimination::UnobservablesSet(empty_set);
return UnobservablesSet(empty_set);
}
StoreStoreElimination::UnobservablesSet
StoreStoreElimination::UnobservablesSet::Intersect(
const StoreStoreElimination::UnobservablesSet& other,
const StoreStoreElimination::UnobservablesSet& empty, Zone* zone) const {
UnobservablesSet UnobservablesSet::Intersect(const UnobservablesSet& other,
const UnobservablesSet& empty,
Zone* zone) const {
if (IsEmpty() || other.IsEmpty()) {
return empty;
} else {
......@@ -293,13 +428,12 @@ StoreStoreElimination::UnobservablesSet::Intersect(
other.set()->end(),
std::inserter(*intersection, intersection->end()));
return StoreStoreElimination::UnobservablesSet(intersection);
return UnobservablesSet(intersection);
}
}
StoreStoreElimination::UnobservablesSet
StoreStoreElimination::UnobservablesSet::Add(UnobservableStore obs,
Zone* zone) const {
UnobservablesSet UnobservablesSet::Add(UnobservableStore obs,
Zone* zone) const {
bool found = set()->find(obs) != set()->end();
if (found) {
return *this;
......@@ -315,13 +449,12 @@ StoreStoreElimination::UnobservablesSet::Add(UnobservableStore obs,
DCHECK(inserted);
USE(inserted); // silence warning about unused variable
return StoreStoreElimination::UnobservablesSet(new_set);
return UnobservablesSet(new_set);
}
}
StoreStoreElimination::UnobservablesSet
StoreStoreElimination::UnobservablesSet::RemoveSameOffset(StoreOffset offset,
Zone* zone) const {
UnobservablesSet UnobservablesSet::RemoveSameOffset(StoreOffset offset,
Zone* zone) const {
// Make a new empty set.
ZoneSet<UnobservableStore>* new_set =
new (zone->New(sizeof(ZoneSet<UnobservableStore>)))
......@@ -333,7 +466,29 @@ StoreStoreElimination::UnobservablesSet::RemoveSameOffset(StoreOffset offset,
}
}
return StoreStoreElimination::UnobservablesSet(new_set);
return UnobservablesSet(new_set);
}
} // namespace
// static
void StoreStoreElimination::Run(JSGraph* js_graph, TickCounter* tick_counter,
Zone* temp_zone) {
// Find superfluous nodes
RedundantStoreFinder finder(js_graph, tick_counter, temp_zone);
finder.Find();
// Remove superfluous nodes
for (Node* node : finder.to_remove_const()) {
if (FLAG_trace_store_elimination) {
PrintF("StoreStoreElimination::Run: Eliminating node #%d:%s\n",
node->id(), node->op()->mnemonic());
}
Node* previous_effect = NodeProperties::GetEffectInput(node);
NodeProperties::ReplaceUses(node, nullptr, previous_effect, nullptr,
nullptr);
node->Kill();
}
}
#undef TRACE
......
......@@ -5,18 +5,18 @@
#ifndef V8_COMPILER_STORE_STORE_ELIMINATION_H_
#define V8_COMPILER_STORE_STORE_ELIMINATION_H_
#include "src/compiler/common-operator.h"
#include "src/compiler/js-graph.h"
#include "src/compiler/simplified-operator.h"
#include "src/zone/zone-containers.h"
#include "src/common/globals.h"
namespace v8 {
namespace internal {
class TickCounter;
class Zone;
namespace compiler {
class JSGraph;
// Store-store elimination.
//
// The aim of this optimization is to detect the following pattern in the
......@@ -44,176 +44,10 @@ namespace compiler {
//
// This implementation needs all dead nodes removed from the graph, and the
// graph should be trimmed.
class StoreStoreElimination final {
class StoreStoreElimination final : public AllStatic {
public:
static void Run(JSGraph* js_graph, TickCounter* tick_counter,
Zone* temp_zone);
private:
using StoreOffset = uint32_t;
struct UnobservableStore {
NodeId id_;
StoreOffset offset_;
bool operator==(const UnobservableStore other) const {
return (id_ == other.id_) && (offset_ == other.offset_);
}
bool operator<(const UnobservableStore other) const {
return (id_ < other.id_) || (id_ == other.id_ && offset_ < other.offset_);
}
};
// Instances of UnobservablesSet are immutable. They represent either a set of
// UnobservableStores, or the "unvisited empty set".
//
// We apply some sharing to save memory. The class UnobservablesSet is only a
// pointer wide, and a copy does not use any heap (or temp_zone) memory. Most
// changes to an UnobservablesSet might allocate in the temp_zone.
//
// The size of an instance should be the size of a pointer, plus additional
// space in the zone in the case of non-unvisited UnobservablesSets. Copying
// an UnobservablesSet allocates no memory.
class UnobservablesSet final {
public:
// Creates a new UnobservablesSet, with the null set.
static UnobservablesSet Unvisited() { return UnobservablesSet(); }
// Create a new empty UnobservablesSet. This allocates in the zone, and
// can probably be optimized to use a global singleton.
static UnobservablesSet VisitedEmpty(Zone* zone);
UnobservablesSet(const UnobservablesSet& other) V8_NOEXCEPT = default;
// Computes the intersection of two UnobservablesSets. If one of the sets is
// empty, will return empty.
UnobservablesSet Intersect(const UnobservablesSet& other,
const UnobservablesSet& empty, Zone* zone) const;
// Returns a set that it is the current one, plus the observation obs passed
// as parameter. If said obs it's already in the set, we don't have to
// create a new one.
UnobservablesSet Add(UnobservableStore obs, Zone* zone) const;
// Returns a set that it is the current one, except for all of the
// observations with offset off. This is done by creating a new set and
// copying all observations with different offsets.
// This can probably be done better if the observations are stored first by
// offset and then by node.
// We are removing all nodes with offset off since different nodes may
// alias one another, and we currently we don't have the means to know if
// two nodes are definitely the same value.
UnobservablesSet RemoveSameOffset(StoreOffset off, Zone* zone) const;
const ZoneSet<UnobservableStore>* set() const { return set_; }
bool IsUnvisited() const { return set_ == nullptr; }
bool IsEmpty() const { return set_ == nullptr || set_->empty(); }
bool Contains(UnobservableStore obs) const {
return set_ != nullptr && (set_->find(obs) != set_->end());
}
bool operator==(const UnobservablesSet& other) const {
if (IsUnvisited() || other.IsUnvisited()) {
return IsEmpty() && other.IsEmpty();
} else {
// Both pointers guaranteed not to be nullptrs.
return *set() == *(other.set());
}
}
bool operator!=(const UnobservablesSet& other) const {
return !(*this == other);
}
private:
UnobservablesSet();
explicit UnobservablesSet(const ZoneSet<UnobservableStore>* set)
: set_(set) {}
const ZoneSet<UnobservableStore>* set_;
};
class RedundantStoreFinder final {
public:
// Note that we Initialize unobservable_ with js_graph->graph->NodeCount()
// amount of empty sets.
RedundantStoreFinder(JSGraph* js_graph, TickCounter* tick_counter,
Zone* temp_zone)
: jsgraph_(js_graph),
tick_counter_(tick_counter),
temp_zone_(temp_zone),
revisit_(temp_zone),
in_revisit_(js_graph->graph()->NodeCount(), temp_zone),
unobservable_(js_graph->graph()->NodeCount(),
StoreStoreElimination::UnobservablesSet::Unvisited(),
temp_zone),
to_remove_(temp_zone),
unobservables_visited_empty_(
StoreStoreElimination::UnobservablesSet::VisitedEmpty(
temp_zone)) {}
// Crawls from the end of the graph to the beginning, with the objective of
// finding redundant stores.
void Find();
// This method is used for const correctness to go through the final list of
// redundant stores that are replaced on the graph.
const ZoneSet<Node*>& to_remove_const() { return to_remove_; }
private:
// Assumption: All effectful nodes are reachable from End via a sequence of
// control, then a sequence of effect edges.
// Visit goes through the control chain, visiting effectful nodes that it
// encounters.
void Visit(Node* node);
// Marks effect inputs for visiting, if we are able to update this path of
// the graph.
void VisitEffectfulNode(Node* node);
// Compute the intersection of the UnobservablesSets of all effect uses and
// return it.
// The result UnobservablesSet will never be null.
UnobservablesSet RecomputeUseIntersection(Node* node);
// Recompute unobservables-set for a node. Will also mark superfluous nodes
// as to be removed.
UnobservablesSet RecomputeSet(Node* node, const UnobservablesSet& uses);
// Returns true if node's opcode cannot observe StoreFields.
static bool CannotObserveStoreField(Node* node);
void MarkForRevisit(Node* node);
bool HasBeenVisited(Node* node);
// To safely cast an offset from a FieldAccess, which has a potentially
// wider range (namely int).
StoreOffset ToOffset(const FieldAccess& access) {
DCHECK_GE(access.offset, 0);
return static_cast<StoreOffset>(access.offset);
}
JSGraph* jsgraph() const { return jsgraph_; }
Isolate* isolate() { return jsgraph()->isolate(); }
Zone* temp_zone() const { return temp_zone_; }
UnobservablesSet& unobservable_for_id(NodeId id) {
DCHECK_LT(id, unobservable_.size());
return unobservable_[id];
}
ZoneSet<Node*>& to_remove() { return to_remove_; }
JSGraph* const jsgraph_;
TickCounter* const tick_counter_;
Zone* const temp_zone_;
ZoneStack<Node*> revisit_;
ZoneVector<bool> in_revisit_;
// Maps node IDs to UnobservableNodeSets.
ZoneVector<UnobservablesSet> unobservable_;
ZoneSet<Node*> to_remove_;
const UnobservablesSet unobservables_visited_empty_;
};
};
} // 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