OLD | NEW |
| (Empty) |
1 // Copyright 2013 the V8 project authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "src/hydrogen-alias-analysis.h" | |
6 #include "src/hydrogen-flow-engine.h" | |
7 #include "src/hydrogen-instructions.h" | |
8 #include "src/hydrogen-load-elimination.h" | |
9 | |
10 namespace v8 { | |
11 namespace internal { | |
12 | |
13 #define GLOBAL true | |
14 #define TRACE(x) if (FLAG_trace_load_elimination) PrintF x | |
15 | |
16 static const int kMaxTrackedFields = 16; | |
17 static const int kMaxTrackedObjects = 5; | |
18 | |
19 // An element in the field approximation list. | |
20 class HFieldApproximation : public ZoneObject { | |
21 public: // Just a data blob. | |
22 HValue* object_; | |
23 HValue* last_value_; | |
24 HFieldApproximation* next_; | |
25 | |
26 // Recursively copy the entire linked list of field approximations. | |
27 HFieldApproximation* Copy(Zone* zone) { | |
28 HFieldApproximation* copy = new(zone) HFieldApproximation(); | |
29 copy->object_ = this->object_; | |
30 copy->last_value_ = this->last_value_; | |
31 copy->next_ = this->next_ == NULL ? NULL : this->next_->Copy(zone); | |
32 return copy; | |
33 } | |
34 }; | |
35 | |
36 | |
37 // The main datastructure used during load/store elimination. Each in-object | |
38 // field is tracked separately. For each field, store a list of known field | |
39 // values for known objects. | |
40 class HLoadEliminationTable : public ZoneObject { | |
41 public: | |
42 HLoadEliminationTable(Zone* zone, HAliasAnalyzer* aliasing) | |
43 : zone_(zone), fields_(kMaxTrackedFields, zone), aliasing_(aliasing) { } | |
44 | |
45 // The main processing of instructions. | |
46 HLoadEliminationTable* Process(HInstruction* instr, Zone* zone) { | |
47 switch (instr->opcode()) { | |
48 case HValue::kLoadNamedField: { | |
49 HLoadNamedField* l = HLoadNamedField::cast(instr); | |
50 TRACE((" process L%d field %d (o%d)\n", | |
51 instr->id(), | |
52 FieldOf(l->access()), | |
53 l->object()->ActualValue()->id())); | |
54 HValue* result = load(l); | |
55 if (result != instr && l->CanBeReplacedWith(result)) { | |
56 // The load can be replaced with a previous load or a value. | |
57 TRACE((" replace L%d -> v%d\n", instr->id(), result->id())); | |
58 instr->DeleteAndReplaceWith(result); | |
59 } | |
60 break; | |
61 } | |
62 case HValue::kStoreNamedField: { | |
63 HStoreNamedField* s = HStoreNamedField::cast(instr); | |
64 TRACE((" process S%d field %d (o%d) = v%d\n", | |
65 instr->id(), | |
66 FieldOf(s->access()), | |
67 s->object()->ActualValue()->id(), | |
68 s->value()->id())); | |
69 HValue* result = store(s); | |
70 if (result == NULL) { | |
71 // The store is redundant. Remove it. | |
72 TRACE((" remove S%d\n", instr->id())); | |
73 instr->DeleteAndReplaceWith(NULL); | |
74 } | |
75 break; | |
76 } | |
77 case HValue::kTransitionElementsKind: { | |
78 HTransitionElementsKind* t = HTransitionElementsKind::cast(instr); | |
79 HValue* object = t->object()->ActualValue(); | |
80 KillFieldInternal(object, FieldOf(JSArray::kElementsOffset), NULL); | |
81 KillFieldInternal(object, FieldOf(JSObject::kMapOffset), NULL); | |
82 break; | |
83 } | |
84 default: { | |
85 if (instr->CheckChangesFlag(kInobjectFields)) { | |
86 TRACE((" kill-all i%d\n", instr->id())); | |
87 Kill(); | |
88 break; | |
89 } | |
90 if (instr->CheckChangesFlag(kMaps)) { | |
91 TRACE((" kill-maps i%d\n", instr->id())); | |
92 KillOffset(JSObject::kMapOffset); | |
93 } | |
94 if (instr->CheckChangesFlag(kElementsKind)) { | |
95 TRACE((" kill-elements-kind i%d\n", instr->id())); | |
96 KillOffset(JSObject::kMapOffset); | |
97 KillOffset(JSObject::kElementsOffset); | |
98 } | |
99 if (instr->CheckChangesFlag(kElementsPointer)) { | |
100 TRACE((" kill-elements i%d\n", instr->id())); | |
101 KillOffset(JSObject::kElementsOffset); | |
102 } | |
103 if (instr->CheckChangesFlag(kOsrEntries)) { | |
104 TRACE((" kill-osr i%d\n", instr->id())); | |
105 Kill(); | |
106 } | |
107 } | |
108 // Improvements possible: | |
109 // - learn from HCheckMaps for field 0 | |
110 // - remove unobservable stores (write-after-write) | |
111 // - track cells | |
112 // - track globals | |
113 // - track roots | |
114 } | |
115 return this; | |
116 } | |
117 | |
118 // Support for global analysis with HFlowEngine: Merge given state with | |
119 // the other incoming state. | |
120 static HLoadEliminationTable* Merge(HLoadEliminationTable* succ_state, | |
121 HBasicBlock* succ_block, | |
122 HLoadEliminationTable* pred_state, | |
123 HBasicBlock* pred_block, | |
124 Zone* zone) { | |
125 DCHECK(pred_state != NULL); | |
126 if (succ_state == NULL) { | |
127 return pred_state->Copy(succ_block, pred_block, zone); | |
128 } else { | |
129 return succ_state->Merge(succ_block, pred_state, pred_block, zone); | |
130 } | |
131 } | |
132 | |
133 // Support for global analysis with HFlowEngine: Given state merged with all | |
134 // the other incoming states, prepare it for use. | |
135 static HLoadEliminationTable* Finish(HLoadEliminationTable* state, | |
136 HBasicBlock* block, | |
137 Zone* zone) { | |
138 DCHECK(state != NULL); | |
139 return state; | |
140 } | |
141 | |
142 private: | |
143 // Copy state to successor block. | |
144 HLoadEliminationTable* Copy(HBasicBlock* succ, HBasicBlock* from_block, | |
145 Zone* zone) { | |
146 HLoadEliminationTable* copy = | |
147 new(zone) HLoadEliminationTable(zone, aliasing_); | |
148 copy->EnsureFields(fields_.length()); | |
149 for (int i = 0; i < fields_.length(); i++) { | |
150 copy->fields_[i] = fields_[i] == NULL ? NULL : fields_[i]->Copy(zone); | |
151 } | |
152 if (FLAG_trace_load_elimination) { | |
153 TRACE((" copy-to B%d\n", succ->block_id())); | |
154 copy->Print(); | |
155 } | |
156 return copy; | |
157 } | |
158 | |
159 // Merge this state with the other incoming state. | |
160 HLoadEliminationTable* Merge(HBasicBlock* succ, HLoadEliminationTable* that, | |
161 HBasicBlock* that_block, Zone* zone) { | |
162 if (that->fields_.length() < fields_.length()) { | |
163 // Drop fields not in the other table. | |
164 fields_.Rewind(that->fields_.length()); | |
165 } | |
166 for (int i = 0; i < fields_.length(); i++) { | |
167 // Merge the field approximations for like fields. | |
168 HFieldApproximation* approx = fields_[i]; | |
169 HFieldApproximation* prev = NULL; | |
170 while (approx != NULL) { | |
171 // TODO(titzer): Merging is O(N * M); sort? | |
172 HFieldApproximation* other = that->Find(approx->object_, i); | |
173 if (other == NULL || !Equal(approx->last_value_, other->last_value_)) { | |
174 // Kill an entry that doesn't agree with the other value. | |
175 if (prev != NULL) { | |
176 prev->next_ = approx->next_; | |
177 } else { | |
178 fields_[i] = approx->next_; | |
179 } | |
180 approx = approx->next_; | |
181 continue; | |
182 } | |
183 prev = approx; | |
184 approx = approx->next_; | |
185 } | |
186 } | |
187 if (FLAG_trace_load_elimination) { | |
188 TRACE((" merge-to B%d\n", succ->block_id())); | |
189 Print(); | |
190 } | |
191 return this; | |
192 } | |
193 | |
194 friend class HLoadEliminationEffects; // Calls Kill() and others. | |
195 friend class HLoadEliminationPhase; | |
196 | |
197 private: | |
198 // Process a load instruction, updating internal table state. If a previous | |
199 // load or store for this object and field exists, return the new value with | |
200 // which the load should be replaced. Otherwise, return {instr}. | |
201 HValue* load(HLoadNamedField* instr) { | |
202 // There must be no loads from non observable in-object properties. | |
203 DCHECK(!instr->access().IsInobject() || | |
204 instr->access().existing_inobject_property()); | |
205 | |
206 int field = FieldOf(instr->access()); | |
207 if (field < 0) return instr; | |
208 | |
209 HValue* object = instr->object()->ActualValue(); | |
210 HFieldApproximation* approx = FindOrCreate(object, field); | |
211 | |
212 if (approx->last_value_ == NULL) { | |
213 // Load is not redundant. Fill out a new entry. | |
214 approx->last_value_ = instr; | |
215 return instr; | |
216 } else if (approx->last_value_->block()->EqualToOrDominates( | |
217 instr->block())) { | |
218 // Eliminate the load. Reuse previously stored value or load instruction. | |
219 return approx->last_value_; | |
220 } else { | |
221 return instr; | |
222 } | |
223 } | |
224 | |
225 // Process a store instruction, updating internal table state. If a previous | |
226 // store to the same object and field makes this store redundant (e.g. because | |
227 // the stored values are the same), return NULL indicating that this store | |
228 // instruction is redundant. Otherwise, return {instr}. | |
229 HValue* store(HStoreNamedField* instr) { | |
230 if (instr->access().IsInobject() && | |
231 !instr->access().existing_inobject_property()) { | |
232 TRACE((" skipping non existing property initialization store\n")); | |
233 return instr; | |
234 } | |
235 | |
236 int field = FieldOf(instr->access()); | |
237 if (field < 0) return KillIfMisaligned(instr); | |
238 | |
239 HValue* object = instr->object()->ActualValue(); | |
240 HValue* value = instr->value(); | |
241 | |
242 if (instr->has_transition()) { | |
243 // A transition introduces a new field and alters the map of the object. | |
244 // Since the field in the object is new, it cannot alias existing entries. | |
245 // TODO(titzer): introduce a constant for the new map and remember it. | |
246 KillFieldInternal(object, FieldOf(JSObject::kMapOffset), NULL); | |
247 } else { | |
248 // Kill non-equivalent may-alias entries. | |
249 KillFieldInternal(object, field, value); | |
250 } | |
251 HFieldApproximation* approx = FindOrCreate(object, field); | |
252 | |
253 if (Equal(approx->last_value_, value)) { | |
254 // The store is redundant because the field already has this value. | |
255 return NULL; | |
256 } else { | |
257 // The store is not redundant. Update the entry. | |
258 approx->last_value_ = value; | |
259 return instr; | |
260 } | |
261 } | |
262 | |
263 // Kill everything in this table. | |
264 void Kill() { | |
265 fields_.Rewind(0); | |
266 } | |
267 | |
268 // Kill all entries matching the given offset. | |
269 void KillOffset(int offset) { | |
270 int field = FieldOf(offset); | |
271 if (field >= 0 && field < fields_.length()) { | |
272 fields_[field] = NULL; | |
273 } | |
274 } | |
275 | |
276 // Kill all entries aliasing the given store. | |
277 void KillStore(HStoreNamedField* s) { | |
278 int field = FieldOf(s->access()); | |
279 if (field >= 0) { | |
280 KillFieldInternal(s->object()->ActualValue(), field, s->value()); | |
281 } else { | |
282 KillIfMisaligned(s); | |
283 } | |
284 } | |
285 | |
286 // Kill multiple entries in the case of a misaligned store. | |
287 HValue* KillIfMisaligned(HStoreNamedField* instr) { | |
288 HObjectAccess access = instr->access(); | |
289 if (access.IsInobject()) { | |
290 int offset = access.offset(); | |
291 if ((offset % kPointerSize) != 0) { | |
292 // Kill the field containing the first word of the access. | |
293 HValue* object = instr->object()->ActualValue(); | |
294 int field = offset / kPointerSize; | |
295 KillFieldInternal(object, field, NULL); | |
296 | |
297 // Kill the next field in case of overlap. | |
298 int size = access.representation().size(); | |
299 int next_field = (offset + size - 1) / kPointerSize; | |
300 if (next_field != field) KillFieldInternal(object, next_field, NULL); | |
301 } | |
302 } | |
303 return instr; | |
304 } | |
305 | |
306 // Find an entry for the given object and field pair. | |
307 HFieldApproximation* Find(HValue* object, int field) { | |
308 // Search for a field approximation for this object. | |
309 HFieldApproximation* approx = fields_[field]; | |
310 while (approx != NULL) { | |
311 if (aliasing_->MustAlias(object, approx->object_)) return approx; | |
312 approx = approx->next_; | |
313 } | |
314 return NULL; | |
315 } | |
316 | |
317 // Find or create an entry for the given object and field pair. | |
318 HFieldApproximation* FindOrCreate(HValue* object, int field) { | |
319 EnsureFields(field + 1); | |
320 | |
321 // Search for a field approximation for this object. | |
322 HFieldApproximation* approx = fields_[field]; | |
323 int count = 0; | |
324 while (approx != NULL) { | |
325 if (aliasing_->MustAlias(object, approx->object_)) return approx; | |
326 count++; | |
327 approx = approx->next_; | |
328 } | |
329 | |
330 if (count >= kMaxTrackedObjects) { | |
331 // Pull the last entry off the end and repurpose it for this object. | |
332 approx = ReuseLastApproximation(field); | |
333 } else { | |
334 // Allocate a new entry. | |
335 approx = new(zone_) HFieldApproximation(); | |
336 } | |
337 | |
338 // Insert the entry at the head of the list. | |
339 approx->object_ = object; | |
340 approx->last_value_ = NULL; | |
341 approx->next_ = fields_[field]; | |
342 fields_[field] = approx; | |
343 | |
344 return approx; | |
345 } | |
346 | |
347 // Kill all entries for a given field that _may_ alias the given object | |
348 // and do _not_ have the given value. | |
349 void KillFieldInternal(HValue* object, int field, HValue* value) { | |
350 if (field >= fields_.length()) return; // Nothing to do. | |
351 | |
352 HFieldApproximation* approx = fields_[field]; | |
353 HFieldApproximation* prev = NULL; | |
354 while (approx != NULL) { | |
355 if (aliasing_->MayAlias(object, approx->object_)) { | |
356 if (!Equal(approx->last_value_, value)) { | |
357 // Kill an aliasing entry that doesn't agree on the value. | |
358 if (prev != NULL) { | |
359 prev->next_ = approx->next_; | |
360 } else { | |
361 fields_[field] = approx->next_; | |
362 } | |
363 approx = approx->next_; | |
364 continue; | |
365 } | |
366 } | |
367 prev = approx; | |
368 approx = approx->next_; | |
369 } | |
370 } | |
371 | |
372 bool Equal(HValue* a, HValue* b) { | |
373 if (a == b) return true; | |
374 if (a != NULL && b != NULL && a->CheckFlag(HValue::kUseGVN)) { | |
375 return a->Equals(b); | |
376 } | |
377 return false; | |
378 } | |
379 | |
380 // Remove the last approximation for a field so that it can be reused. | |
381 // We reuse the last entry because it was the first inserted and is thus | |
382 // farthest away from the current instruction. | |
383 HFieldApproximation* ReuseLastApproximation(int field) { | |
384 HFieldApproximation* approx = fields_[field]; | |
385 DCHECK(approx != NULL); | |
386 | |
387 HFieldApproximation* prev = NULL; | |
388 while (approx->next_ != NULL) { | |
389 prev = approx; | |
390 approx = approx->next_; | |
391 } | |
392 if (prev != NULL) prev->next_ = NULL; | |
393 return approx; | |
394 } | |
395 | |
396 // Compute the field index for the given object access; -1 if not tracked. | |
397 int FieldOf(HObjectAccess access) { | |
398 return access.IsInobject() ? FieldOf(access.offset()) : -1; | |
399 } | |
400 | |
401 // Compute the field index for the given in-object offset; -1 if not tracked. | |
402 int FieldOf(int offset) { | |
403 if (offset >= kMaxTrackedFields * kPointerSize) return -1; | |
404 // TODO(titzer): track misaligned loads in a separate list? | |
405 if ((offset % kPointerSize) != 0) return -1; // Ignore misaligned accesses. | |
406 return offset / kPointerSize; | |
407 } | |
408 | |
409 // Ensure internal storage for the given number of fields. | |
410 void EnsureFields(int num_fields) { | |
411 if (fields_.length() < num_fields) { | |
412 fields_.AddBlock(NULL, num_fields - fields_.length(), zone_); | |
413 } | |
414 } | |
415 | |
416 // Print this table to stdout. | |
417 void Print() { | |
418 for (int i = 0; i < fields_.length(); i++) { | |
419 PrintF(" field %d: ", i); | |
420 for (HFieldApproximation* a = fields_[i]; a != NULL; a = a->next_) { | |
421 PrintF("[o%d =", a->object_->id()); | |
422 if (a->last_value_ != NULL) PrintF(" v%d", a->last_value_->id()); | |
423 PrintF("] "); | |
424 } | |
425 PrintF("\n"); | |
426 } | |
427 } | |
428 | |
429 Zone* zone_; | |
430 ZoneList<HFieldApproximation*> fields_; | |
431 HAliasAnalyzer* aliasing_; | |
432 }; | |
433 | |
434 | |
435 // Support for HFlowEngine: collect store effects within loops. | |
436 class HLoadEliminationEffects : public ZoneObject { | |
437 public: | |
438 explicit HLoadEliminationEffects(Zone* zone) | |
439 : zone_(zone), stores_(5, zone) { } | |
440 | |
441 inline bool Disabled() { | |
442 return false; // Effects are _not_ disabled. | |
443 } | |
444 | |
445 // Process a possibly side-effecting instruction. | |
446 void Process(HInstruction* instr, Zone* zone) { | |
447 if (instr->IsStoreNamedField()) { | |
448 stores_.Add(HStoreNamedField::cast(instr), zone_); | |
449 } else { | |
450 flags_.Add(instr->ChangesFlags()); | |
451 } | |
452 } | |
453 | |
454 // Apply these effects to the given load elimination table. | |
455 void Apply(HLoadEliminationTable* table) { | |
456 // Loads must not be hoisted past the OSR entry, therefore we kill | |
457 // everything if we see an OSR entry. | |
458 if (flags_.Contains(kInobjectFields) || flags_.Contains(kOsrEntries)) { | |
459 table->Kill(); | |
460 return; | |
461 } | |
462 if (flags_.Contains(kElementsKind) || flags_.Contains(kMaps)) { | |
463 table->KillOffset(JSObject::kMapOffset); | |
464 } | |
465 if (flags_.Contains(kElementsKind) || flags_.Contains(kElementsPointer)) { | |
466 table->KillOffset(JSObject::kElementsOffset); | |
467 } | |
468 | |
469 // Kill non-agreeing fields for each store contained in these effects. | |
470 for (int i = 0; i < stores_.length(); i++) { | |
471 table->KillStore(stores_[i]); | |
472 } | |
473 } | |
474 | |
475 // Union these effects with the other effects. | |
476 void Union(HLoadEliminationEffects* that, Zone* zone) { | |
477 flags_.Add(that->flags_); | |
478 for (int i = 0; i < that->stores_.length(); i++) { | |
479 stores_.Add(that->stores_[i], zone); | |
480 } | |
481 } | |
482 | |
483 private: | |
484 Zone* zone_; | |
485 GVNFlagSet flags_; | |
486 ZoneList<HStoreNamedField*> stores_; | |
487 }; | |
488 | |
489 | |
490 // The main routine of the analysis phase. Use the HFlowEngine for either a | |
491 // local or a global analysis. | |
492 void HLoadEliminationPhase::Run() { | |
493 HFlowEngine<HLoadEliminationTable, HLoadEliminationEffects> | |
494 engine(graph(), zone()); | |
495 HAliasAnalyzer aliasing; | |
496 HLoadEliminationTable* table = | |
497 new(zone()) HLoadEliminationTable(zone(), &aliasing); | |
498 | |
499 if (GLOBAL) { | |
500 // Perform a global analysis. | |
501 engine.AnalyzeDominatedBlocks(graph()->blocks()->at(0), table); | |
502 } else { | |
503 // Perform only local analysis. | |
504 for (int i = 0; i < graph()->blocks()->length(); i++) { | |
505 table->Kill(); | |
506 engine.AnalyzeOneBlock(graph()->blocks()->at(i), table); | |
507 } | |
508 } | |
509 } | |
510 | |
511 } // namespace internal | |
512 } // namespace v8 | |
OLD | NEW |