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/crankshaft/hydrogen-load-elimination.h"
6 
7 #include "src/crankshaft/hydrogen-alias-analysis.h"
8 #include "src/crankshaft/hydrogen-flow-engine.h"
9 #include "src/crankshaft/hydrogen-instructions.h"
10 
11 namespace v8 {
12 namespace internal {
13 
14 #define GLOBAL true
15 #define TRACE(x) if (FLAG_trace_load_elimination) PrintF x
16 
17 static const int kMaxTrackedFields = 16;
18 static const int kMaxTrackedObjects = 5;
19 
20 // An element in the field approximation list.
21 class HFieldApproximation : public ZoneObject {
22  public:  // Just a data blob.
23   HValue* object_;
24   HValue* last_value_;
25   HFieldApproximation* next_;
26 
27   // Recursively copy the entire linked list of field approximations.
Copy(Zone * zone)28   HFieldApproximation* Copy(Zone* zone) {
29     HFieldApproximation* copy = new(zone) HFieldApproximation();
30     copy->object_ = this->object_;
31     copy->last_value_ = this->last_value_;
32     copy->next_ = this->next_ == NULL ? NULL : this->next_->Copy(zone);
33     return copy;
34   }
35 };
36 
37 
38 // The main datastructure used during load/store elimination. Each in-object
39 // field is tracked separately. For each field, store a list of known field
40 // values for known objects.
41 class HLoadEliminationTable : public ZoneObject {
42  public:
HLoadEliminationTable(Zone * zone,HAliasAnalyzer * aliasing)43   HLoadEliminationTable(Zone* zone, HAliasAnalyzer* aliasing)
44     : zone_(zone), fields_(kMaxTrackedFields, zone), aliasing_(aliasing) { }
45 
46   // The main processing of instructions.
Process(HInstruction * instr,Zone * zone)47   HLoadEliminationTable* Process(HInstruction* instr, Zone* zone) {
48     switch (instr->opcode()) {
49       case HValue::kLoadNamedField: {
50         HLoadNamedField* l = HLoadNamedField::cast(instr);
51         TRACE((" process L%d field %d (o%d)\n",
52                instr->id(),
53                FieldOf(l->access()),
54                l->object()->ActualValue()->id()));
55         HValue* result = load(l);
56         if (result != instr && l->CanBeReplacedWith(result)) {
57           // The load can be replaced with a previous load or a value.
58           TRACE(("  replace L%d -> v%d\n", instr->id(), result->id()));
59           instr->DeleteAndReplaceWith(result);
60         }
61         break;
62       }
63       case HValue::kStoreNamedField: {
64         HStoreNamedField* s = HStoreNamedField::cast(instr);
65         TRACE((" process S%d field %d (o%d) = v%d\n",
66                instr->id(),
67                FieldOf(s->access()),
68                s->object()->ActualValue()->id(),
69                s->value()->id()));
70         HValue* result = store(s);
71         if (result == NULL) {
72           // The store is redundant. Remove it.
73           TRACE(("  remove S%d\n", instr->id()));
74           instr->DeleteAndReplaceWith(NULL);
75         }
76         break;
77       }
78       case HValue::kTransitionElementsKind: {
79         HTransitionElementsKind* t = HTransitionElementsKind::cast(instr);
80         HValue* object = t->object()->ActualValue();
81         KillFieldInternal(object, FieldOf(JSArray::kElementsOffset), NULL);
82         KillFieldInternal(object, FieldOf(JSObject::kMapOffset), NULL);
83         break;
84       }
85       default: {
86         if (instr->CheckChangesFlag(kInobjectFields)) {
87           TRACE((" kill-all i%d\n", instr->id()));
88           Kill();
89           break;
90         }
91         if (instr->CheckChangesFlag(kMaps)) {
92           TRACE((" kill-maps i%d\n", instr->id()));
93           KillOffset(JSObject::kMapOffset);
94         }
95         if (instr->CheckChangesFlag(kElementsKind)) {
96           TRACE((" kill-elements-kind i%d\n", instr->id()));
97           KillOffset(JSObject::kMapOffset);
98           KillOffset(JSObject::kElementsOffset);
99         }
100         if (instr->CheckChangesFlag(kElementsPointer)) {
101           TRACE((" kill-elements i%d\n", instr->id()));
102           KillOffset(JSObject::kElementsOffset);
103         }
104         if (instr->CheckChangesFlag(kOsrEntries)) {
105           TRACE((" kill-osr i%d\n", instr->id()));
106           Kill();
107         }
108       }
109       // Improvements possible:
110       // - learn from HCheckMaps for field 0
111       // - remove unobservable stores (write-after-write)
112       // - track cells
113       // - track globals
114       // - track roots
115     }
116     return this;
117   }
118 
119   // Support for global analysis with HFlowEngine: Merge given state with
120   // the other incoming state.
Merge(HLoadEliminationTable * succ_state,HBasicBlock * succ_block,HLoadEliminationTable * pred_state,HBasicBlock * pred_block,Zone * zone)121   static HLoadEliminationTable* Merge(HLoadEliminationTable* succ_state,
122                                       HBasicBlock* succ_block,
123                                       HLoadEliminationTable* pred_state,
124                                       HBasicBlock* pred_block,
125                                       Zone* zone) {
126     DCHECK(pred_state != NULL);
127     if (succ_state == NULL) {
128       return pred_state->Copy(succ_block, pred_block, zone);
129     } else {
130       return succ_state->Merge(succ_block, pred_state, pred_block, zone);
131     }
132   }
133 
134   // Support for global analysis with HFlowEngine: Given state merged with all
135   // the other incoming states, prepare it for use.
Finish(HLoadEliminationTable * state,HBasicBlock * block,Zone * zone)136   static HLoadEliminationTable* Finish(HLoadEliminationTable* state,
137                                        HBasicBlock* block,
138                                        Zone* zone) {
139     DCHECK(state != NULL);
140     return state;
141   }
142 
143  private:
144   // Copy state to successor block.
Copy(HBasicBlock * succ,HBasicBlock * from_block,Zone * zone)145   HLoadEliminationTable* Copy(HBasicBlock* succ, HBasicBlock* from_block,
146                               Zone* zone) {
147     HLoadEliminationTable* copy =
148         new(zone) HLoadEliminationTable(zone, aliasing_);
149     copy->EnsureFields(fields_.length());
150     for (int i = 0; i < fields_.length(); i++) {
151       copy->fields_[i] = fields_[i] == NULL ? NULL : fields_[i]->Copy(zone);
152     }
153     if (FLAG_trace_load_elimination) {
154       TRACE((" copy-to B%d\n", succ->block_id()));
155       copy->Print();
156     }
157     return copy;
158   }
159 
160   // Merge this state with the other incoming state.
Merge(HBasicBlock * succ,HLoadEliminationTable * that,HBasicBlock * that_block,Zone * zone)161   HLoadEliminationTable* Merge(HBasicBlock* succ, HLoadEliminationTable* that,
162                                HBasicBlock* that_block, Zone* zone) {
163     if (that->fields_.length() < fields_.length()) {
164       // Drop fields not in the other table.
165       fields_.Rewind(that->fields_.length());
166     }
167     for (int i = 0; i < fields_.length(); i++) {
168       // Merge the field approximations for like fields.
169       HFieldApproximation* approx = fields_[i];
170       HFieldApproximation* prev = NULL;
171       while (approx != NULL) {
172         // TODO(titzer): Merging is O(N * M); sort?
173         HFieldApproximation* other = that->Find(approx->object_, i);
174         if (other == NULL || !Equal(approx->last_value_, other->last_value_)) {
175           // Kill an entry that doesn't agree with the other value.
176           if (prev != NULL) {
177             prev->next_ = approx->next_;
178           } else {
179             fields_[i] = approx->next_;
180           }
181           approx = approx->next_;
182           continue;
183         }
184         prev = approx;
185         approx = approx->next_;
186       }
187     }
188     if (FLAG_trace_load_elimination) {
189       TRACE((" merge-to B%d\n", succ->block_id()));
190       Print();
191     }
192     return this;
193   }
194 
195   friend class HLoadEliminationEffects;  // Calls Kill() and others.
196   friend class HLoadEliminationPhase;
197 
198  private:
199   // Process a load instruction, updating internal table state. If a previous
200   // load or store for this object and field exists, return the new value with
201   // which the load should be replaced. Otherwise, return {instr}.
load(HLoadNamedField * instr)202   HValue* load(HLoadNamedField* instr) {
203     // There must be no loads from non observable in-object properties.
204     DCHECK(!instr->access().IsInobject() ||
205            instr->access().existing_inobject_property());
206 
207     int field = FieldOf(instr->access());
208     if (field < 0) return instr;
209 
210     HValue* object = instr->object()->ActualValue();
211     HFieldApproximation* approx = FindOrCreate(object, field);
212 
213     if (approx->last_value_ == NULL) {
214       // Load is not redundant. Fill out a new entry.
215       approx->last_value_ = instr;
216       return instr;
217     } else if (approx->last_value_->block()->EqualToOrDominates(
218         instr->block())) {
219       // Eliminate the load. Reuse previously stored value or load instruction.
220       return approx->last_value_;
221     } else {
222       return instr;
223     }
224   }
225 
226   // Process a store instruction, updating internal table state. If a previous
227   // store to the same object and field makes this store redundant (e.g. because
228   // the stored values are the same), return NULL indicating that this store
229   // instruction is redundant. Otherwise, return {instr}.
store(HStoreNamedField * instr)230   HValue* store(HStoreNamedField* instr) {
231     if (instr->access().IsInobject() &&
232         !instr->access().existing_inobject_property()) {
233       TRACE(("  skipping non existing property initialization store\n"));
234       return instr;
235     }
236 
237     int field = FieldOf(instr->access());
238     if (field < 0) return KillIfMisaligned(instr);
239 
240     HValue* object = instr->object()->ActualValue();
241     HValue* value = instr->value();
242 
243     if (instr->has_transition()) {
244       // A transition introduces a new field and alters the map of the object.
245       // Since the field in the object is new, it cannot alias existing entries.
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.
Kill()264   void Kill() {
265     fields_.Rewind(0);
266   }
267 
268   // Kill all entries matching the given offset.
KillOffset(int 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.
KillStore(HStoreNamedField * s)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.
KillIfMisaligned(HStoreNamedField * instr)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.
Find(HValue * object,int field)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.
FindOrCreate(HValue * object,int field)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.
KillFieldInternal(HValue * object,int field,HValue * 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 
Equal(HValue * a,HValue * b)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.
ReuseLastApproximation(int field)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.
FieldOf(HObjectAccess access)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.
FieldOf(int offset)402   int FieldOf(int offset) {
403     if (offset >= kMaxTrackedFields * kPointerSize) return -1;
404     if ((offset % kPointerSize) != 0) return -1;  // Ignore misaligned accesses.
405     return offset / kPointerSize;
406   }
407 
408   // Ensure internal storage for the given number of fields.
EnsureFields(int num_fields)409   void EnsureFields(int num_fields) {
410     if (fields_.length() < num_fields) {
411       fields_.AddBlock(NULL, num_fields - fields_.length(), zone_);
412     }
413   }
414 
415   // Print this table to stdout.
Print()416   void Print() {
417     for (int i = 0; i < fields_.length(); i++) {
418       PrintF("  field %d: ", i);
419       for (HFieldApproximation* a = fields_[i]; a != NULL; a = a->next_) {
420         PrintF("[o%d =", a->object_->id());
421         if (a->last_value_ != NULL) PrintF(" v%d", a->last_value_->id());
422         PrintF("] ");
423       }
424       PrintF("\n");
425     }
426   }
427 
428   Zone* zone_;
429   ZoneList<HFieldApproximation*> fields_;
430   HAliasAnalyzer* aliasing_;
431 };
432 
433 
434 // Support for HFlowEngine: collect store effects within loops.
435 class HLoadEliminationEffects : public ZoneObject {
436  public:
HLoadEliminationEffects(Zone * zone)437   explicit HLoadEliminationEffects(Zone* zone)
438     : zone_(zone), stores_(5, zone) { }
439 
Disabled()440   inline bool Disabled() {
441     return false;  // Effects are _not_ disabled.
442   }
443 
444   // Process a possibly side-effecting instruction.
Process(HInstruction * instr,Zone * zone)445   void Process(HInstruction* instr, Zone* zone) {
446     if (instr->IsStoreNamedField()) {
447       stores_.Add(HStoreNamedField::cast(instr), zone_);
448     } else {
449       flags_.Add(instr->ChangesFlags());
450     }
451   }
452 
453   // Apply these effects to the given load elimination table.
Apply(HLoadEliminationTable * table)454   void Apply(HLoadEliminationTable* table) {
455     // Loads must not be hoisted past the OSR entry, therefore we kill
456     // everything if we see an OSR entry.
457     if (flags_.Contains(kInobjectFields) || flags_.Contains(kOsrEntries)) {
458       table->Kill();
459       return;
460     }
461     if (flags_.Contains(kElementsKind) || flags_.Contains(kMaps)) {
462       table->KillOffset(JSObject::kMapOffset);
463     }
464     if (flags_.Contains(kElementsKind) || flags_.Contains(kElementsPointer)) {
465       table->KillOffset(JSObject::kElementsOffset);
466     }
467 
468     // Kill non-agreeing fields for each store contained in these effects.
469     for (int i = 0; i < stores_.length(); i++) {
470       table->KillStore(stores_[i]);
471     }
472   }
473 
474   // Union these effects with the other effects.
Union(HLoadEliminationEffects * that,Zone * zone)475   void Union(HLoadEliminationEffects* that, Zone* zone) {
476     flags_.Add(that->flags_);
477     for (int i = 0; i < that->stores_.length(); i++) {
478       stores_.Add(that->stores_[i], zone);
479     }
480   }
481 
482  private:
483   Zone* zone_;
484   GVNFlagSet flags_;
485   ZoneList<HStoreNamedField*> stores_;
486 };
487 
488 
489 // The main routine of the analysis phase. Use the HFlowEngine for either a
490 // local or a global analysis.
Run()491 void HLoadEliminationPhase::Run() {
492   HFlowEngine<HLoadEliminationTable, HLoadEliminationEffects>
493     engine(graph(), zone());
494   HAliasAnalyzer aliasing;
495   HLoadEliminationTable* table =
496       new(zone()) HLoadEliminationTable(zone(), &aliasing);
497 
498   if (GLOBAL) {
499     // Perform a global analysis.
500     engine.AnalyzeDominatedBlocks(graph()->blocks()->at(0), table);
501   } else {
502     // Perform only local analysis.
503     for (int i = 0; i < graph()->blocks()->length(); i++) {
504       table->Kill();
505       engine.AnalyzeOneBlock(graph()->blocks()->at(i), table);
506     }
507   }
508 }
509 
510 }  // namespace internal
511 }  // namespace v8
512