OLD | NEW |
1 // Copyright 2016 the V8 project authors. All rights reserved. | 1 // Copyright 2016 the V8 project authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
| 5 #include <iterator> |
| 6 |
5 #include "src/compiler/store-store-elimination.h" | 7 #include "src/compiler/store-store-elimination.h" |
6 | 8 |
7 #include "src/compiler/all-nodes.h" | 9 #include "src/compiler/all-nodes.h" |
8 #include "src/compiler/js-graph.h" | 10 #include "src/compiler/js-graph.h" |
9 #include "src/compiler/node-properties.h" | 11 #include "src/compiler/node-properties.h" |
10 #include "src/compiler/simplified-operator.h" | 12 #include "src/compiler/simplified-operator.h" |
11 | 13 |
12 namespace v8 { | 14 namespace v8 { |
13 namespace internal { | 15 namespace internal { |
14 namespace compiler { | 16 namespace compiler { |
15 | 17 |
16 #define TRACE(fmt, ...) \ | 18 #define TRACE(fmt, ...) \ |
17 do { \ | 19 do { \ |
18 if (FLAG_trace_store_elimination) { \ | 20 if (FLAG_trace_store_elimination) { \ |
19 PrintF("StoreStoreElimination::ReduceEligibleNode: " fmt "\n", \ | 21 PrintF("RedundantStoreFinder: " fmt "\n", ##__VA_ARGS__); \ |
20 ##__VA_ARGS__); \ | 22 } \ |
21 } \ | |
22 } while (false) | 23 } while (false) |
23 | 24 |
24 // A simple store-store elimination. When the effect chain contains the | 25 // CHECK_EXTRA is like CHECK, but has two or more arguments: a boolean |
25 // following sequence, | 26 // expression, a format string, and any number of extra arguments. The boolean |
26 // | 27 // expression will be evaluated at runtime. If it evaluates to false, then an |
27 // - StoreField[[+off_1]](x1, y1) | 28 // error message will be shown containing the condition, as well as the extra |
28 // - StoreField[[+off_2]](x2, y2) | 29 // info formatted like with printf. |
29 // - StoreField[[+off_3]](x3, y3) | 30 #define CHECK_EXTRA(condition, fmt, ...) \ |
30 // ... | 31 do { \ |
31 // - StoreField[[+off_n]](xn, yn) | 32 if (V8_UNLIKELY(!(condition))) { \ |
32 // | 33 V8_Fatal(__FILE__, __LINE__, "Check failed: %s. Extra info: " fmt, \ |
33 // where the xes are the objects and the ys are the values to be stored, then | 34 #condition, ##__VA_ARGS__); \ |
34 // we are going to say that a store is superfluous if the same offset of the | 35 } \ |
35 // same object will be stored to in the future. If off_i == off_j and xi == xj | 36 } while (0) |
36 // and i < j, then we optimize the i'th StoreField away. | 37 |
37 // | 38 #ifdef DEBUG |
38 // This optimization should be initiated on the last StoreField in such a | 39 #define DCHECK_EXTRA(condition, fmt, ...) \ |
39 // sequence. | 40 CHECK_EXTRA(condition, fmt, ##__VA_ARGS__) |
40 // | 41 #else |
41 // The algorithm works by walking the effect chain from the last StoreField | 42 #define DCHECK_EXTRA(condition, fmt, ...) ((void)0) |
42 // upwards. While walking, we maintain a map {futureStore} from offsets to | 43 #endif |
43 // nodes; initially it is empty. As we walk the effect chain upwards, if | 44 |
44 // futureStore[off] = n, then any store to node {n} with offset {off} is | 45 // Store-store elimination. |
45 // guaranteed to be useless because we do a tagged-width[2] store to that | 46 // |
46 // offset of that object in the near future anyway. For example, for this | 47 // The aim of this optimization is to detect the following pattern in the |
47 // effect chain | 48 // effect graph: |
48 // | 49 // |
49 // 71: StoreField(60, 0) | 50 // - StoreField[+24, kRepTagged](263, ...) |
50 // 72: StoreField(65, 8) | 51 // |
51 // 73: StoreField(63, 8) | 52 // ... lots of nodes from which the field at offset 24 of the object |
52 // 74: StoreField(65, 16) | 53 // returned by node #263 cannot be observed ... |
53 // 75: StoreField(62, 8) | 54 // |
54 // | 55 // - StoreField[+24, kRepTagged](263, ...) |
55 // just before we get to 72, we will have futureStore = {8: 63, 16: 65}. | 56 // |
56 // | 57 // In such situations, the earlier StoreField cannot be observed, and can be |
57 // Here is the complete process. | 58 // eliminated. This optimization should work for any offset and input node, of |
58 // | 59 // course. |
59 // - We are at the end of a sequence of consecutive StoreFields. | 60 // |
60 // - We start out with futureStore = empty. | 61 // The optimization also works across splits. It currently does not work for |
61 // - We then walk the effect chain upwards to find the next StoreField [1]. | 62 // loops, because we tend to put a stack check in loops, and like deopts, |
62 // | 63 // stack checks can observe anything. |
63 // 1. If the offset is not a key of {futureStore} yet, we put it in. | 64 |
64 // 2. If the offset is a key of {futureStore}, but futureStore[offset] is a | 65 // Assumption: every byte of a JS object is only ever accessed through one |
65 // different node, we overwrite futureStore[offset] with the current node. | 66 // offset. For instance, byte 15 of a given object may be accessed using a |
66 // 3. If the offset is a key of {futureStore} and futureStore[offset] equals | 67 // two-byte read at offset 14, or a four-byte read at offset 12, but never |
67 // this node, we eliminate this StoreField. | 68 // both in the same program. |
68 // | 69 // |
69 // As long as the current effect input points to a node with a single effect | 70 // This implementation needs all dead nodes removed from the graph, and the |
70 // output, and as long as its opcode is StoreField, we keep traversing | 71 // graph should be trimmed. |
71 // upwards. | |
72 // | |
73 // | |
74 // | |
75 // footnotes: | |
76 // | |
77 // [1] We make sure that we only traverse the linear part, that is, the part | |
78 // where every node has exactly one incoming and one outgoing effect edge. | |
79 // Also, we only keep walking upwards as long as we keep finding consecutive | |
80 // StoreFields on the same node. | |
81 // | |
82 // [2] This optimization is sound only in certain cases. Specifically, the | |
83 // presence of a future store to {off} by itself does not automatically mean | |
84 // that earlier stores to {off} are superfluous: a future narrow store does | |
85 // not obviate an earlier wide store. However, future stores of a certain | |
86 // size do obviate stores to the same offset of lesser or equal size. | |
87 // | |
88 // It turns out that we are most interested in stores of "tagged" size, | |
89 // which is 8 bytes on 64-bit archs and 4 bit on 32-bit archs. In | |
90 // {futureStore}, we record future writes that are of at least this size. | |
91 // The three cases are actually a bit more subtle. | |
92 // | |
93 // 1. If the offset is not a key of {futureStore} and the StoreField is of | |
94 // "tagged" size or wider, then we put it in. | |
95 // 2. If the offset is present in {futureStore} but the value is different, | |
96 // then we overwrite the value if the current StoreField is of "tagged" | |
97 // size or wider. | |
98 // 3. If the offset is present and the value matches the node, and the | |
99 // current StoreField is AT MOST of "tagged" size, then we eliminate this | |
100 // StoreField. | |
101 // | |
102 // Examples of stores that we do not detect as superfluous: 2-byte stores | |
103 // followed by 2-byte stores to the same offset; 16-byte stores followed by | |
104 // 16-byte stores to the same offset. On ia32, we do not detect consecutive | |
105 // float64 stores as superfluous, and on x86 we do not detect consecutive | |
106 // int32 stores as superfluous. | |
107 | |
108 // At a late stage, we realized that this code is more complicated than it | |
109 // needs to be: if we store a set of pairs (offset, node), the code simplifies | |
110 // to 3 cases instead of 6. We could even store a map from nodes to sets of | |
111 // bytes. | |
112 | |
113 StoreStoreElimination::StoreStoreElimination(JSGraph* js_graph, Zone* temp_zone) | |
114 : jsgraph_(js_graph), temp_zone_(temp_zone) {} | |
115 | |
116 StoreStoreElimination::~StoreStoreElimination() {} | |
117 | |
118 void StoreStoreElimination::Run() { | |
119 // The store-store elimination performs work on chains of certain types of | |
120 // nodes. The elimination must be invoked on the lowest node in such a | |
121 // chain; we have a helper function IsEligibleNode that returns true | |
122 // precisely on the lowest node in such a chain. | |
123 // | |
124 // Because the elimination removes nodes from the graph, even remove nodes | |
125 // that the elimination was not invoked on, we cannot use a normal | |
126 // AdvancedReducer but we manually find which nodes to invoke the | |
127 // elimination on. Then in a next step, we invoke the elimination for each | |
128 // node that was eligible. | |
129 | |
130 NodeVector eligible(temp_zone()); // loops over all nodes | |
131 AllNodes all(temp_zone(), jsgraph()->graph()); | |
132 | |
133 for (Node* node : all.reachable) { | |
134 if (IsEligibleNode(node)) { | |
135 eligible.push_back(node); | |
136 } | |
137 } | |
138 | |
139 for (Node* node : eligible) { | |
140 ReduceEligibleNode(node); | |
141 } | |
142 } | |
143 | 72 |
144 namespace { | 73 namespace { |
145 | 74 |
146 // 16 bits was chosen fairly arbitrarily; it seems enough now. 8 bits is too | 75 // 16 bits was chosen fairly arbitrarily; it seems enough now. 8 bits is too |
147 // few. | 76 // few. |
148 typedef uint16_t Offset; | 77 typedef uint16_t StoreOffset; |
| 78 |
| 79 struct UnobservableStore { |
| 80 NodeId id_; |
| 81 StoreOffset offset_; |
| 82 |
| 83 bool operator==(const UnobservableStore) const; |
| 84 bool operator!=(const UnobservableStore) const; |
| 85 bool operator<(const UnobservableStore) const; |
| 86 }; |
| 87 |
| 88 } // namespace |
| 89 |
| 90 namespace { |
| 91 |
| 92 // Instances of UnobservablesSet are immutable. They represent either a set of |
| 93 // UnobservableStores, or the "unvisited empty set". |
| 94 // |
| 95 // We apply some sharing to save memory. The class UnobservablesSet is only a |
| 96 // pointer wide, and a copy does not use any heap (or temp_zone) memory. Most |
| 97 // changes to an UnobservablesSet might allocate in the temp_zone. |
| 98 // |
| 99 // The size of an instance should be the size of a pointer, plus additional |
| 100 // space in the zone in the case of non-unvisited UnobservablesSets. Copying |
| 101 // an UnobservablesSet allocates no memory. |
| 102 class UnobservablesSet final { |
| 103 public: |
| 104 static UnobservablesSet Unvisited(); |
| 105 static UnobservablesSet VisitedEmpty(Zone* zone); |
| 106 UnobservablesSet(); // unvisited |
| 107 UnobservablesSet(const UnobservablesSet& other) : set_(other.set_) {} |
| 108 |
| 109 UnobservablesSet Intersect(UnobservablesSet other, Zone* zone) const; |
| 110 UnobservablesSet Add(UnobservableStore obs, Zone* zone) const; |
| 111 UnobservablesSet RemoveSameOffset(StoreOffset off, Zone* zone) const; |
| 112 |
| 113 const ZoneSet<UnobservableStore>* set() const { return set_; } |
| 114 |
| 115 bool IsUnvisited() const { return set_ == nullptr; } |
| 116 bool IsEmpty() const { return set_ == nullptr || set_->empty(); } |
| 117 bool Contains(UnobservableStore obs) const { |
| 118 return set_ != nullptr && (set_->find(obs) != set_->end()); |
| 119 } |
| 120 |
| 121 bool operator==(const UnobservablesSet&) const; |
| 122 bool operator!=(const UnobservablesSet&) const; |
| 123 |
| 124 private: |
| 125 explicit UnobservablesSet(const ZoneSet<UnobservableStore>* set) |
| 126 : set_(set) {} |
| 127 const ZoneSet<UnobservableStore>* set_; |
| 128 }; |
| 129 |
| 130 } // namespace |
| 131 |
| 132 namespace { |
| 133 |
| 134 class RedundantStoreFinder final { |
| 135 public: |
| 136 RedundantStoreFinder(JSGraph* js_graph, Zone* temp_zone); |
| 137 |
| 138 void Find(); |
| 139 |
| 140 const ZoneSet<Node*>& to_remove_const() { return to_remove_; } |
| 141 |
| 142 virtual void Visit(Node* node); |
| 143 |
| 144 private: |
| 145 static bool IsEffectful(Node* node); |
| 146 void VisitEffectfulNode(Node* node); |
| 147 UnobservablesSet RecomputeUseIntersection(Node* node); |
| 148 UnobservablesSet RecomputeSet(Node* node, UnobservablesSet uses); |
| 149 static bool CannotObserveStoreField(Node* node); |
| 150 static bool CanObserveAnything(Node* node); |
| 151 |
| 152 void MarkForRevisit(Node* node); |
| 153 bool HasBeenVisited(Node* node); |
| 154 |
| 155 JSGraph* jsgraph() const { return jsgraph_; } |
| 156 Zone* temp_zone() const { return temp_zone_; } |
| 157 ZoneVector<UnobservablesSet>& unobservable() { return unobservable_; } |
| 158 UnobservablesSet& unobservable_for_id(NodeId id) { |
| 159 DCHECK_LT(id, unobservable().size()); |
| 160 return unobservable()[id]; |
| 161 } |
| 162 ZoneSet<Node*>& to_remove() { return to_remove_; } |
| 163 |
| 164 JSGraph* const jsgraph_; |
| 165 Zone* const temp_zone_; |
| 166 |
| 167 ZoneStack<Node*> revisit_; |
| 168 ZoneVector<bool> in_revisit_; |
| 169 // Maps node IDs to UnobservableNodeSets. |
| 170 ZoneVector<UnobservablesSet> unobservable_; |
| 171 ZoneSet<Node*> to_remove_; |
| 172 const UnobservablesSet unobservables_visited_empty_; |
| 173 }; |
149 | 174 |
150 // To safely cast an offset from a FieldAccess, which has a wider range | 175 // To safely cast an offset from a FieldAccess, which has a wider range |
151 // (namely int). | 176 // (namely int). |
152 Offset ToOffset(int offset) { | 177 StoreOffset ToOffset(int offset) { |
153 CHECK(0 <= offset && offset < (1 << 8 * sizeof(Offset))); | 178 CHECK(0 <= offset && offset < (1 << 8 * sizeof(StoreOffset))); |
154 return (Offset)offset; | 179 return (StoreOffset)offset; |
155 } | 180 } |
156 | 181 |
157 Offset ToOffset(const FieldAccess& access) { return ToOffset(access.offset); } | 182 StoreOffset ToOffset(const FieldAccess& access) { |
158 | 183 return ToOffset(access.offset); |
159 // If node has a single effect use, return that node. If node has no or | 184 } |
160 // multiple effect uses, return nullptr. | 185 |
161 Node* SingleEffectUse(Node* node) { | 186 unsigned int RepSizeOf(MachineRepresentation rep) { |
162 Node* last_use = nullptr; | 187 return 1u << ElementSizeLog2Of(rep); |
| 188 } |
| 189 unsigned int RepSizeOf(FieldAccess access) { |
| 190 return RepSizeOf(access.machine_type.representation()); |
| 191 } |
| 192 |
| 193 bool AtMostTagged(FieldAccess access) { |
| 194 return RepSizeOf(access) <= RepSizeOf(MachineRepresentation::kTagged); |
| 195 } |
| 196 |
| 197 bool AtLeastTagged(FieldAccess access) { |
| 198 return RepSizeOf(access) >= RepSizeOf(MachineRepresentation::kTagged); |
| 199 } |
| 200 |
| 201 } // namespace |
| 202 |
| 203 void RedundantStoreFinder::Find() { |
| 204 Visit(jsgraph()->graph()->end()); |
| 205 |
| 206 while (!revisit_.empty()) { |
| 207 Node* next = revisit_.top(); |
| 208 revisit_.pop(); |
| 209 DCHECK_LT(next->id(), in_revisit_.size()); |
| 210 in_revisit_[next->id()] = false; |
| 211 Visit(next); |
| 212 } |
| 213 |
| 214 #ifdef DEBUG |
| 215 // Check that we visited all the StoreFields |
| 216 AllNodes all(temp_zone(), jsgraph()->graph()); |
| 217 for (Node* node : all.reachable) { |
| 218 if (node->op()->opcode() == IrOpcode::kStoreField) { |
| 219 DCHECK_EXTRA(HasBeenVisited(node), "#%d:%s", node->id(), |
| 220 node->op()->mnemonic()); |
| 221 } |
| 222 } |
| 223 #endif |
| 224 } |
| 225 |
| 226 void RedundantStoreFinder::MarkForRevisit(Node* node) { |
| 227 DCHECK_LT(node->id(), in_revisit_.size()); |
| 228 if (!in_revisit_[node->id()]) { |
| 229 revisit_.push(node); |
| 230 in_revisit_[node->id()] = true; |
| 231 } |
| 232 } |
| 233 |
| 234 bool RedundantStoreFinder::HasBeenVisited(Node* node) { |
| 235 return !unobservable_for_id(node->id()).IsUnvisited(); |
| 236 } |
| 237 |
| 238 void StoreStoreElimination::Run(JSGraph* js_graph, Zone* temp_zone) { |
| 239 // Find superfluous nodes |
| 240 RedundantStoreFinder finder(js_graph, temp_zone); |
| 241 finder.Find(); |
| 242 |
| 243 // Remove superfluous nodes |
| 244 |
| 245 for (Node* node : finder.to_remove_const()) { |
| 246 if (FLAG_trace_store_elimination) { |
| 247 PrintF("StoreStoreElimination::Run: Eliminating node #%d:%s\n", |
| 248 node->id(), node->op()->mnemonic()); |
| 249 } |
| 250 Node* previous_effect = NodeProperties::GetEffectInput(node); |
| 251 NodeProperties::ReplaceUses(node, nullptr, previous_effect, nullptr, |
| 252 nullptr); |
| 253 node->Kill(); |
| 254 } |
| 255 } |
| 256 |
| 257 bool RedundantStoreFinder::IsEffectful(Node* node) { |
| 258 return (node->op()->EffectInputCount() >= 1); |
| 259 } |
| 260 |
| 261 // Recompute unobservables-set for a node. Will also mark superfluous nodes |
| 262 // as to be removed. |
| 263 |
| 264 UnobservablesSet RedundantStoreFinder::RecomputeSet(Node* node, |
| 265 UnobservablesSet uses) { |
| 266 switch (node->op()->opcode()) { |
| 267 case IrOpcode::kStoreField: { |
| 268 Node* stored_to = node->InputAt(0); |
| 269 FieldAccess access = OpParameter<FieldAccess>(node->op()); |
| 270 StoreOffset offset = ToOffset(access); |
| 271 |
| 272 UnobservableStore observation = {stored_to->id(), offset}; |
| 273 bool isNotObservable = uses.Contains(observation); |
| 274 |
| 275 if (isNotObservable && AtMostTagged(access)) { |
| 276 TRACE(" #%d is StoreField[+%d,%s](#%d), unobservable", node->id(), |
| 277 offset, MachineReprToString(access.machine_type.representation()), |
| 278 stored_to->id()); |
| 279 to_remove().insert(node); |
| 280 return uses; |
| 281 } else if (isNotObservable && !AtMostTagged(access)) { |
| 282 TRACE( |
| 283 " #%d is StoreField[+%d,%s](#%d), repeated in future but too " |
| 284 "big to optimize away", |
| 285 node->id(), offset, |
| 286 MachineReprToString(access.machine_type.representation()), |
| 287 stored_to->id()); |
| 288 return uses; |
| 289 } else if (!isNotObservable && AtLeastTagged(access)) { |
| 290 TRACE(" #%d is StoreField[+%d,%s](#%d), observable, recording in set", |
| 291 node->id(), offset, |
| 292 MachineReprToString(access.machine_type.representation()), |
| 293 stored_to->id()); |
| 294 return uses.Add(observation, temp_zone()); |
| 295 } else if (!isNotObservable && !AtLeastTagged(access)) { |
| 296 TRACE( |
| 297 " #%d is StoreField[+%d,%s](#%d), observable but too small to " |
| 298 "record", |
| 299 node->id(), offset, |
| 300 MachineReprToString(access.machine_type.representation()), |
| 301 stored_to->id()); |
| 302 return uses; |
| 303 } else { |
| 304 UNREACHABLE(); |
| 305 } |
| 306 break; |
| 307 } |
| 308 case IrOpcode::kLoadField: { |
| 309 Node* loaded_from = node->InputAt(0); |
| 310 FieldAccess access = OpParameter<FieldAccess>(node->op()); |
| 311 StoreOffset offset = ToOffset(access); |
| 312 |
| 313 TRACE( |
| 314 " #%d is LoadField[+%d,%s](#%d), removing all offsets [+%d] from " |
| 315 "set", |
| 316 node->id(), offset, |
| 317 MachineReprToString(access.machine_type.representation()), |
| 318 loaded_from->id(), offset); |
| 319 |
| 320 return uses.RemoveSameOffset(offset, temp_zone()); |
| 321 break; |
| 322 } |
| 323 default: |
| 324 if (CannotObserveStoreField(node)) { |
| 325 TRACE(" #%d:%s can observe nothing, set stays unchanged", node->id(), |
| 326 node->op()->mnemonic()); |
| 327 return uses; |
| 328 } else if (CanObserveAnything(node)) { |
| 329 TRACE(" #%d:%s can observe everything, recording empty set", |
| 330 node->id(), node->op()->mnemonic()); |
| 331 return unobservables_visited_empty_; |
| 332 } else { |
| 333 // It is safe to turn this check off in the future, but it is better |
| 334 // to list opcodes in CannotObserveStoreField, in CanObserveAnything, |
| 335 // or if you don't know, to add another case inside this DCHECK_EXTRA. |
| 336 DCHECK_EXTRA(node->op()->opcode() == IrOpcode::kCall, "%s", |
| 337 node->op()->mnemonic()); |
| 338 TRACE( |
| 339 " cannot determine unobservables-set for #%d:%s; " |
| 340 "conservatively recording empty set", |
| 341 node->id(), node->op()->mnemonic()); |
| 342 return unobservables_visited_empty_; |
| 343 } |
| 344 } |
| 345 UNREACHABLE(); |
| 346 return UnobservablesSet::Unvisited(); |
| 347 } |
| 348 |
| 349 bool RedundantStoreFinder::CannotObserveStoreField(Node* node) { |
| 350 Operator::Properties mask = |
| 351 Operator::kNoRead | Operator::kNoDeopt | Operator::kNoThrow; |
| 352 |
| 353 return (node->op()->properties() & mask) == mask || |
| 354 node->opcode() == IrOpcode::kAllocate || |
| 355 node->opcode() == IrOpcode::kCheckedLoad || |
| 356 node->opcode() == IrOpcode::kLoadElement || |
| 357 node->opcode() == IrOpcode::kLoad; |
| 358 } |
| 359 |
| 360 bool RedundantStoreFinder::CanObserveAnything(Node* node) { |
| 361 return !node->op()->HasProperty(Operator::kNoThrow) || |
| 362 !node->op()->HasProperty(Operator::kNoDeopt); |
| 363 } |
| 364 |
| 365 // Initialize unobservable_ with js_graph->graph->NodeCount() empty sets. |
| 366 RedundantStoreFinder::RedundantStoreFinder(JSGraph* js_graph, Zone* temp_zone) |
| 367 : jsgraph_(js_graph), |
| 368 temp_zone_(temp_zone), |
| 369 revisit_(temp_zone), |
| 370 in_revisit_(js_graph->graph()->NodeCount(), temp_zone), |
| 371 unobservable_(js_graph->graph()->NodeCount(), |
| 372 UnobservablesSet::Unvisited(), temp_zone), |
| 373 to_remove_(temp_zone), |
| 374 unobservables_visited_empty_(UnobservablesSet::VisitedEmpty(temp_zone)) {} |
| 375 |
| 376 void RedundantStoreFinder::Visit(Node* node) { |
| 377 // All effectful nodes should be reachable from End via a sequence of |
| 378 // control, then a sequence of effect edges. In VisitEffectfulNode we mark |
| 379 // all effect inputs for revisiting (if they might have stale state); here |
| 380 // we mark all control inputs at least once. |
| 381 |
| 382 if (!HasBeenVisited(node)) { |
| 383 for (int i = 0; i < node->op()->ControlInputCount(); i++) { |
| 384 Node* control_input = NodeProperties::GetControlInput(node, i); |
| 385 if (!HasBeenVisited(control_input)) { |
| 386 MarkForRevisit(control_input); |
| 387 } |
| 388 } |
| 389 } |
| 390 |
| 391 bool isEffectful = (node->op()->EffectInputCount() >= 1); |
| 392 if (isEffectful) { |
| 393 VisitEffectfulNode(node); |
| 394 DCHECK(HasBeenVisited(node)); |
| 395 } |
| 396 |
| 397 if (!HasBeenVisited(node)) { |
| 398 // Mark as visited. |
| 399 unobservable_for_id(node->id()) = unobservables_visited_empty_; |
| 400 } |
| 401 } |
| 402 |
| 403 void RedundantStoreFinder::VisitEffectfulNode(Node* node) { |
| 404 if (HasBeenVisited(node)) { |
| 405 TRACE("- Revisiting: #%d:%s", node->id(), node->op()->mnemonic()); |
| 406 } |
| 407 UnobservablesSet after_set = RecomputeUseIntersection(node); |
| 408 UnobservablesSet before_set = RecomputeSet(node, after_set); |
| 409 DCHECK(!before_set.IsUnvisited()); |
| 410 |
| 411 UnobservablesSet stored_for_node = unobservable_for_id(node->id()); |
| 412 bool cur_set_changed = |
| 413 (stored_for_node.IsUnvisited() || stored_for_node != before_set); |
| 414 if (!cur_set_changed) { |
| 415 // We will not be able to update the part of this chain above any more. |
| 416 // Exit. |
| 417 TRACE("+ No change: stabilized. Not visiting effect inputs."); |
| 418 } else { |
| 419 unobservable_for_id(node->id()) = before_set; |
| 420 |
| 421 // Mark effect inputs for visiting. |
| 422 for (int i = 0; i < node->op()->EffectInputCount(); i++) { |
| 423 Node* input = NodeProperties::GetEffectInput(node, i); |
| 424 if (!CanObserveAnything(input) || !HasBeenVisited(input)) { |
| 425 TRACE(" marking #%d:%s for revisit", input->id(), |
| 426 input->op()->mnemonic()); |
| 427 MarkForRevisit(input); |
| 428 } |
| 429 } |
| 430 } |
| 431 } |
| 432 |
| 433 // Compute the intersection of the UnobservablesSets of all effect uses and |
| 434 // return it. This function only works if {node} has an effect use. |
| 435 // |
| 436 // The result UnobservablesSet will always be visited. |
| 437 UnobservablesSet RedundantStoreFinder::RecomputeUseIntersection(Node* node) { |
| 438 // {first} == true indicates that we haven't looked at any elements yet. |
| 439 // {first} == false indicates that cur_set is the intersection of at least one |
| 440 // thing. |
| 441 |
| 442 bool first = true; |
| 443 UnobservablesSet cur_set = UnobservablesSet::Unvisited(); // irrelevant |
| 444 |
163 for (Edge edge : node->use_edges()) { | 445 for (Edge edge : node->use_edges()) { |
| 446 // Skip non-effect edges |
164 if (!NodeProperties::IsEffectEdge(edge)) { | 447 if (!NodeProperties::IsEffectEdge(edge)) { |
165 continue; | 448 continue; |
166 } | 449 } |
167 if (last_use != nullptr) { | 450 |
168 // more than one | 451 Node* use = edge.from(); |
169 return nullptr; | 452 UnobservablesSet new_set = unobservable_for_id(use->id()); |
170 } | 453 // Include new_set in the intersection. |
171 last_use = edge.from(); | 454 if (first) { |
172 DCHECK_NOT_NULL(last_use); | 455 // Intersection of a one-element set is that one element |
173 } | 456 first = false; |
174 return last_use; | 457 cur_set = new_set; |
175 } | 458 } else { |
176 | 459 // Take the intersection of cur_set and new_set. |
177 // Return true if node is the last consecutive StoreField node in a linear | 460 cur_set = cur_set.Intersect(new_set, temp_zone()); |
178 // part of the effect chain. | 461 } |
179 bool IsEndOfStoreFieldChain(Node* node) { | 462 } |
180 Node* next_on_chain = SingleEffectUse(node); | 463 |
181 return (next_on_chain == nullptr || | 464 if (first) { |
182 next_on_chain->op()->opcode() != IrOpcode::kStoreField); | 465 // There were no effect uses. |
183 } | 466 auto opcode = node->op()->opcode(); |
184 | 467 // List of opcodes that may end this effect chain. The opcodes are not |
185 // The argument must be a StoreField node. If there is a node before it in the | 468 // important to the soundness of this optimization; this serves as a |
186 // effect chain, and if this part of the effect chain is linear (no other | 469 // general sanity check. Add opcodes to this list as it suits you. |
187 // effect uses of that previous node), then return that previous node. | 470 // |
188 // Otherwise, return nullptr. | 471 // Everything is observable after these opcodes; return the empty set. |
189 // | 472 DCHECK_EXTRA( |
190 // The returned node need not be a StoreField. | 473 opcode == IrOpcode::kReturn || opcode == IrOpcode::kTerminate || |
191 Node* PreviousEffectBeforeStoreField(Node* node) { | 474 opcode == IrOpcode::kDeoptimize || opcode == IrOpcode::kThrow, |
192 DCHECK_EQ(node->op()->opcode(), IrOpcode::kStoreField); | 475 "for #%d:%s", node->id(), node->op()->mnemonic()); |
193 DCHECK_EQ(node->op()->EffectInputCount(), 1); | 476 USE(opcode); // silence warning about unused variable in release mode |
194 | 477 |
195 Node* previous = NodeProperties::GetEffectInput(node); | 478 return unobservables_visited_empty_; |
196 if (previous != nullptr && node == SingleEffectUse(previous)) { | |
197 return previous; | |
198 } else { | 479 } else { |
199 return nullptr; | 480 if (cur_set.IsUnvisited()) { |
200 } | 481 cur_set = unobservables_visited_empty_; |
201 } | 482 } |
202 | 483 |
203 size_t rep_size_of(MachineRepresentation rep) { | 484 return cur_set; |
204 return ((size_t)1) << ElementSizeLog2Of(rep); | 485 } |
205 } | 486 } |
206 size_t rep_size_of(FieldAccess access) { | 487 |
207 return rep_size_of(access.machine_type.representation()); | 488 UnobservablesSet UnobservablesSet::Unvisited() { return UnobservablesSet(); } |
208 } | 489 |
209 | 490 UnobservablesSet::UnobservablesSet() : set_(nullptr) {} |
210 bool AtMostTagged(FieldAccess access) { | 491 |
211 return rep_size_of(access) <= rep_size_of(MachineRepresentation::kTagged); | 492 UnobservablesSet UnobservablesSet::VisitedEmpty(Zone* zone) { |
212 } | 493 // Create a new empty UnobservablesSet. This allocates in the zone, and |
213 | 494 // can probably be optimized to use a global singleton. |
214 bool AtLeastTagged(FieldAccess access) { | 495 ZoneSet<UnobservableStore>* empty_set = |
215 return rep_size_of(access) >= rep_size_of(MachineRepresentation::kTagged); | 496 new (zone->New(sizeof(ZoneSet<UnobservableStore>))) |
216 } | 497 ZoneSet<UnobservableStore>(zone); |
217 | 498 return UnobservablesSet(empty_set); |
218 } // namespace | 499 } |
219 | 500 |
220 bool StoreStoreElimination::IsEligibleNode(Node* node) { | 501 // Computes the intersection of two UnobservablesSets. May return |
221 return (node->op()->opcode() == IrOpcode::kStoreField) && | 502 // UnobservablesSet::Unvisited() instead of an empty UnobservablesSet for |
222 IsEndOfStoreFieldChain(node); | 503 // speed. |
223 } | 504 UnobservablesSet UnobservablesSet::Intersect(UnobservablesSet other, |
224 | 505 Zone* zone) const { |
225 void StoreStoreElimination::ReduceEligibleNode(Node* node) { | 506 if (IsEmpty() || other.IsEmpty()) { |
226 DCHECK(IsEligibleNode(node)); | 507 return Unvisited(); |
227 | 508 } else { |
228 // if (FLAG_trace_store_elimination) { | 509 ZoneSet<UnobservableStore>* intersection = |
229 // PrintF("** StoreStoreElimination::ReduceEligibleNode: activated: | 510 new (zone->New(sizeof(ZoneSet<UnobservableStore>))) |
230 // #%d\n", | 511 ZoneSet<UnobservableStore>(zone); |
231 // node->id()); | 512 // Put the intersection of set() and other.set() in intersection. |
232 // } | 513 set_intersection(set()->begin(), set()->end(), other.set()->begin(), |
233 | 514 other.set()->end(), |
234 TRACE("activated: #%d", node->id()); | 515 std::inserter(*intersection, intersection->end())); |
235 | 516 |
236 // Initialize empty futureStore. | 517 return UnobservablesSet(intersection); |
237 ZoneMap<Offset, Node*> futureStore(temp_zone()); | 518 } |
238 | 519 } |
239 Node* current_node = node; | 520 |
240 | 521 UnobservablesSet UnobservablesSet::Add(UnobservableStore obs, |
241 do { | 522 Zone* zone) const { |
242 FieldAccess access = OpParameter<FieldAccess>(current_node->op()); | 523 bool present = (set()->find(obs) != set()->end()); |
243 Offset offset = ToOffset(access); | 524 if (present) { |
244 Node* object_input = current_node->InputAt(0); | 525 return *this; |
245 | 526 } else { |
246 Node* previous = PreviousEffectBeforeStoreField(current_node); | 527 // Make a new empty set. |
247 | 528 ZoneSet<UnobservableStore>* new_set = |
248 // Find the map entry. | 529 new (zone->New(sizeof(ZoneSet<UnobservableStore>))) |
249 ZoneMap<Offset, Node*>::iterator find_result = futureStore.find(offset); | 530 ZoneSet<UnobservableStore>(zone); |
250 | 531 // Copy the old elements over. |
251 bool present = find_result != futureStore.end(); | 532 *new_set = *set(); |
252 Node* value = present ? find_result->second : nullptr; | 533 // Add the new element. |
253 | 534 bool inserted = new_set->insert(obs).second; |
254 if (present && value == object_input && AtMostTagged(access)) { | 535 DCHECK(inserted); |
255 // Key was present, and the value equalled object_input. This means | 536 USE(inserted); // silence warning about unused variable |
256 // that soon after in the effect chain, we will do a StoreField to the | 537 |
257 // same object with the same offset, therefore current_node can be | 538 return UnobservablesSet(new_set); |
258 // optimized away. Also, the future StoreField is at least as big as this | 539 } |
259 // one. | 540 } |
260 // | 541 |
261 // We don't need to update futureStore. | 542 UnobservablesSet UnobservablesSet::RemoveSameOffset(StoreOffset offset, |
262 | 543 Zone* zone) const { |
263 Node* previous_effect = NodeProperties::GetEffectInput(current_node); | 544 // Make a new empty set. |
264 | 545 ZoneSet<UnobservableStore>* new_set = |
265 NodeProperties::ReplaceUses(current_node, nullptr, previous_effect, | 546 new (zone->New(sizeof(ZoneSet<UnobservableStore>))) |
266 nullptr, nullptr); | 547 ZoneSet<UnobservableStore>(zone); |
267 current_node->Kill(); | 548 // Copy all elements over that have a different offset. |
268 TRACE("#%d[[+%d,%s]](#%d) -- at most tagged size, eliminated", | 549 for (auto obs : *set()) { |
269 current_node->id(), offset, | 550 if (obs.offset_ != offset) { |
270 MachineReprToString(access.machine_type.representation()), | 551 new_set->insert(obs); |
271 object_input->id()); | 552 } |
272 } else if (present && value == object_input && !AtMostTagged(access)) { | 553 } |
273 TRACE("#%d[[+%d,%s]](#%d) -- too wide, not eliminated", | 554 |
274 current_node->id(), offset, | 555 return UnobservablesSet(new_set); |
275 MachineReprToString(access.machine_type.representation()), | 556 } |
276 object_input->id()); | 557 |
277 } else if (present && value != object_input && AtLeastTagged(access)) { | 558 // Used for debugging. |
278 // Key was present, and the value did not equal object_input. This means | 559 bool UnobservablesSet::operator==(const UnobservablesSet& other) const { |
279 // that there is a StoreField to this offset in the future, but the | 560 if (IsUnvisited() || other.IsUnvisited()) { |
280 // object instance comes from a different Node. We pessimistically | 561 return IsEmpty() && other.IsEmpty(); |
281 // assume that we cannot optimize current_node away. However, we will | 562 } else { |
282 // guess that the current StoreField is more relevant than the future | 563 // Both pointers guaranteed not to be nullptrs. |
283 // one, record the current StoreField in futureStore instead, and | 564 return *set() == *other.set(); |
284 // continue ascending up the chain. | 565 } |
285 find_result->second = object_input; | 566 } |
286 TRACE("#%d[[+%d,%s]](#%d) -- wide enough, diff object, updated in map", | 567 |
287 current_node->id(), offset, | 568 bool UnobservablesSet::operator!=(const UnobservablesSet& other) const { |
288 MachineReprToString(access.machine_type.representation()), | 569 return !(*this == other); |
289 object_input->id()); | 570 } |
290 } else if (!present && AtLeastTagged(access)) { | 571 |
291 // Key was not present. This means that there is no matching | 572 bool UnobservableStore::operator==(const UnobservableStore other) const { |
292 // StoreField to this offset in the future, so we cannot optimize | 573 return (id_ == other.id_) && (offset_ == other.offset_); |
293 // current_node away. However, we will record the current StoreField | 574 } |
294 // in futureStore, and continue ascending up the chain. | 575 |
295 futureStore.insert(std::make_pair(offset, object_input)); | 576 bool UnobservableStore::operator!=(const UnobservableStore other) const { |
296 TRACE( | 577 return !(*this == other); |
297 "#%d[[+%d,%s]](#%d) -- wide enough, key not present, inserted in map", | 578 } |
298 current_node->id(), offset, | 579 |
299 MachineReprToString(access.machine_type.representation()), | 580 bool UnobservableStore::operator<(const UnobservableStore other) const { |
300 object_input->id()); | 581 return (id_ < other.id_) || (id_ == other.id_ && offset_ < other.offset_); |
301 } else if (!AtLeastTagged(access)) { | |
302 TRACE("#%d[[+%d,%s]](#%d) -- too narrow to record", current_node->id(), | |
303 offset, MachineReprToString(access.machine_type.representation()), | |
304 object_input->id()); | |
305 } else { | |
306 UNREACHABLE(); | |
307 } | |
308 | |
309 // Regardless of whether we eliminated node {current}, we want to | |
310 // continue walking up the effect chain. | |
311 | |
312 current_node = previous; | |
313 } while (current_node != nullptr && | |
314 current_node->op()->opcode() == IrOpcode::kStoreField); | |
315 | |
316 TRACE("finished"); | |
317 } | 582 } |
318 | 583 |
319 } // namespace compiler | 584 } // namespace compiler |
320 } // namespace internal | 585 } // namespace internal |
321 } // namespace v8 | 586 } // namespace v8 |
OLD | NEW |