summaryrefslogtreecommitdiff
path: root/deps/v8/src/compiler/store-store-elimination.cc
diff options
context:
space:
mode:
Diffstat (limited to 'deps/v8/src/compiler/store-store-elimination.cc')
-rw-r--r--deps/v8/src/compiler/store-store-elimination.cc393
1 files changed, 86 insertions, 307 deletions
diff --git a/deps/v8/src/compiler/store-store-elimination.cc b/deps/v8/src/compiler/store-store-elimination.cc
index b71bcd7e66..bd53fb895f 100644
--- a/deps/v8/src/compiler/store-store-elimination.cc
+++ b/deps/v8/src/compiler/store-store-elimination.cc
@@ -10,7 +10,6 @@
#include "src/compiler/all-nodes.h"
#include "src/compiler/js-graph.h"
#include "src/compiler/node-properties.h"
-#include "src/compiler/simplified-operator.h"
namespace v8 {
namespace internal {
@@ -42,163 +41,7 @@ namespace compiler {
#define DCHECK_EXTRA(condition, fmt, ...) ((void)0)
#endif
-// Store-store elimination.
-//
-// The aim of this optimization is to detect the following pattern in the
-// effect graph:
-//
-// - StoreField[+24, kRepTagged](263, ...)
-//
-// ... lots of nodes from which the field at offset 24 of the object
-// returned by node #263 cannot be observed ...
-//
-// - StoreField[+24, kRepTagged](263, ...)
-//
-// In such situations, the earlier StoreField cannot be observed, and can be
-// eliminated. This optimization should work for any offset and input node, of
-// course.
-//
-// The optimization also works across splits. It currently does not work for
-// loops, because we tend to put a stack check in loops, and like deopts,
-// stack checks can observe anything.
-
-// Assumption: every byte of a JS object is only ever accessed through one
-// offset. For instance, byte 15 of a given object may be accessed using a
-// two-byte read at offset 14, or a four-byte read at offset 12, but never
-// both in the same program.
-//
-// This implementation needs all dead nodes removed from the graph, and the
-// graph should be trimmed.
-
-namespace {
-
-using StoreOffset = uint32_t;
-
-struct UnobservableStore {
- NodeId id_;
- StoreOffset offset_;
-
- bool operator==(const UnobservableStore) const;
- bool operator<(const UnobservableStore) const;
-};
-
-} // namespace
-
-namespace {
-
-// 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:
- static UnobservablesSet Unvisited();
- static UnobservablesSet VisitedEmpty(Zone* zone);
- UnobservablesSet(); // unvisited
- UnobservablesSet(const UnobservablesSet& other) V8_NOEXCEPT = default;
-
- UnobservablesSet Intersect(const UnobservablesSet& other, Zone* zone) const;
- UnobservablesSet Add(UnobservableStore obs, Zone* zone) const;
- 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&) const;
- bool operator!=(const UnobservablesSet&) const;
-
- private:
- explicit UnobservablesSet(const ZoneSet<UnobservableStore>* set)
- : set_(set) {}
- const ZoneSet<UnobservableStore>* set_;
-};
-
-} // namespace
-
-namespace {
-
-class RedundantStoreFinder final {
- public:
- RedundantStoreFinder(JSGraph* js_graph, TickCounter* tick_counter,
- Zone* temp_zone);
-
- void Find();
-
- const ZoneSet<Node*>& to_remove_const() { return to_remove_; }
-
- void Visit(Node* node);
-
- private:
- void VisitEffectfulNode(Node* node);
- UnobservablesSet RecomputeUseIntersection(Node* node);
- UnobservablesSet RecomputeSet(Node* node, const UnobservablesSet& uses);
- static bool CannotObserveStoreField(Node* node);
-
- void MarkForRevisit(Node* node);
- bool HasBeenVisited(Node* node);
-
- JSGraph* jsgraph() const { return jsgraph_; }
- Isolate* isolate() { return jsgraph()->isolate(); }
- Zone* temp_zone() const { return temp_zone_; }
- ZoneVector<UnobservablesSet>& unobservable() { return unobservable_; }
- 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_;
-};
-
-// To safely cast an offset from a FieldAccess, which has a potentially wider
-// range (namely int).
-StoreOffset ToOffset(int offset) {
- CHECK_LE(0, offset);
- return static_cast<StoreOffset>(offset);
-}
-
-StoreOffset ToOffset(const FieldAccess& access) {
- return ToOffset(access.offset);
-}
-
-unsigned int RepSizeOf(MachineRepresentation rep) {
- return 1u << ElementSizeLog2Of(rep);
-}
-unsigned int RepSizeOf(FieldAccess access) {
- return RepSizeOf(access.machine_type.representation());
-}
-
-bool AtMostTagged(FieldAccess access) {
- return RepSizeOf(access) <= RepSizeOf(MachineRepresentation::kTagged);
-}
-
-bool AtLeastTagged(FieldAccess access) {
- return RepSizeOf(access) >= RepSizeOf(MachineRepresentation::kTagged);
-}
-
-} // namespace
-
-void RedundantStoreFinder::Find() {
+void StoreStoreElimination::RedundantStoreFinder::Find() {
Visit(jsgraph()->graph()->end());
while (!revisit_.empty()) {
@@ -222,7 +65,7 @@ void RedundantStoreFinder::Find() {
#endif
}
-void RedundantStoreFinder::MarkForRevisit(Node* node) {
+void StoreStoreElimination::RedundantStoreFinder::MarkForRevisit(Node* node) {
DCHECK_LT(node->id(), in_revisit_.size());
if (!in_revisit_[node->id()]) {
revisit_.push(node);
@@ -230,7 +73,7 @@ void RedundantStoreFinder::MarkForRevisit(Node* node) {
}
}
-bool RedundantStoreFinder::HasBeenVisited(Node* node) {
+bool StoreStoreElimination::RedundantStoreFinder::HasBeenVisited(Node* node) {
return !unobservable_for_id(node->id()).IsUnvisited();
}
@@ -241,7 +84,6 @@ void StoreStoreElimination::Run(JSGraph* js_graph, TickCounter* tick_counter,
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",
@@ -254,11 +96,9 @@ void StoreStoreElimination::Run(JSGraph* js_graph, TickCounter* tick_counter,
}
}
-// Recompute unobservables-set for a node. Will also mark superfluous nodes
-// as to be removed.
-
-UnobservablesSet RedundantStoreFinder::RecomputeSet(
- Node* node, const UnobservablesSet& uses) {
+StoreStoreElimination::UnobservablesSet
+StoreStoreElimination::RedundantStoreFinder::RecomputeSet(
+ Node* node, const StoreStoreElimination::UnobservablesSet& uses) {
switch (node->op()->opcode()) {
case IrOpcode::kStoreField: {
Node* stored_to = node->InputAt(0);
@@ -266,40 +106,21 @@ UnobservablesSet RedundantStoreFinder::RecomputeSet(
StoreOffset offset = ToOffset(access);
UnobservableStore observation = {stored_to->id(), offset};
- bool isNotObservable = uses.Contains(observation);
+ bool is_not_observable = uses.Contains(observation);
- if (isNotObservable && AtMostTagged(access)) {
+ if (is_not_observable) {
TRACE(" #%d is StoreField[+%d,%s](#%d), unobservable", node->id(),
offset, MachineReprToString(access.machine_type.representation()),
stored_to->id());
to_remove().insert(node);
return uses;
- } else if (isNotObservable && !AtMostTagged(access)) {
- TRACE(
- " #%d is StoreField[+%d,%s](#%d), repeated in future but too "
- "big to optimize away",
- node->id(), offset,
- MachineReprToString(access.machine_type.representation()),
- stored_to->id());
- return uses;
- } else if (!isNotObservable && AtLeastTagged(access)) {
+ } else {
TRACE(" #%d is StoreField[+%d,%s](#%d), observable, recording in set",
node->id(), offset,
MachineReprToString(access.machine_type.representation()),
stored_to->id());
return uses.Add(observation, temp_zone());
- } else if (!isNotObservable && !AtLeastTagged(access)) {
- TRACE(
- " #%d is StoreField[+%d,%s](#%d), observable but too small to "
- "record",
- node->id(), offset,
- MachineReprToString(access.machine_type.representation()),
- stored_to->id());
- return uses;
- } else {
- UNREACHABLE();
}
- break;
}
case IrOpcode::kLoadField: {
Node* loaded_from = node->InputAt(0);
@@ -314,7 +135,6 @@ UnobservablesSet RedundantStoreFinder::RecomputeSet(
loaded_from->id(), offset);
return uses.RemoveSameOffset(offset, temp_zone());
- break;
}
default:
if (CannotObserveStoreField(node)) {
@@ -330,36 +150,16 @@ UnobservablesSet RedundantStoreFinder::RecomputeSet(
UNREACHABLE();
}
-bool RedundantStoreFinder::CannotObserveStoreField(Node* node) {
- return node->opcode() == IrOpcode::kLoadElement ||
- node->opcode() == IrOpcode::kLoad ||
- node->opcode() == IrOpcode::kStore ||
- node->opcode() == IrOpcode::kEffectPhi ||
- node->opcode() == IrOpcode::kStoreElement ||
- node->opcode() == IrOpcode::kUnsafePointerAdd ||
- node->opcode() == IrOpcode::kRetain;
+bool StoreStoreElimination::RedundantStoreFinder::CannotObserveStoreField(
+ Node* node) {
+ IrOpcode::Value opcode = node->opcode();
+ return opcode == IrOpcode::kLoadElement || opcode == IrOpcode::kLoad ||
+ opcode == IrOpcode::kStore || opcode == IrOpcode::kEffectPhi ||
+ opcode == IrOpcode::kStoreElement ||
+ opcode == IrOpcode::kUnsafePointerAdd || opcode == IrOpcode::kRetain;
}
-// Initialize unobservable_ with js_graph->graph->NodeCount() empty sets.
-RedundantStoreFinder::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)) {}
-
-void RedundantStoreFinder::Visit(Node* node) {
- // All effectful nodes should be reachable from End via a sequence of
- // control, then a sequence of effect edges. In VisitEffectfulNode we mark
- // all effect inputs for revisiting (if they might have stale state); here
- // we mark all control inputs at least once.
-
+void StoreStoreElimination::RedundantStoreFinder::Visit(Node* node) {
if (!HasBeenVisited(node)) {
for (int i = 0; i < node->op()->ControlInputCount(); i++) {
Node* control_input = NodeProperties::GetControlInput(node, i);
@@ -369,29 +169,32 @@ void RedundantStoreFinder::Visit(Node* node) {
}
}
- bool isEffectful = (node->op()->EffectInputCount() >= 1);
- if (isEffectful) {
+ bool is_effectful = node->op()->EffectInputCount() >= 1;
+ if (is_effectful) {
+ // mark all effect inputs for revisiting (if they might have stale state).
VisitEffectfulNode(node);
DCHECK(HasBeenVisited(node));
- }
-
- if (!HasBeenVisited(node)) {
+ } else if (!HasBeenVisited(node)) {
// Mark as visited.
unobservable_for_id(node->id()) = unobservables_visited_empty_;
}
}
-void RedundantStoreFinder::VisitEffectfulNode(Node* node) {
+void StoreStoreElimination::RedundantStoreFinder::VisitEffectfulNode(
+ Node* node) {
if (HasBeenVisited(node)) {
TRACE("- Revisiting: #%d:%s", node->id(), node->op()->mnemonic());
}
- UnobservablesSet after_set = RecomputeUseIntersection(node);
- UnobservablesSet before_set = RecomputeSet(node, after_set);
+ StoreStoreElimination::UnobservablesSet after_set =
+ RecomputeUseIntersection(node);
+ StoreStoreElimination::UnobservablesSet before_set =
+ RecomputeSet(node, after_set);
DCHECK(!before_set.IsUnvisited());
- UnobservablesSet stored_for_node = unobservable_for_id(node->id());
+ StoreStoreElimination::UnobservablesSet stores_for_node =
+ unobservable_for_id(node->id());
bool cur_set_changed =
- (stored_for_node.IsUnvisited() || stored_for_node != before_set);
+ stores_for_node.IsUnvisited() || stores_for_node != before_set;
if (!cur_set_changed) {
// We will not be able to update the part of this chain above any more.
// Exit.
@@ -409,81 +212,78 @@ void RedundantStoreFinder::VisitEffectfulNode(Node* node) {
}
}
-// Compute the intersection of the UnobservablesSets of all effect uses and
-// return it. This function only works if {node} has an effect use.
-//
-// The result UnobservablesSet will always be visited.
-UnobservablesSet RedundantStoreFinder::RecomputeUseIntersection(Node* node) {
+StoreStoreElimination::UnobservablesSet
+StoreStoreElimination::RedundantStoreFinder::RecomputeUseIntersection(
+ Node* node) {
+ // There were no effect uses. Break early.
+ if (node->op()->EffectOutputCount() == 0) {
+ IrOpcode::Value opcode = node->opcode();
+ // List of opcodes that may end this effect chain. The opcodes are not
+ // important to the soundness of this optimization; this serves as a
+ // general sanity check. Add opcodes to this list as it suits you.
+ //
+ // Everything is observable after these opcodes; return the empty set.
+ DCHECK_EXTRA(
+ opcode == IrOpcode::kReturn || opcode == IrOpcode::kTerminate ||
+ opcode == IrOpcode::kDeoptimize || opcode == IrOpcode::kThrow,
+ "for #%d:%s", node->id(), node->op()->mnemonic());
+ USE(opcode);
+
+ return unobservables_visited_empty_;
+ }
+
// {first} == true indicates that we haven't looked at any elements yet.
// {first} == false indicates that cur_set is the intersection of at least one
// thing.
-
bool first = true;
- UnobservablesSet cur_set = UnobservablesSet::Unvisited(); // irrelevant
-
+ StoreStoreElimination::UnobservablesSet cur_set =
+ StoreStoreElimination::UnobservablesSet::Unvisited(); // irrelevant
for (Edge edge : node->use_edges()) {
- // Skip non-effect edges
if (!NodeProperties::IsEffectEdge(edge)) {
continue;
}
+ // Intersect with the new use node.
Node* use = edge.from();
- UnobservablesSet new_set = unobservable_for_id(use->id());
- // Include new_set in the intersection.
+ StoreStoreElimination::UnobservablesSet new_set =
+ unobservable_for_id(use->id());
if (first) {
- // Intersection of a one-element set is that one element
first = false;
cur_set = new_set;
+ if (cur_set.IsUnvisited()) {
+ cur_set = unobservables_visited_empty_;
+ }
} else {
- // Take the intersection of cur_set and new_set.
- cur_set = cur_set.Intersect(new_set, temp_zone());
+ cur_set =
+ cur_set.Intersect(new_set, unobservables_visited_empty_, temp_zone());
}
- }
- if (first) {
- // There were no effect uses.
- auto opcode = node->op()->opcode();
- // List of opcodes that may end this effect chain. The opcodes are not
- // important to the soundness of this optimization; this serves as a
- // general sanity check. Add opcodes to this list as it suits you.
- //
- // Everything is observable after these opcodes; return the empty set.
- DCHECK_EXTRA(
- opcode == IrOpcode::kReturn || opcode == IrOpcode::kTerminate ||
- opcode == IrOpcode::kDeoptimize || opcode == IrOpcode::kThrow,
- "for #%d:%s", node->id(), node->op()->mnemonic());
- USE(opcode); // silence warning about unused variable in release mode
-
- return unobservables_visited_empty_;
- } else {
- if (cur_set.IsUnvisited()) {
- cur_set = unobservables_visited_empty_;
+ // Break fast for the empty set since the intersection will always be empty.
+ if (cur_set.IsEmpty()) {
+ break;
}
-
- return cur_set;
}
-}
-UnobservablesSet UnobservablesSet::Unvisited() { return UnobservablesSet(); }
+ DCHECK(!cur_set.IsUnvisited());
+ return cur_set;
+}
-UnobservablesSet::UnobservablesSet() : set_(nullptr) {}
+StoreStoreElimination::UnobservablesSet::UnobservablesSet() : set_(nullptr) {}
-UnobservablesSet UnobservablesSet::VisitedEmpty(Zone* zone) {
- // Create a new empty UnobservablesSet. This allocates in the zone, and
- // can probably be optimized to use a global singleton.
+StoreStoreElimination::UnobservablesSet
+StoreStoreElimination::UnobservablesSet::VisitedEmpty(Zone* zone) {
ZoneSet<UnobservableStore>* empty_set =
new (zone->New(sizeof(ZoneSet<UnobservableStore>)))
ZoneSet<UnobservableStore>(zone);
- return UnobservablesSet(empty_set);
+ return StoreStoreElimination::UnobservablesSet(empty_set);
}
-// Computes the intersection of two UnobservablesSets. May return
-// UnobservablesSet::Unvisited() instead of an empty UnobservablesSet for
-// speed.
-UnobservablesSet UnobservablesSet::Intersect(const UnobservablesSet& other,
- Zone* zone) const {
+StoreStoreElimination::UnobservablesSet
+StoreStoreElimination::UnobservablesSet::Intersect(
+ const StoreStoreElimination::UnobservablesSet& other,
+ const StoreStoreElimination::UnobservablesSet& empty, Zone* zone) const {
if (IsEmpty() || other.IsEmpty()) {
- return Unvisited();
+ return empty;
} else {
ZoneSet<UnobservableStore>* intersection =
new (zone->New(sizeof(ZoneSet<UnobservableStore>)))
@@ -493,14 +293,15 @@ UnobservablesSet UnobservablesSet::Intersect(const UnobservablesSet& other,
other.set()->end(),
std::inserter(*intersection, intersection->end()));
- return UnobservablesSet(intersection);
+ return StoreStoreElimination::UnobservablesSet(intersection);
}
}
-UnobservablesSet UnobservablesSet::Add(UnobservableStore obs,
- Zone* zone) const {
- bool present = (set()->find(obs) != set()->end());
- if (present) {
+StoreStoreElimination::UnobservablesSet
+StoreStoreElimination::UnobservablesSet::Add(UnobservableStore obs,
+ Zone* zone) const {
+ bool found = set()->find(obs) != set()->end();
+ if (found) {
return *this;
} else {
// Make a new empty set.
@@ -514,12 +315,13 @@ UnobservablesSet UnobservablesSet::Add(UnobservableStore obs,
DCHECK(inserted);
USE(inserted); // silence warning about unused variable
- return UnobservablesSet(new_set);
+ return StoreStoreElimination::UnobservablesSet(new_set);
}
}
-UnobservablesSet UnobservablesSet::RemoveSameOffset(StoreOffset offset,
- Zone* zone) const {
+StoreStoreElimination::UnobservablesSet
+StoreStoreElimination::UnobservablesSet::RemoveSameOffset(StoreOffset offset,
+ Zone* zone) const {
// Make a new empty set.
ZoneSet<UnobservableStore>* new_set =
new (zone->New(sizeof(ZoneSet<UnobservableStore>)))
@@ -531,30 +333,7 @@ UnobservablesSet UnobservablesSet::RemoveSameOffset(StoreOffset offset,
}
}
- return UnobservablesSet(new_set);
-}
-
-// Used for debugging.
-bool UnobservablesSet::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 UnobservablesSet::operator!=(const UnobservablesSet& other) const {
- return !(*this == other);
-}
-
-bool UnobservableStore::operator==(const UnobservableStore other) const {
- return (id_ == other.id_) && (offset_ == other.offset_);
-}
-
-
-bool UnobservableStore::operator<(const UnobservableStore other) const {
- return (id_ < other.id_) || (id_ == other.id_ && offset_ < other.offset_);
+ return StoreStoreElimination::UnobservablesSet(new_set);
}
#undef TRACE