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

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: Cut down on documentation and tracing. There is now only a single trace level. Created 4 years, 5 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
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("StoreStoreFinder: " 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
27 // expression will be evaluated at runtime. If it evaluates to false, then an
28 // error message will be shown containing the condition, as well as the extra
29 // info formatted like with printf.
30 #define CHECK_EXTRA(condition, fmt, ...) \
31 do { \
32 if (V8_UNLIKELY(!(condition))) { \
33 V8_Fatal(__FILE__, __LINE__, "Check failed: %s. Extra info: " fmt, \
34 #condition, ##__VA_ARGS__); \
35 } \
36 } while (0)
37
38 #ifdef DEBUG
39 #define DCHECK_EXTRA(condition, fmt, ...) \
40 CHECK_EXTRA(condition, fmt, ##__VA_ARGS__)
41 #else
42 #define DCHECK_EXTRA(condition, fmt, ...) ((void)0)
43 #endif
44
45 // Store-store elimination.
26 // 46 //
27 // - StoreField[[+off_1]](x1, y1) 47 // The aim of this optimization is to detect the following pattern in the
28 // - StoreField[[+off_2]](x2, y2) 48 // effect graph:
29 // - StoreField[[+off_3]](x3, y3)
30 // ...
31 // - StoreField[[+off_n]](xn, yn)
32 // 49 //
33 // where the xes are the objects and the ys are the values to be stored, then 50 // - StoreField[+24, kRepTagged](263, ...)
34 // we are going to say that a store is superfluous if the same offset of the
35 // same object will be stored to in the future. If off_i == off_j and xi == xj
36 // and i < j, then we optimize the i'th StoreField away.
37 // 51 //
38 // This optimization should be initiated on the last StoreField in such a 52 // ... lots of nodes from which the field at offset 24 of the object
39 // sequence. 53 // returned by node #263 cannot be observed ...
40 // 54 //
41 // The algorithm works by walking the effect chain from the last StoreField 55 // - StoreField[+24, kRepTagged](263, ...)
42 // upwards. While walking, we maintain a map {futureStore} from offsets to
43 // nodes; initially it is empty. As we walk the effect chain upwards, if
44 // futureStore[off] = n, then any store to node {n} with offset {off} is
45 // guaranteed to be useless because we do a tagged-width[2] store to that
46 // offset of that object in the near future anyway. For example, for this
47 // effect chain
48 // 56 //
49 // 71: StoreField(60, 0) 57 // In such situations, the earlier StoreField cannot be observed, and can be
50 // 72: StoreField(65, 8) 58 // eliminated. This optimization should work for any offset and input node, of
51 // 73: StoreField(63, 8) 59 // course.
52 // 74: StoreField(65, 16)
53 // 75: StoreField(62, 8)
54 // 60 //
55 // just before we get to 72, we will have futureStore = {8: 63, 16: 65}. 61 // The optimization also works across splits. It currently does not work for
62 // loops, because we tend to put a stack check in loops, and like deopts,
63 // stack checks can observe anything.
64
65 // Assumption: every byte of a JS object is only ever accessed through one
66 // offset. For instance, byte 15 of a given object may be accessed using a
67 // two-byte read at offset 14, or a four-byte read at offset 12, but never
68 // both in the same program.
56 // 69 //
57 // Here is the complete process. 70 // This implementation needs all dead nodes removed from the graph, and the
58 // 71 // graph should be trimmed.
59 // - We are at the end of a sequence of consecutive StoreFields.
60 // - We start out with futureStore = empty.
61 // - We then walk the effect chain upwards to find the next StoreField [1].
62 //
63 // 1. If the offset is not a key of {futureStore} yet, we put it in.
64 // 2. If the offset is a key of {futureStore}, but futureStore[offset] is a
65 // different node, we overwrite futureStore[offset] with the current node.
66 // 3. If the offset is a key of {futureStore} and futureStore[offset] equals
67 // this node, we eliminate this StoreField.
68 //
69 // As long as the current effect input points to a node with a single effect
70 // output, and as long as its opcode is StoreField, we keep traversing
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.live) {
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 std::ostream& operator<<(std::ostream&, const UnobservableStore);
91
92 namespace {
93
94 // Instances of UnobservablesSet are immutable. They represent either a set of
95 // UnobservableStores, or the "undetermined empty set".
96 //
97 // We apply some sharing to save memory. The class UnobservablesSet is only a
98 // pointer wide, and a copy does not use any heap (or temp_zone) memory. Most
99 // changes to an UnobservablesSet might allocate in the temp_zone.
100 //
101 // The size of an instance should be the size of a pointer, plus additional
102 // space in the zone for determined UnobservablesSets. Copying an
103 // UnobservablesSet allocates no memory.
104 class UnobservablesSet final {
105 public:
106 static UnobservablesSet Undetermined();
107 static UnobservablesSet DeterminedEmpty(Zone* zone);
108 UnobservablesSet(); // undetermined
109 UnobservablesSet(const UnobservablesSet& other) : set_(other.set_) {}
110
111 UnobservablesSet Intersect(UnobservablesSet other, Zone* zone) const;
112 UnobservablesSet Add(UnobservableStore obs, Zone* zone) const;
113 UnobservablesSet RemoveSameOffset(StoreOffset off, Zone* zone) const;
114
115 const ZoneSet<UnobservableStore>* set() const { return set_; }
116
117 bool IsUndetermined() const { return set_ == nullptr; }
118 bool IsEmpty() const { return set_ == nullptr || set_->empty(); }
119 bool Contains(UnobservableStore obs) const {
120 return set_ != nullptr && (set_->find(obs) != set_->end());
121 }
122
123 bool operator==(const UnobservablesSet&) const;
124 bool operator!=(const UnobservablesSet&) const;
125
126 private:
127 explicit UnobservablesSet(const ZoneSet<UnobservableStore>* set)
128 : set_(set) {}
129 const ZoneSet<UnobservableStore>* set_;
130 };
131
132 } // namespace
133
134 std::ostream& operator<<(std::ostream& os, UnobservablesSet set);
135
136 namespace {
137
138 class StoreStoreFinder final {
Jarin 2016/07/25 09:52:13 Maybe rename to RedundantStoreFinder? (Or Unobserv
bgeron 2016/07/27 13:40:38 Done.
139 public:
140 StoreStoreFinder(JSGraph* js_graph, Zone* temp_zone);
141
142 void Find();
143
144 const ZoneSet<Node*>& to_remove_const() { return to_remove_; }
145
146 virtual void Visit(Node* node);
147
148 private:
149 static bool IsEligibleNode(Node* node);
150 void VisitEligibleNode(Node* node);
151 UnobservablesSet RecomputeUseIntersection(Node* node);
152 UnobservablesSet RecomputeSet(Node* node, UnobservablesSet uses);
153 static bool CanObserveNothing(Node* node);
154 static bool CanObserveAnything(Node* node);
155
156 void MarkForRevisit(Node* node);
157
158 JSGraph* jsgraph() const { return jsgraph_; }
159 Zone* temp_zone() const { return temp_zone_; }
160 ZoneVector<UnobservablesSet>& unobservable() { return unobservable_; }
161 UnobservablesSet& unobservable_for_id(NodeId id) {
162 return unobservable().at(id);
163 }
164 ZoneSet<Node*>& to_remove() { return to_remove_; }
165
166 JSGraph* const jsgraph_;
167 Zone* const temp_zone_;
168
169 ZoneStack<Node*> revisit_;
170 ZoneVector<bool> in_revisit_;
171 // Maps node IDs to UnobservableNodeSets.
172 ZoneVector<UnobservablesSet> unobservable_;
173 ZoneSet<Node*> to_remove_;
174 };
149 175
150 // To safely cast an offset from a FieldAccess, which has a wider range 176 // To safely cast an offset from a FieldAccess, which has a wider range
151 // (namely int). 177 // (namely int).
152 Offset ToOffset(int offset) { 178 StoreOffset ToOffset(int offset) {
153 CHECK(0 <= offset && offset < (1 << 8 * sizeof(Offset))); 179 CHECK(0 <= offset && offset < (1 << 8 * sizeof(StoreOffset)));
154 return (Offset)offset; 180 return (StoreOffset)offset;
155 } 181 }
156 182
157 Offset ToOffset(const FieldAccess& access) { return ToOffset(access.offset); } 183 StoreOffset ToOffset(const FieldAccess& access) {
184 return ToOffset(access.offset);
185 }
158 186
159 // If node has a single effect use, return that node. If node has no or 187 // If node has a single effect use, return that node. If node has no or
160 // multiple effect uses, return nullptr. 188 // multiple effect uses, return nullptr.
161 Node* SingleEffectUse(Node* node) { 189 Node* SingleEffectUse(Node* node) {
162 Node* last_use = nullptr; 190 Node* last_use = nullptr;
163 for (Edge edge : node->use_edges()) { 191 for (Edge edge : node->use_edges()) {
164 if (!NodeProperties::IsEffectEdge(edge)) { 192 if (!NodeProperties::IsEffectEdge(edge)) {
165 continue; 193 continue;
166 } 194 }
167 if (last_use != nullptr) { 195 if (last_use != nullptr) {
168 // more than one 196 // more than one
Jarin 2016/07/25 09:52:13 Leave out the comment (or make into a sentence wit
bgeron 2016/07/27 13:40:37 Done.
169 return nullptr; 197 return nullptr;
170 } 198 }
171 last_use = edge.from(); 199 last_use = edge.from();
172 DCHECK_NOT_NULL(last_use); 200 DCHECK_NOT_NULL(last_use);
173 } 201 }
174 return last_use; 202 return last_use;
175 } 203 }
176 204
177 // Return true if node is the last consecutive StoreField node in a linear 205 // If there is a node before {node} in the effect chain, and if this part of
178 // part of the effect chain. 206 // the effect chain is linear (no other effect uses of that previous node),
179 bool IsEndOfStoreFieldChain(Node* node) { 207 // then return that previous node. Otherwise, return nullptr.
180 Node* next_on_chain = SingleEffectUse(node); 208 Node* PreviousEffectInChain(Node* node) {
181 return (next_on_chain == nullptr || 209 if (node->op()->EffectInputCount() == 1) {
182 next_on_chain->op()->opcode() != IrOpcode::kStoreField); 210 Node* previous = NodeProperties::GetEffectInput(node);
183 } 211 if (previous != nullptr && node == SingleEffectUse(previous)) {
184 212 return previous;
185 // The argument must be a StoreField node. If there is a node before it in the 213 } else {
Jarin 2016/07/25 09:52:13 How about leaving out the else branches and just h
bgeron 2016/07/27 13:40:38 Nice spot. Done.
186 // effect chain, and if this part of the effect chain is linear (no other 214 return nullptr;
187 // effect uses of that previous node), then return that previous node. 215 }
188 // Otherwise, return nullptr.
189 //
190 // The returned node need not be a StoreField.
191 Node* PreviousEffectBeforeStoreField(Node* node) {
192 DCHECK_EQ(node->op()->opcode(), IrOpcode::kStoreField);
193 DCHECK_EQ(node->op()->EffectInputCount(), 1);
194
195 Node* previous = NodeProperties::GetEffectInput(node);
196 if (previous != nullptr && node == SingleEffectUse(previous)) {
197 return previous;
198 } else { 216 } else {
199 return nullptr; 217 return nullptr;
200 } 218 }
201 } 219 }
202 220
203 size_t rep_size_of(MachineRepresentation rep) { 221 size_t rep_size_of(MachineRepresentation rep) {
Jarin 2016/07/25 09:52:13 CamelCase, please. (Here and below.)
bgeron 2016/07/27 13:40:38 Done. I left `unobservable_for_id` as is, because
204 return ((size_t)1) << ElementSizeLog2Of(rep); 222 return ((size_t)1) << ElementSizeLog2Of(rep);
205 } 223 }
206 size_t rep_size_of(FieldAccess access) { 224 size_t rep_size_of(FieldAccess access) {
207 return rep_size_of(access.machine_type.representation()); 225 return rep_size_of(access.machine_type.representation());
208 } 226 }
209 227
210 bool AtMostTagged(FieldAccess access) { 228 bool AtMostTagged(FieldAccess access) {
211 return rep_size_of(access) <= rep_size_of(MachineRepresentation::kTagged); 229 return rep_size_of(access) <= rep_size_of(MachineRepresentation::kTagged);
212 } 230 }
213 231
214 bool AtLeastTagged(FieldAccess access) { 232 bool AtLeastTagged(FieldAccess access) {
215 return rep_size_of(access) >= rep_size_of(MachineRepresentation::kTagged); 233 return rep_size_of(access) >= rep_size_of(MachineRepresentation::kTagged);
216 } 234 }
217 235
236 int EffectUseCount(Node* node) {
237 int uses = 0;
238 for (const Edge edge : node->use_edges()) {
239 if (NodeProperties::IsEffectEdge(edge)) {
240 uses++;
241 }
242 }
243 return uses;
244 }
245
218 } // namespace 246 } // namespace
219 247
220 bool StoreStoreElimination::IsEligibleNode(Node* node) { 248 void StoreStoreFinder::Find() {
221 return (node->op()->opcode() == IrOpcode::kStoreField) && 249 Visit(jsgraph()->graph()->end());
222 IsEndOfStoreFieldChain(node); 250
223 } 251 while (!revisit_.empty()) {
224 252 Node* next = revisit_.top();
225 void StoreStoreElimination::ReduceEligibleNode(Node* node) { 253 revisit_.pop();
226 DCHECK(IsEligibleNode(node)); 254 in_revisit_.at(next->id()) = false;
Jarin 2016/07/25 09:52:13 Do not use the 'at' method - it can throw exceptio
bgeron 2016/07/27 13:40:38 Done.
227 255 Visit(next);
228 // if (FLAG_trace_store_elimination) { 256 }
229 // PrintF("** StoreStoreElimination::ReduceEligibleNode: activated: 257
230 // #%d\n", 258 #ifdef DEBUG
231 // node->id()); 259 // Check that we visited all the StoreFields
232 // } 260 AllNodes all(temp_zone(), jsgraph()->graph());
233 261 for (Node* node : all.live) {
234 TRACE("activated: #%d", node->id()); 262 if (node->op()->opcode() == IrOpcode::kStoreField) {
235 263 UnobservablesSet node_unobservable = unobservable_for_id(node->id());
236 // Initialize empty futureStore. 264 DCHECK_EXTRA(!node_unobservable.IsUndetermined(), "#%d:%s", node->id(),
237 ZoneMap<Offset, Node*> futureStore(temp_zone()); 265 node->op()->mnemonic());
238 266 }
239 Node* current_node = node; 267 }
Jarin 2016/07/25 09:52:13 Niiiice, I love this check; will use this elsewher
bgeron 2016/07/27 13:40:38 We should put it in src/base/logging.h :D
268 #endif
269 }
270
271 void StoreStoreFinder::MarkForRevisit(Node* node) {
272 if (!in_revisit_.at(node->id())) {
273 revisit_.push(node);
274 in_revisit_[node->id()] = true;
275 }
276 }
277
278 void StoreStoreElimination::Run(JSGraph* js_graph, Zone* temp_zone) {
279 // Find superfluous nodes
280 StoreStoreFinder finder(js_graph, temp_zone);
281 finder.Find();
282
283 // Remove superfluous nodes
284
285 for (Node* node : finder.to_remove_const()) {
286 if (FLAG_trace_store_elimination) {
287 PrintF("StoreStoreElimination::Run: Eliminating node #%d:%s\n",
288 node->id(), node->op()->mnemonic());
289 }
290 Node* previous_effect = NodeProperties::GetEffectInput(node);
291 NodeProperties::ReplaceUses(node, nullptr, previous_effect, nullptr,
292 nullptr);
293 node->Kill();
294 }
295 }
296
297 bool StoreStoreFinder::IsEligibleNode(Node* node) {
298 DCHECK_LE(node->op()->EffectOutputCount(), 1);
299
300 bool isEffectful = (node->op()->EffectInputCount() >= 1);
301 bool endsEffectChain = (EffectUseCount(node) != 1) ||
302 (SingleEffectUse(node)->op()->EffectInputCount() >= 2);
303 if (endsEffectChain && EffectUseCount(node) >= 1 &&
Jarin 2016/07/25 09:52:13 The if is here just for the DCHECK, please fold th
bgeron 2016/07/27 13:40:38 Method disappeared during refactoring.
304 node->op()->opcode() != IrOpcode::kStart) {
305 DCHECK_EXTRA(isEffectful, "#%d:%s", node->id(), node->op()->mnemonic());
306 }
307 return isEffectful && endsEffectChain;
308 }
309
310 // Recompute unobservables-set for a node. Will also mark superfluous nodes
311 // as to be removed.
312
313 UnobservablesSet StoreStoreFinder::RecomputeSet(Node* node,
314 UnobservablesSet uses) {
315 // Usually, we decide using the operator properties that an operator
316 // observes everything or observes nothing (see CanObserveAnything,
317 // CanObserveNothing), but there are some opcodes we treat specially.
Jarin 2016/07/25 09:52:13 This comment basically just states what is obvious
bgeron 2016/07/27 13:40:38 Done.
318 switch (node->op()->opcode()) {
319 case IrOpcode::kStoreField: {
320 Node* stored_to = node->InputAt(0);
321 FieldAccess access = OpParameter<FieldAccess>(node->op());
322 StoreOffset offset = ToOffset(access);
323
324 UnobservableStore observation = {stored_to->id(), offset};
325 bool presentInSet = uses.Contains(observation);
Jarin 2016/07/25 09:52:13 presentInSet => isNotObservable?
bgeron 2016/07/27 13:40:38 Done.
326
327 if (presentInSet && AtMostTagged(access)) {
328 TRACE(" #%d is StoreField[+%d,%s](#%d), unobservable", node->id(),
329 offset, MachineReprToString(access.machine_type.representation()),
330 stored_to->id());
331 to_remove().insert(node);
332 return uses;
333 } else if (presentInSet && !AtMostTagged(access)) {
334 TRACE(
335 " #%d is StoreField[+%d,%s](#%d), repeated in future but too "
336 "big to optimize away",
337 node->id(), offset,
338 MachineReprToString(access.machine_type.representation()),
339 stored_to->id());
340 return uses;
341 } else if (!presentInSet && AtLeastTagged(access)) {
342 TRACE(" #%d is StoreField[+%d,%s](#%d), observable, recording in set",
343 node->id(), offset,
344 MachineReprToString(access.machine_type.representation()),
345 stored_to->id());
346 return uses.Add(observation, temp_zone());
347 } else if (!presentInSet && !AtLeastTagged(access)) {
348 TRACE(
349 " #%d is StoreField[+%d,%s](#%d), observable but too small to "
350 "record",
351 node->id(), offset,
352 MachineReprToString(access.machine_type.representation()),
353 stored_to->id());
354 return uses;
355 } else {
356 UNREACHABLE();
357 }
358 break;
359 }
360 case IrOpcode::kLoadField: {
361 Node* loaded_from = node->InputAt(0);
362 FieldAccess access = OpParameter<FieldAccess>(node->op());
363 StoreOffset offset = ToOffset(access);
364
365 TRACE(
366 " #%d is LoadField[+%d,%s](#%d), removing all offsets [+%d] from "
367 "set",
368 node->id(), offset,
369 MachineReprToString(access.machine_type.representation()),
370 loaded_from->id(), offset);
371
372 return uses.RemoveSameOffset(offset, temp_zone());
Jarin 2016/07/25 09:52:13 In future, we could be a bit smarter here and only
bgeron 2016/07/27 13:40:38 Ack. Reminds me.. for the load elimination, I enco
373 break;
374 }
375 default:
376 if (CanObserveNothing(node)) {
377 TRACE(" #%d:%s can observe nothing, set stays unchanged", node->id(),
378 node->op()->mnemonic());
379 return uses;
380 } else if (CanObserveAnything(node)) {
381 TRACE(" #%d:%s can observe everything, recording empty set",
382 node->id(), node->op()->mnemonic());
383 return UnobservablesSet::DeterminedEmpty(temp_zone());
384 } else {
385 // It is safe to turn this check off in the future, but it is better
386 // to list opcodes in CanObserveNothing, in CanObserveAnything, or if
387 // you don't know, to add another case inside this DCHECK_EXTRA.
388 DCHECK_EXTRA(node->op()->opcode() == IrOpcode::kCall, "%s",
389 node->op()->mnemonic());
390 TRACE(
391 " cannot determine unobservables-set for #%d:%s; "
392 "conservatively recording empty set",
393 node->id(), node->op()->mnemonic());
394 return UnobservablesSet::DeterminedEmpty(temp_zone());
395 }
396 }
397 UNREACHABLE();
398 return UnobservablesSet::Undetermined();
399 }
400
401 bool StoreStoreFinder::CanObserveNothing(Node* node) {
Jarin 2016/07/25 09:52:13 CannotObserveStoreField?
bgeron 2016/07/27 13:40:38 Done.
402 Operator::Properties mask =
403 Operator::kNoRead | Operator::kNoDeopt | Operator::kNoThrow;
404
405 return (node->op()->properties() & mask) == mask ||
406 node->opcode() == IrOpcode::kAllocate ||
407 node->opcode() == IrOpcode::kCheckedLoad ||
408 node->opcode() == IrOpcode::kLoadElement;
409 }
410
411 bool StoreStoreFinder::CanObserveAnything(Node* node) {
412 const Operator* op = node->op();
413 auto opcode = op->opcode();
414 if (opcode == IrOpcode::kLoad) {
Jarin 2016/07/25 09:52:13 Benedikt says kLoad should never alias a field, so
bgeron 2016/07/27 13:40:38 Well, if I make it so that Load cannot observe any
415 return true;
416 }
417 return !op->HasProperty(Operator::kNoThrow) ||
418 !op->HasProperty(Operator::kNoDeopt);
419 }
420
421 // Initialize unobservable_ with js_graph->graph->NodeCount() empty sets.
422 StoreStoreFinder::StoreStoreFinder(JSGraph* js_graph, Zone* temp_zone)
423 : jsgraph_(js_graph),
424 temp_zone_(temp_zone),
425 revisit_(temp_zone),
426 in_revisit_(js_graph->graph()->NodeCount(), temp_zone),
427 unobservable_(js_graph->graph()->NodeCount(),
428 UnobservablesSet::Undetermined(), temp_zone),
429 to_remove_(temp_zone) {}
430
431 void StoreStoreFinder::Visit(Node* node) {
432 // All eligible nodes should be reachable from the control graph. If this
433 // node was not visited (unobservable_for_id(node->id()).IsUndetermined()),
434 // then visit the control inputs and mark as visited
435 // (unobservable_for_id(node->id() = DeterminedEmpty(..)).
436
437 bool isEffectful = (node->op()->EffectInputCount() >= 1);
438
439 if (IsEligibleNode(node)) {
Jarin 2016/07/25 09:52:13 I do not understand what is the advantage of walki
bgeron 2016/07/27 13:40:38 Good idea; this simplified the file significantly.
440 VisitEligibleNode(node);
441 } else if (!isEffectful) {
442 // If node was not visited before, then visit its control inputs.
443 if (unobservable_for_id(node->id()).IsUndetermined()) {
444 for (Edge edge : node->input_edges()) {
445 if (NodeProperties::IsControlEdge(edge)) {
446 MarkForRevisit(edge.to());
447 }
448 }
449
450 unobservable_for_id(node->id()) =
451 UnobservablesSet::DeterminedEmpty(temp_zone());
452 }
453 }
454 }
455
456 void StoreStoreFinder::VisitEligibleNode(Node* node) {
457 TRACE("Found end of effect chain: %4d:%s", node->id(),
458 node->op()->mnemonic());
459
460 Node* cur = node;
461 UnobservablesSet after_set = RecomputeUseIntersection(cur);
462 bool cur_set_changed;
240 463
241 do { 464 do {
242 FieldAccess access = OpParameter<FieldAccess>(current_node->op()); 465 UnobservablesSet before_set = RecomputeSet(cur, after_set);
243 Offset offset = ToOffset(access); 466
244 Node* object_input = current_node->InputAt(0); 467 DCHECK(!before_set.IsUndetermined());
245 468
246 Node* previous = PreviousEffectBeforeStoreField(current_node); 469 UnobservablesSet* stored_for_node = &unobservable_for_id(cur->id());
247 470
248 // Find the map entry. 471 cur_set_changed =
249 ZoneMap<Offset, Node*>::iterator find_result = futureStore.find(offset); 472 (stored_for_node->IsUndetermined() || *stored_for_node != before_set);
250 473
251 bool present = find_result != futureStore.end(); 474 if (!cur_set_changed) {
252 Node* value = present ? find_result->second : nullptr; 475 // We will not be able to update the part of this chain above any more.
253 476 // Exit.
254 if (present && value == object_input && AtMostTagged(access)) { 477 TRACE("+ No change: stabilized. Stopping this chain.");
255 // Key was present, and the value equalled object_input. This means 478 break;
256 // that soon after in the effect chain, we will do a StoreField to the 479 } else if (cur_set_changed && before_set.IsEmpty()) {
257 // same object with the same offset, therefore current_node can be 480 DCHECK(before_set.IsEmpty());
bgeron 2016/07/27 13:40:38 Oops!
258 // optimized away. Also, the future StoreField is at least as big as this 481 } else if (cur_set_changed && !before_set.IsEmpty()) {
259 // one.
260 //
261 // We don't need to update futureStore.
262
263 Node* previous_effect = NodeProperties::GetEffectInput(current_node);
264
265 NodeProperties::ReplaceUses(current_node, nullptr, previous_effect,
266 nullptr, nullptr);
267 current_node->Kill();
268 TRACE("#%d[[+%d,%s]](#%d) -- at most tagged size, eliminated",
269 current_node->id(), offset,
270 MachineReprToString(access.machine_type.representation()),
271 object_input->id());
272 } else if (present && value == object_input && !AtMostTagged(access)) {
273 TRACE("#%d[[+%d,%s]](#%d) -- too wide, not eliminated",
274 current_node->id(), offset,
275 MachineReprToString(access.machine_type.representation()),
276 object_input->id());
277 } else if (present && value != object_input && AtLeastTagged(access)) {
278 // Key was present, and the value did not equal object_input. This means
279 // that there is a StoreField to this offset in the future, but the
280 // object instance comes from a different Node. We pessimistically
281 // assume that we cannot optimize current_node away. However, we will
282 // guess that the current StoreField is more relevant than the future
283 // one, record the current StoreField in futureStore instead, and
284 // continue ascending up the chain.
285 find_result->second = object_input;
286 TRACE("#%d[[+%d,%s]](#%d) -- wide enough, diff object, updated in map",
287 current_node->id(), offset,
288 MachineReprToString(access.machine_type.representation()),
289 object_input->id());
290 } else if (!present && AtLeastTagged(access)) {
291 // Key was not present. This means that there is no matching
292 // StoreField to this offset in the future, so we cannot optimize
293 // current_node away. However, we will record the current StoreField
294 // in futureStore, and continue ascending up the chain.
295 futureStore.insert(std::make_pair(offset, object_input));
296 TRACE(
297 "#%d[[+%d,%s]](#%d) -- wide enough, key not present, inserted in map",
298 current_node->id(), offset,
299 MachineReprToString(access.machine_type.representation()),
300 object_input->id());
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 { 482 } else {
306 UNREACHABLE(); 483 UNREACHABLE();
307 } 484 }
308 485
309 // Regardless of whether we eliminated node {current}, we want to 486 // Overwrite vector in-place.
310 // continue walking up the effect chain. 487 *stored_for_node = before_set;
311 488
312 current_node = previous; 489 Node* previous = PreviousEffectInChain(cur);
313 } while (current_node != nullptr && 490 if (previous == nullptr && cur_set_changed) {
314 current_node->op()->opcode() == IrOpcode::kStoreField); 491 TRACE("- Reached top of chain; marking effect inputs for revisiting.");
315 492 for (int i = 0; i < cur->op()->EffectInputCount(); i++) {
316 TRACE("finished"); 493 Node* input = NodeProperties::GetEffectInput(cur, i);
494 if (!CanObserveAnything(input) ||
495 unobservable_for_id(input->id()).IsUndetermined()) {
496 MarkForRevisit(input);
497 }
498 }
499
500 cur = nullptr;
501 } else if (previous == nullptr && !cur_set_changed) {
502 TRACE("+ Reached top of chain and stabilized.");
503 cur = nullptr;
504 } else {
505 // Update variables for next loop iteration
506 cur = previous;
507 DCHECK(EffectUseCount(previous) == 1);
508 after_set = before_set;
509 if (FLAG_turbo_verify_store_elimination) {
510 DCHECK(after_set == RecomputeUseIntersection(cur));
511 }
512 DCHECK_NOT_NULL(cur);
513 }
514 } while (cur != nullptr);
515 }
516
517 // Compute the intersection of the UnobservablesSets of all effect uses and
518 // return it. This function only works if {node} has an effect use.
519 //
520 // The result UnobservablesSet will always be determined.
521 UnobservablesSet StoreStoreFinder::RecomputeUseIntersection(Node* node) {
522 // {first} == true indicates that we haven't looked at any elements yet.
523 // {first} == false indicates that cur_set is the intersection of at least one
524 // thing.
525
526 bool first = true;
527 UnobservablesSet cur_set = UnobservablesSet::Undetermined(); // irrelevant
528
529 for (Edge edge : node->use_edges()) {
530 // Skip non-effect edges
531 if (!NodeProperties::IsEffectEdge(edge)) {
532 continue;
533 }
534
535 Node* use = edge.from();
536 UnobservablesSet new_set = unobservable_for_id(use->id());
537 // Include new_set in the intersection.
538 if (first) {
539 // Intersection of a one-element set is that one element
540 first = false;
541 cur_set = new_set;
542 } else {
543 // Take the intersection of cur_set and new_set.
544 cur_set = cur_set.Intersect(new_set, temp_zone());
545 }
546
547 if (FLAG_trace_store_elimination) {
548 // Serialise the UnobservablesSet.
549 std::ostringstream os;
550 os << "intersected with " << new_set << ", current intersection is "
551 << cur_set;
552 std::string msg = os.str();
553 }
554 }
555
556 if (first) {
557 // There were no effect uses.
558 auto opcode = node->op()->opcode();
559 // List of opcodes that may end this effect chain. The opcodes are not
560 // important to the soundness of this optimization; this serves as a
561 // general sanity check. Add opcodes to this list as it suits you.
562 //
563 // Everything is observable after these opcodes; return the empty set.
564 DCHECK_EXTRA(
565 opcode == IrOpcode::kReturn || opcode == IrOpcode::kTerminate ||
566 opcode == IrOpcode::kDeoptimize || opcode == IrOpcode::kThrow,
567 "for #%d:%s", node->id(), node->op()->mnemonic());
568 USE(opcode); // silence warning about unused variable
569
570 return UnobservablesSet::DeterminedEmpty(temp_zone());
571 } else {
572 if (cur_set.IsUndetermined()) {
573 cur_set = UnobservablesSet::DeterminedEmpty(temp_zone());
574 }
575
576 if (FLAG_trace_store_elimination) {
577 // Serialise the UnobservablesSet.
578 std::ostringstream os;
579 os << cur_set;
580 std::string msg = os.str();
581 }
582
583 return cur_set;
584 }
585 }
586
587 UnobservablesSet UnobservablesSet::Undetermined() { return UnobservablesSet(); }
588
589 UnobservablesSet::UnobservablesSet() : set_(nullptr) {}
590
591 UnobservablesSet UnobservablesSet::DeterminedEmpty(Zone* zone) {
592 // Create a new empty UnobservablesSet. This allocates in the zone, and
593 // can probably be optimized to use a global singleton.
594 ZoneSet<UnobservableStore>* empty_set =
595 new (zone->New(sizeof(ZoneSet<UnobservableStore>)))
596 ZoneSet<UnobservableStore>(zone);
597 return UnobservablesSet(empty_set);
598 }
599
600 // Computes the intersection of two UnobservablesSets. May return
601 // UnobservablesSet::Undetermined() instead of an empty UnobservablesSet for
602 // speed.
603 UnobservablesSet UnobservablesSet::Intersect(UnobservablesSet other,
604 Zone* zone) const {
605 if (set() == nullptr || other.set() == nullptr) {
606 return Undetermined();
607 } else if (other.set() == nullptr) {
608 return *this;
609 } else {
610 ZoneSet<UnobservableStore>* intersection =
611 new (zone->New(sizeof(ZoneSet<UnobservableStore>)))
612 ZoneSet<UnobservableStore>(zone);
613 // Put the intersection of set() and other.set() in intersection.
614 set_intersection(set()->begin(), set()->end(), other.set()->begin(),
615 other.set()->end(),
616 std::inserter(*intersection, intersection->end()));
617
618 return UnobservablesSet(intersection);
619 }
620 }
621
622 UnobservablesSet UnobservablesSet::Add(UnobservableStore obs,
623 Zone* zone) const {
624 bool present = (set()->find(obs) != set()->end());
625 if (present) {
626 return *this;
627 } else {
628 // Make a new empty set.
629 ZoneSet<UnobservableStore>* new_set =
630 new (zone->New(sizeof(ZoneSet<UnobservableStore>)))
631 ZoneSet<UnobservableStore>(zone);
632 // Copy the old elements over.
633 *new_set = *set();
634 // Add the new element.
635 bool inserted = new_set->insert(obs).second;
636 DCHECK(inserted);
637 USE(inserted); // silence warning about unused variable
638
639 return UnobservablesSet(new_set);
640 }
641 }
642
643 UnobservablesSet UnobservablesSet::RemoveSameOffset(StoreOffset offset,
644 Zone* zone) const {
645 // Make a new empty set.
646 ZoneSet<UnobservableStore>* new_set =
647 new (zone->New(sizeof(ZoneSet<UnobservableStore>)))
648 ZoneSet<UnobservableStore>(zone);
649 // Copy all elements over that have a different offset.
650 for (auto obs : *set()) {
651 if (obs.offset_ != offset) {
652 new_set->insert(obs);
653 }
654 }
655
656 return UnobservablesSet(new_set);
657 }
658
659 // Used for debugging.
660 std::ostream& operator<<(std::ostream& os, UnobservablesSet set) {
661 if (set.set() == nullptr) {
662 os << "(undetermined)";
663 } else {
664 os << "[";
665 bool first = true;
666 for (UnobservableStore obs : *set.set()) {
667 if (!first) {
668 os << ",";
669 } else {
670 first = false;
671 }
672 os << obs;
673 }
674 os << "]";
675 }
676 return os;
677 }
678
679 bool UnobservablesSet::operator==(const UnobservablesSet& other) const {
680 if (IsUndetermined() || other.IsUndetermined()) {
681 return IsEmpty() && other.IsEmpty();
682 } else {
683 // Both pointers guaranteed not to be nullptrs.
684 return *set() == *other.set();
685 }
686 }
687
688 bool UnobservablesSet::operator!=(const UnobservablesSet& other) const {
689 return !(*this == other);
690 }
691
692 bool UnobservableStore::operator==(const UnobservableStore other) const {
693 return (id_ == other.id_) && (offset_ == other.offset_);
694 }
695
696 bool UnobservableStore::operator!=(const UnobservableStore other) const {
697 return !(*this == other);
698 }
699
700 bool UnobservableStore::operator<(const UnobservableStore other) const {
701 return (id_ < other.id_) || (id_ == other.id_ && offset_ < other.offset_);
702 }
703
704 std::ostream& operator<<(std::ostream& os, UnobservableStore obs) {
705 os << "#" << obs.id_ << "[+" << obs.offset_ << "]";
706 return os;
317 } 707 }
318 708
319 } // namespace compiler 709 } // namespace compiler
320 } // namespace internal 710 } // namespace internal
321 } // namespace v8 711 } // namespace v8
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698