Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(247)

Side by Side Diff: src/compiler/store-store-elimination.cc

Issue 2159303002: [turbofan] Improve the store-store elimination. (Closed) Base URL: https://chromium.googlesource.com/v8/v8.git@p1-base
Patch Set: Fix merge conflict Created 4 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « src/compiler/store-store-elimination.h ('k') | src/flag-definitions.h » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
OLDNEW
« no previous file with comments | « src/compiler/store-store-elimination.h ('k') | src/flag-definitions.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698