Index: src/compiler/store-store-elimination.cc |
diff --git a/src/compiler/store-store-elimination.cc b/src/compiler/store-store-elimination.cc |
index cd0c75c297839edc59fe66a8751f4b8cecae41b0..75c88f811f94948b0d6447c3a1eb6cf824d0a29b 100644 |
--- a/src/compiler/store-store-elimination.cc |
+++ b/src/compiler/store-store-elimination.cc |
@@ -2,6 +2,8 @@ |
// 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/compiler/all-nodes.h" |
@@ -13,307 +15,570 @@ namespace v8 { |
namespace internal { |
namespace compiler { |
-#define TRACE(fmt, ...) \ |
- do { \ |
- if (FLAG_trace_store_elimination) { \ |
- PrintF("StoreStoreElimination::ReduceEligibleNode: " fmt "\n", \ |
- ##__VA_ARGS__); \ |
- } \ |
+#define TRACE(fmt, ...) \ |
+ do { \ |
+ if (FLAG_trace_store_elimination) { \ |
+ PrintF("RedundantStoreFinder: " fmt "\n", ##__VA_ARGS__); \ |
+ } \ |
} while (false) |
-// A simple store-store elimination. When the effect chain contains the |
-// following sequence, |
-// |
-// - StoreField[[+off_1]](x1, y1) |
-// - StoreField[[+off_2]](x2, y2) |
-// - StoreField[[+off_3]](x3, y3) |
-// ... |
-// - StoreField[[+off_n]](xn, yn) |
-// |
-// where the xes are the objects and the ys are the values to be stored, then |
-// we are going to say that a store is superfluous if the same offset of the |
-// same object will be stored to in the future. If off_i == off_j and xi == xj |
-// and i < j, then we optimize the i'th StoreField away. |
-// |
-// This optimization should be initiated on the last StoreField in such a |
-// sequence. |
-// |
-// The algorithm works by walking the effect chain from the last StoreField |
-// upwards. While walking, we maintain a map {futureStore} from offsets to |
-// nodes; initially it is empty. As we walk the effect chain upwards, if |
-// futureStore[off] = n, then any store to node {n} with offset {off} is |
-// guaranteed to be useless because we do a tagged-width[2] store to that |
-// offset of that object in the near future anyway. For example, for this |
-// effect chain |
-// |
-// 71: StoreField(60, 0) |
-// 72: StoreField(65, 8) |
-// 73: StoreField(63, 8) |
-// 74: StoreField(65, 16) |
-// 75: StoreField(62, 8) |
-// |
-// just before we get to 72, we will have futureStore = {8: 63, 16: 65}. |
-// |
-// Here is the complete process. |
-// |
-// - We are at the end of a sequence of consecutive StoreFields. |
-// - We start out with futureStore = empty. |
-// - We then walk the effect chain upwards to find the next StoreField [1]. |
+// CHECK_EXTRA is like CHECK, but has two or more arguments: a boolean |
+// expression, a format string, and any number of extra arguments. The boolean |
+// expression will be evaluated at runtime. If it evaluates to false, then an |
+// error message will be shown containing the condition, as well as the extra |
+// info formatted like with printf. |
+#define CHECK_EXTRA(condition, fmt, ...) \ |
+ do { \ |
+ if (V8_UNLIKELY(!(condition))) { \ |
+ V8_Fatal(__FILE__, __LINE__, "Check failed: %s. Extra info: " fmt, \ |
+ #condition, ##__VA_ARGS__); \ |
+ } \ |
+ } while (0) |
+ |
+#ifdef DEBUG |
+#define DCHECK_EXTRA(condition, fmt, ...) \ |
+ CHECK_EXTRA(condition, fmt, ##__VA_ARGS__) |
+#else |
+#define DCHECK_EXTRA(condition, fmt, ...) ((void)0) |
+#endif |
+ |
+// Store-store elimination. |
// |
-// 1. If the offset is not a key of {futureStore} yet, we put it in. |
-// 2. If the offset is a key of {futureStore}, but futureStore[offset] is a |
-// different node, we overwrite futureStore[offset] with the current node. |
-// 3. If the offset is a key of {futureStore} and futureStore[offset] equals |
-// this node, we eliminate this StoreField. |
+// The aim of this optimization is to detect the following pattern in the |
+// effect graph: |
// |
-// As long as the current effect input points to a node with a single effect |
-// output, and as long as its opcode is StoreField, we keep traversing |
-// upwards. |
+// - 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, ...) |
// |
-// footnotes: |
+// 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. |
// |
-// [1] We make sure that we only traverse the linear part, that is, the part |
-// where every node has exactly one incoming and one outgoing effect edge. |
-// Also, we only keep walking upwards as long as we keep finding consecutive |
-// StoreFields on the same node. |
+// 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. |
// |
-// [2] This optimization is sound only in certain cases. Specifically, the |
-// presence of a future store to {off} by itself does not automatically mean |
-// that earlier stores to {off} are superfluous: a future narrow store does |
-// not obviate an earlier wide store. However, future stores of a certain |
-// size do obviate stores to the same offset of lesser or equal size. |
-// |
-// It turns out that we are most interested in stores of "tagged" size, |
-// which is 8 bytes on 64-bit archs and 4 bit on 32-bit archs. In |
-// {futureStore}, we record future writes that are of at least this size. |
-// The three cases are actually a bit more subtle. |
+// This implementation needs all dead nodes removed from the graph, and the |
+// graph should be trimmed. |
+ |
+namespace { |
+ |
+// 16 bits was chosen fairly arbitrarily; it seems enough now. 8 bits is too |
+// few. |
+typedef uint16_t StoreOffset; |
+ |
+struct UnobservableStore { |
+ NodeId id_; |
+ StoreOffset offset_; |
+ |
+ bool operator==(const UnobservableStore) const; |
+ 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". |
// |
-// 1. If the offset is not a key of {futureStore} and the StoreField is of |
-// "tagged" size or wider, then we put it in. |
-// 2. If the offset is present in {futureStore} but the value is different, |
-// then we overwrite the value if the current StoreField is of "tagged" |
-// size or wider. |
-// 3. If the offset is present and the value matches the node, and the |
-// current StoreField is AT MOST of "tagged" size, then we eliminate this |
-// StoreField. |
+// 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. |
// |
-// Examples of stores that we do not detect as superfluous: 2-byte stores |
-// followed by 2-byte stores to the same offset; 16-byte stores followed by |
-// 16-byte stores to the same offset. On ia32, we do not detect consecutive |
-// float64 stores as superfluous, and on x86 we do not detect consecutive |
-// int32 stores as superfluous. |
- |
-// At a late stage, we realized that this code is more complicated than it |
-// needs to be: if we store a set of pairs (offset, node), the code simplifies |
-// to 3 cases instead of 6. We could even store a map from nodes to sets of |
-// bytes. |
- |
-StoreStoreElimination::StoreStoreElimination(JSGraph* js_graph, Zone* temp_zone) |
- : jsgraph_(js_graph), temp_zone_(temp_zone) {} |
- |
-StoreStoreElimination::~StoreStoreElimination() {} |
- |
-void StoreStoreElimination::Run() { |
- // The store-store elimination performs work on chains of certain types of |
- // nodes. The elimination must be invoked on the lowest node in such a |
- // chain; we have a helper function IsEligibleNode that returns true |
- // precisely on the lowest node in such a chain. |
- // |
- // Because the elimination removes nodes from the graph, even remove nodes |
- // that the elimination was not invoked on, we cannot use a normal |
- // AdvancedReducer but we manually find which nodes to invoke the |
- // elimination on. Then in a next step, we invoke the elimination for each |
- // node that was eligible. |
- |
- NodeVector eligible(temp_zone()); // loops over all nodes |
- AllNodes all(temp_zone(), jsgraph()->graph()); |
- |
- for (Node* node : all.reachable) { |
- if (IsEligibleNode(node)) { |
- eligible.push_back(node); |
- } |
+// 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) : set_(other.set_) {} |
+ |
+ UnobservablesSet Intersect(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()); |
} |
- for (Node* node : eligible) { |
- ReduceEligibleNode(node); |
- } |
-} |
+ 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 { |
-// 16 bits was chosen fairly arbitrarily; it seems enough now. 8 bits is too |
-// few. |
-typedef uint16_t Offset; |
+class RedundantStoreFinder final { |
+ public: |
+ RedundantStoreFinder(JSGraph* js_graph, Zone* temp_zone); |
+ |
+ void Find(); |
+ |
+ const ZoneSet<Node*>& to_remove_const() { return to_remove_; } |
+ |
+ virtual void Visit(Node* node); |
+ |
+ private: |
+ static bool IsEffectful(Node* node); |
+ void VisitEffectfulNode(Node* node); |
+ UnobservablesSet RecomputeUseIntersection(Node* node); |
+ UnobservablesSet RecomputeSet(Node* node, UnobservablesSet uses); |
+ static bool CannotObserveStoreField(Node* node); |
+ static bool CanObserveAnything(Node* node); |
+ |
+ void MarkForRevisit(Node* node); |
+ bool HasBeenVisited(Node* node); |
+ |
+ JSGraph* jsgraph() const { return jsgraph_; } |
+ 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_; |
+ 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 wider range |
// (namely int). |
-Offset ToOffset(int offset) { |
- CHECK(0 <= offset && offset < (1 << 8 * sizeof(Offset))); |
- return (Offset)offset; |
+StoreOffset ToOffset(int offset) { |
+ CHECK(0 <= offset && offset < (1 << 8 * sizeof(StoreOffset))); |
+ return (StoreOffset)offset; |
} |
-Offset ToOffset(const FieldAccess& access) { return ToOffset(access.offset); } |
+StoreOffset ToOffset(const FieldAccess& access) { |
+ return ToOffset(access.offset); |
+} |
-// If node has a single effect use, return that node. If node has no or |
-// multiple effect uses, return nullptr. |
-Node* SingleEffectUse(Node* node) { |
- Node* last_use = nullptr; |
- for (Edge edge : node->use_edges()) { |
- if (!NodeProperties::IsEffectEdge(edge)) { |
- continue; |
- } |
- if (last_use != nullptr) { |
- // more than one |
- return nullptr; |
+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() { |
+ Visit(jsgraph()->graph()->end()); |
+ |
+ while (!revisit_.empty()) { |
+ Node* next = revisit_.top(); |
+ revisit_.pop(); |
+ DCHECK_LT(next->id(), in_revisit_.size()); |
+ in_revisit_[next->id()] = false; |
+ Visit(next); |
+ } |
+ |
+#ifdef DEBUG |
+ // Check that we visited all the StoreFields |
+ AllNodes all(temp_zone(), jsgraph()->graph()); |
+ for (Node* node : all.reachable) { |
+ if (node->op()->opcode() == IrOpcode::kStoreField) { |
+ DCHECK_EXTRA(HasBeenVisited(node), "#%d:%s", node->id(), |
+ node->op()->mnemonic()); |
} |
- last_use = edge.from(); |
- DCHECK_NOT_NULL(last_use); |
} |
- return last_use; |
+#endif |
} |
-// Return true if node is the last consecutive StoreField node in a linear |
-// part of the effect chain. |
-bool IsEndOfStoreFieldChain(Node* node) { |
- Node* next_on_chain = SingleEffectUse(node); |
- return (next_on_chain == nullptr || |
- next_on_chain->op()->opcode() != IrOpcode::kStoreField); |
+void RedundantStoreFinder::MarkForRevisit(Node* node) { |
+ DCHECK_LT(node->id(), in_revisit_.size()); |
+ if (!in_revisit_[node->id()]) { |
+ revisit_.push(node); |
+ in_revisit_[node->id()] = true; |
+ } |
} |
-// The argument must be a StoreField node. If there is a node before it in the |
-// effect chain, and if this part of the effect chain is linear (no other |
-// effect uses of that previous node), then return that previous node. |
-// Otherwise, return nullptr. |
-// |
-// The returned node need not be a StoreField. |
-Node* PreviousEffectBeforeStoreField(Node* node) { |
- DCHECK_EQ(node->op()->opcode(), IrOpcode::kStoreField); |
- DCHECK_EQ(node->op()->EffectInputCount(), 1); |
- |
- Node* previous = NodeProperties::GetEffectInput(node); |
- if (previous != nullptr && node == SingleEffectUse(previous)) { |
- return previous; |
- } else { |
- return nullptr; |
+bool RedundantStoreFinder::HasBeenVisited(Node* node) { |
+ return !unobservable_for_id(node->id()).IsUnvisited(); |
+} |
+ |
+void StoreStoreElimination::Run(JSGraph* js_graph, Zone* temp_zone) { |
+ // Find superfluous nodes |
+ RedundantStoreFinder finder(js_graph, 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(); |
} |
} |
-size_t rep_size_of(MachineRepresentation rep) { |
- return ((size_t)1) << ElementSizeLog2Of(rep); |
+bool RedundantStoreFinder::IsEffectful(Node* node) { |
+ return (node->op()->EffectInputCount() >= 1); |
} |
-size_t rep_size_of(FieldAccess access) { |
- return rep_size_of(access.machine_type.representation()); |
+ |
+// Recompute unobservables-set for a node. Will also mark superfluous nodes |
+// as to be removed. |
+ |
+UnobservablesSet RedundantStoreFinder::RecomputeSet(Node* node, |
+ UnobservablesSet uses) { |
+ switch (node->op()->opcode()) { |
+ case IrOpcode::kStoreField: { |
+ Node* stored_to = node->InputAt(0); |
+ FieldAccess access = OpParameter<FieldAccess>(node->op()); |
+ StoreOffset offset = ToOffset(access); |
+ |
+ UnobservableStore observation = {stored_to->id(), offset}; |
+ bool isNotObservable = uses.Contains(observation); |
+ |
+ if (isNotObservable && AtMostTagged(access)) { |
+ 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)) { |
+ 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); |
+ FieldAccess access = OpParameter<FieldAccess>(node->op()); |
+ StoreOffset offset = ToOffset(access); |
+ |
+ TRACE( |
+ " #%d is LoadField[+%d,%s](#%d), removing all offsets [+%d] from " |
+ "set", |
+ node->id(), offset, |
+ MachineReprToString(access.machine_type.representation()), |
+ loaded_from->id(), offset); |
+ |
+ return uses.RemoveSameOffset(offset, temp_zone()); |
+ break; |
+ } |
+ default: |
+ if (CannotObserveStoreField(node)) { |
+ TRACE(" #%d:%s can observe nothing, set stays unchanged", node->id(), |
+ node->op()->mnemonic()); |
+ return uses; |
+ } else if (CanObserveAnything(node)) { |
+ TRACE(" #%d:%s can observe everything, recording empty set", |
+ node->id(), node->op()->mnemonic()); |
+ return unobservables_visited_empty_; |
+ } else { |
+ // It is safe to turn this check off in the future, but it is better |
+ // to list opcodes in CannotObserveStoreField, in CanObserveAnything, |
+ // or if you don't know, to add another case inside this DCHECK_EXTRA. |
+ DCHECK_EXTRA(node->op()->opcode() == IrOpcode::kCall, "%s", |
+ node->op()->mnemonic()); |
+ TRACE( |
+ " cannot determine unobservables-set for #%d:%s; " |
+ "conservatively recording empty set", |
+ node->id(), node->op()->mnemonic()); |
+ return unobservables_visited_empty_; |
+ } |
+ } |
+ UNREACHABLE(); |
+ return UnobservablesSet::Unvisited(); |
} |
-bool AtMostTagged(FieldAccess access) { |
- return rep_size_of(access) <= rep_size_of(MachineRepresentation::kTagged); |
+bool RedundantStoreFinder::CannotObserveStoreField(Node* node) { |
+ Operator::Properties mask = |
+ Operator::kNoRead | Operator::kNoDeopt | Operator::kNoThrow; |
+ |
+ return (node->op()->properties() & mask) == mask || |
+ node->opcode() == IrOpcode::kAllocate || |
+ node->opcode() == IrOpcode::kCheckedLoad || |
+ node->opcode() == IrOpcode::kLoadElement || |
+ node->opcode() == IrOpcode::kLoad; |
} |
-bool AtLeastTagged(FieldAccess access) { |
- return rep_size_of(access) >= rep_size_of(MachineRepresentation::kTagged); |
+bool RedundantStoreFinder::CanObserveAnything(Node* node) { |
+ return !node->op()->HasProperty(Operator::kNoThrow) || |
+ !node->op()->HasProperty(Operator::kNoDeopt); |
} |
-} // namespace |
+// Initialize unobservable_ with js_graph->graph->NodeCount() empty sets. |
+RedundantStoreFinder::RedundantStoreFinder(JSGraph* js_graph, Zone* temp_zone) |
+ : jsgraph_(js_graph), |
+ 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. |
+ |
+ if (!HasBeenVisited(node)) { |
+ for (int i = 0; i < node->op()->ControlInputCount(); i++) { |
+ Node* control_input = NodeProperties::GetControlInput(node, i); |
+ if (!HasBeenVisited(control_input)) { |
+ MarkForRevisit(control_input); |
+ } |
+ } |
+ } |
+ |
+ bool isEffectful = (node->op()->EffectInputCount() >= 1); |
+ if (isEffectful) { |
+ VisitEffectfulNode(node); |
+ DCHECK(HasBeenVisited(node)); |
+ } |
+ |
+ if (!HasBeenVisited(node)) { |
+ // Mark as visited. |
+ unobservable_for_id(node->id()) = unobservables_visited_empty_; |
+ } |
+} |
-bool StoreStoreElimination::IsEligibleNode(Node* node) { |
- return (node->op()->opcode() == IrOpcode::kStoreField) && |
- IsEndOfStoreFieldChain(node); |
+void 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); |
+ DCHECK(!before_set.IsUnvisited()); |
+ |
+ UnobservablesSet stored_for_node = unobservable_for_id(node->id()); |
+ bool cur_set_changed = |
+ (stored_for_node.IsUnvisited() || stored_for_node != before_set); |
+ if (!cur_set_changed) { |
+ // We will not be able to update the part of this chain above any more. |
+ // Exit. |
+ TRACE("+ No change: stabilized. Not visiting effect inputs."); |
+ } else { |
+ unobservable_for_id(node->id()) = before_set; |
+ |
+ // Mark effect inputs for visiting. |
+ for (int i = 0; i < node->op()->EffectInputCount(); i++) { |
+ Node* input = NodeProperties::GetEffectInput(node, i); |
+ if (!CanObserveAnything(input) || !HasBeenVisited(input)) { |
+ TRACE(" marking #%d:%s for revisit", input->id(), |
+ input->op()->mnemonic()); |
+ MarkForRevisit(input); |
+ } |
+ } |
+ } |
} |
-void StoreStoreElimination::ReduceEligibleNode(Node* node) { |
- DCHECK(IsEligibleNode(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) { |
+ // {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. |
- // if (FLAG_trace_store_elimination) { |
- // PrintF("** StoreStoreElimination::ReduceEligibleNode: activated: |
- // #%d\n", |
- // node->id()); |
- // } |
+ bool first = true; |
+ UnobservablesSet cur_set = UnobservablesSet::Unvisited(); // irrelevant |
- TRACE("activated: #%d", node->id()); |
+ for (Edge edge : node->use_edges()) { |
+ // Skip non-effect edges |
+ if (!NodeProperties::IsEffectEdge(edge)) { |
+ continue; |
+ } |
- // Initialize empty futureStore. |
- ZoneMap<Offset, Node*> futureStore(temp_zone()); |
+ Node* use = edge.from(); |
+ UnobservablesSet new_set = unobservable_for_id(use->id()); |
+ // Include new_set in the intersection. |
+ if (first) { |
+ // Intersection of a one-element set is that one element |
+ first = false; |
+ cur_set = new_set; |
+ } else { |
+ // Take the intersection of cur_set and new_set. |
+ cur_set = cur_set.Intersect(new_set, temp_zone()); |
+ } |
+ } |
- Node* current_node = node; |
+ 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_; |
+ } |
- do { |
- FieldAccess access = OpParameter<FieldAccess>(current_node->op()); |
- Offset offset = ToOffset(access); |
- Node* object_input = current_node->InputAt(0); |
+ return cur_set; |
+ } |
+} |
- Node* previous = PreviousEffectBeforeStoreField(current_node); |
+UnobservablesSet UnobservablesSet::Unvisited() { return UnobservablesSet(); } |
- // Find the map entry. |
- ZoneMap<Offset, Node*>::iterator find_result = futureStore.find(offset); |
+UnobservablesSet::UnobservablesSet() : set_(nullptr) {} |
- bool present = find_result != futureStore.end(); |
- Node* value = present ? find_result->second : 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. |
+ ZoneSet<UnobservableStore>* empty_set = |
+ new (zone->New(sizeof(ZoneSet<UnobservableStore>))) |
+ ZoneSet<UnobservableStore>(zone); |
+ return UnobservablesSet(empty_set); |
+} |
- if (present && value == object_input && AtMostTagged(access)) { |
- // Key was present, and the value equalled object_input. This means |
- // that soon after in the effect chain, we will do a StoreField to the |
- // same object with the same offset, therefore current_node can be |
- // optimized away. Also, the future StoreField is at least as big as this |
- // one. |
- // |
- // We don't need to update futureStore. |
+// Computes the intersection of two UnobservablesSets. May return |
+// UnobservablesSet::Unvisited() instead of an empty UnobservablesSet for |
+// speed. |
+UnobservablesSet UnobservablesSet::Intersect(UnobservablesSet other, |
+ Zone* zone) const { |
+ if (IsEmpty() || other.IsEmpty()) { |
+ return Unvisited(); |
+ } else { |
+ ZoneSet<UnobservableStore>* intersection = |
+ new (zone->New(sizeof(ZoneSet<UnobservableStore>))) |
+ ZoneSet<UnobservableStore>(zone); |
+ // Put the intersection of set() and other.set() in intersection. |
+ set_intersection(set()->begin(), set()->end(), other.set()->begin(), |
+ other.set()->end(), |
+ std::inserter(*intersection, intersection->end())); |
+ |
+ return UnobservablesSet(intersection); |
+ } |
+} |
- Node* previous_effect = NodeProperties::GetEffectInput(current_node); |
+UnobservablesSet UnobservablesSet::Add(UnobservableStore obs, |
+ Zone* zone) const { |
+ bool present = (set()->find(obs) != set()->end()); |
+ if (present) { |
+ return *this; |
+ } else { |
+ // Make a new empty set. |
+ ZoneSet<UnobservableStore>* new_set = |
+ new (zone->New(sizeof(ZoneSet<UnobservableStore>))) |
+ ZoneSet<UnobservableStore>(zone); |
+ // Copy the old elements over. |
+ *new_set = *set(); |
+ // Add the new element. |
+ bool inserted = new_set->insert(obs).second; |
+ DCHECK(inserted); |
+ USE(inserted); // silence warning about unused variable |
+ |
+ return UnobservablesSet(new_set); |
+ } |
+} |
- NodeProperties::ReplaceUses(current_node, nullptr, previous_effect, |
- nullptr, nullptr); |
- current_node->Kill(); |
- TRACE("#%d[[+%d,%s]](#%d) -- at most tagged size, eliminated", |
- current_node->id(), offset, |
- MachineReprToString(access.machine_type.representation()), |
- object_input->id()); |
- } else if (present && value == object_input && !AtMostTagged(access)) { |
- TRACE("#%d[[+%d,%s]](#%d) -- too wide, not eliminated", |
- current_node->id(), offset, |
- MachineReprToString(access.machine_type.representation()), |
- object_input->id()); |
- } else if (present && value != object_input && AtLeastTagged(access)) { |
- // Key was present, and the value did not equal object_input. This means |
- // that there is a StoreField to this offset in the future, but the |
- // object instance comes from a different Node. We pessimistically |
- // assume that we cannot optimize current_node away. However, we will |
- // guess that the current StoreField is more relevant than the future |
- // one, record the current StoreField in futureStore instead, and |
- // continue ascending up the chain. |
- find_result->second = object_input; |
- TRACE("#%d[[+%d,%s]](#%d) -- wide enough, diff object, updated in map", |
- current_node->id(), offset, |
- MachineReprToString(access.machine_type.representation()), |
- object_input->id()); |
- } else if (!present && AtLeastTagged(access)) { |
- // Key was not present. This means that there is no matching |
- // StoreField to this offset in the future, so we cannot optimize |
- // current_node away. However, we will record the current StoreField |
- // in futureStore, and continue ascending up the chain. |
- futureStore.insert(std::make_pair(offset, object_input)); |
- TRACE( |
- "#%d[[+%d,%s]](#%d) -- wide enough, key not present, inserted in map", |
- current_node->id(), offset, |
- MachineReprToString(access.machine_type.representation()), |
- object_input->id()); |
- } else if (!AtLeastTagged(access)) { |
- TRACE("#%d[[+%d,%s]](#%d) -- too narrow to record", current_node->id(), |
- offset, MachineReprToString(access.machine_type.representation()), |
- object_input->id()); |
- } else { |
- UNREACHABLE(); |
+UnobservablesSet UnobservablesSet::RemoveSameOffset(StoreOffset offset, |
+ Zone* zone) const { |
+ // Make a new empty set. |
+ ZoneSet<UnobservableStore>* new_set = |
+ new (zone->New(sizeof(ZoneSet<UnobservableStore>))) |
+ ZoneSet<UnobservableStore>(zone); |
+ // Copy all elements over that have a different offset. |
+ for (auto obs : *set()) { |
+ if (obs.offset_ != offset) { |
+ new_set->insert(obs); |
} |
+ } |
+ |
+ return UnobservablesSet(new_set); |
+} |
- // Regardless of whether we eliminated node {current}, we want to |
- // continue walking up the effect chain. |
+// 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_); |
+} |
- current_node = previous; |
- } while (current_node != nullptr && |
- current_node->op()->opcode() == IrOpcode::kStoreField); |
+bool UnobservableStore::operator!=(const UnobservableStore other) const { |
+ return !(*this == other); |
+} |
- TRACE("finished"); |
+bool UnobservableStore::operator<(const UnobservableStore other) const { |
+ return (id_ < other.id_) || (id_ == other.id_ && offset_ < other.offset_); |
} |
} // namespace compiler |