• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2013 the V8 project authors. All rights reserved.
2 // Redistribution and use in source and binary forms, with or without
3 // modification, are permitted provided that the following conditions are
4 // met:
5 //
6 //     * Redistributions of source code must retain the above copyright
7 //       notice, this list of conditions and the following disclaimer.
8 //     * Redistributions in binary form must reproduce the above
9 //       copyright notice, this list of conditions and the following
10 //       disclaimer in the documentation and/or other materials provided
11 //       with the distribution.
12 //     * Neither the name of Google Inc. nor the names of its
13 //       contributors may be used to endorse or promote products derived
14 //       from this software without specific prior written permission.
15 //
16 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 
28 #include "hydrogen-alias-analysis.h"
29 #include "hydrogen-load-elimination.h"
30 #include "hydrogen-instructions.h"
31 #include "hydrogen-flow-engine.h"
32 
33 namespace v8 {
34 namespace internal {
35 
36 #define GLOBAL true
37 #define TRACE(x) if (FLAG_trace_load_elimination) PrintF x
38 
39 static const int kMaxTrackedFields = 16;
40 static const int kMaxTrackedObjects = 5;
41 
42 // An element in the field approximation list.
43 class HFieldApproximation : public ZoneObject {
44  public:  // Just a data blob.
45   HValue* object_;
46   HLoadNamedField* last_load_;
47   HValue* last_value_;
48   HFieldApproximation* next_;
49 
50   // Recursively copy the entire linked list of field approximations.
Copy(Zone * zone)51   HFieldApproximation* Copy(Zone* zone) {
52     if (this == NULL) return NULL;
53     HFieldApproximation* copy = new(zone) HFieldApproximation();
54     copy->object_ = this->object_;
55     copy->last_load_ = this->last_load_;
56     copy->last_value_ = this->last_value_;
57     copy->next_ = this->next_->Copy(zone);
58     return copy;
59   }
60 };
61 
62 
63 // The main datastructure used during load/store elimination. Each in-object
64 // field is tracked separately. For each field, store a list of known field
65 // values for known objects.
66 class HLoadEliminationTable : public ZoneObject {
67  public:
HLoadEliminationTable(Zone * zone,HAliasAnalyzer * aliasing)68   HLoadEliminationTable(Zone* zone, HAliasAnalyzer* aliasing)
69     : zone_(zone), fields_(kMaxTrackedFields, zone), aliasing_(aliasing) { }
70 
71   // The main processing of instructions.
Process(HInstruction * instr,Zone * zone)72   HLoadEliminationTable* Process(HInstruction* instr, Zone* zone) {
73     switch (instr->opcode()) {
74       case HValue::kLoadNamedField: {
75         HLoadNamedField* l = HLoadNamedField::cast(instr);
76         TRACE((" process L%d field %d (o%d)\n",
77                instr->id(),
78                FieldOf(l->access()),
79                l->object()->ActualValue()->id()));
80         HValue* result = load(l);
81         if (result != instr) {
82           // The load can be replaced with a previous load or a value.
83           TRACE(("  replace L%d -> v%d\n", instr->id(), result->id()));
84           instr->DeleteAndReplaceWith(result);
85         }
86         break;
87       }
88       case HValue::kStoreNamedField: {
89         HStoreNamedField* s = HStoreNamedField::cast(instr);
90         TRACE((" process S%d field %d (o%d) = v%d\n",
91                instr->id(),
92                FieldOf(s->access()),
93                s->object()->ActualValue()->id(),
94                s->value()->id()));
95         HValue* result = store(s);
96         if (result == NULL) {
97           // The store is redundant. Remove it.
98           TRACE(("  remove S%d\n", instr->id()));
99           instr->DeleteAndReplaceWith(NULL);
100         }
101         break;
102       }
103       default: {
104         if (instr->CheckGVNFlag(kChangesInobjectFields)) {
105           TRACE((" kill-all i%d\n", instr->id()));
106           Kill();
107           break;
108         }
109         if (instr->CheckGVNFlag(kChangesMaps)) {
110           TRACE((" kill-maps i%d\n", instr->id()));
111           KillOffset(JSObject::kMapOffset);
112         }
113         if (instr->CheckGVNFlag(kChangesElementsKind)) {
114           TRACE((" kill-elements-kind i%d\n", instr->id()));
115           KillOffset(JSObject::kMapOffset);
116           KillOffset(JSObject::kElementsOffset);
117         }
118         if (instr->CheckGVNFlag(kChangesElementsPointer)) {
119           TRACE((" kill-elements i%d\n", instr->id()));
120           KillOffset(JSObject::kElementsOffset);
121         }
122         if (instr->CheckGVNFlag(kChangesOsrEntries)) {
123           TRACE((" kill-osr i%d\n", instr->id()));
124           Kill();
125         }
126       }
127       // Improvements possible:
128       // - learn from HCheckMaps for field 0
129       // - remove unobservable stores (write-after-write)
130       // - track cells
131       // - track globals
132       // - track roots
133     }
134     return this;
135   }
136 
137   // Support for global analysis with HFlowEngine: Copy state to sucessor block.
Copy(HBasicBlock * succ,Zone * zone)138   HLoadEliminationTable* Copy(HBasicBlock* succ, Zone* zone) {
139     HLoadEliminationTable* copy =
140         new(zone) HLoadEliminationTable(zone, aliasing_);
141     copy->EnsureFields(fields_.length());
142     for (int i = 0; i < fields_.length(); i++) {
143       copy->fields_[i] = fields_[i]->Copy(zone);
144     }
145     if (FLAG_trace_load_elimination) {
146       TRACE((" copy-to B%d\n", succ->block_id()));
147       copy->Print();
148     }
149     return copy;
150   }
151 
152   // Support for global analysis with HFlowEngine: Merge this state with
153   // the other incoming state.
Merge(HBasicBlock * succ,HLoadEliminationTable * that,Zone * zone)154   HLoadEliminationTable* Merge(HBasicBlock* succ,
155       HLoadEliminationTable* that, Zone* zone) {
156     if (that->fields_.length() < fields_.length()) {
157       // Drop fields not in the other table.
158       fields_.Rewind(that->fields_.length());
159     }
160     for (int i = 0; i < fields_.length(); i++) {
161       // Merge the field approximations for like fields.
162       HFieldApproximation* approx = fields_[i];
163       HFieldApproximation* prev = NULL;
164       while (approx != NULL) {
165         // TODO(titzer): Merging is O(N * M); sort?
166         HFieldApproximation* other = that->Find(approx->object_, i);
167         if (other == NULL || !Equal(approx->last_value_, other->last_value_)) {
168           // Kill an entry that doesn't agree with the other value.
169           if (prev != NULL) {
170             prev->next_ = approx->next_;
171           } else {
172             fields_[i] = approx->next_;
173           }
174           approx = approx->next_;
175           continue;
176         }
177         prev = approx;
178         approx = approx->next_;
179       }
180     }
181     return this;
182   }
183 
184   friend class HLoadEliminationEffects;  // Calls Kill() and others.
185   friend class HLoadEliminationPhase;
186 
187  private:
188   // Process a load instruction, updating internal table state. If a previous
189   // load or store for this object and field exists, return the new value with
190   // which the load should be replaced. Otherwise, return {instr}.
load(HLoadNamedField * instr)191   HValue* load(HLoadNamedField* instr) {
192     int field = FieldOf(instr->access());
193     if (field < 0) return instr;
194 
195     HValue* object = instr->object()->ActualValue();
196     HFieldApproximation* approx = FindOrCreate(object, field);
197 
198     if (approx->last_value_ == NULL) {
199       // Load is not redundant. Fill out a new entry.
200       approx->last_load_ = instr;
201       approx->last_value_ = instr;
202       return instr;
203     } else {
204       // Eliminate the load. Reuse previously stored value or load instruction.
205       return approx->last_value_;
206     }
207   }
208 
209   // Process a store instruction, updating internal table state. If a previous
210   // store to the same object and field makes this store redundant (e.g. because
211   // the stored values are the same), return NULL indicating that this store
212   // instruction is redundant. Otherwise, return {instr}.
store(HStoreNamedField * instr)213   HValue* store(HStoreNamedField* instr) {
214     int field = FieldOf(instr->access());
215     if (field < 0) return KillIfMisaligned(instr);
216 
217     HValue* object = instr->object()->ActualValue();
218     HValue* value = instr->value();
219 
220     // Kill non-equivalent may-alias entries.
221     KillFieldInternal(object, field, value);
222     if (instr->has_transition()) {
223       // A transition store alters the map of the object.
224       // TODO(titzer): remember the new map (a constant) for the object.
225       KillFieldInternal(object, FieldOf(JSObject::kMapOffset), NULL);
226     }
227     HFieldApproximation* approx = FindOrCreate(object, field);
228 
229     if (Equal(approx->last_value_, value)) {
230       // The store is redundant because the field already has this value.
231       return NULL;
232     } else {
233       // The store is not redundant. Update the entry.
234       approx->last_load_ = NULL;
235       approx->last_value_ = value;
236       return instr;
237     }
238   }
239 
240   // Kill everything in this table.
Kill()241   void Kill() {
242     fields_.Rewind(0);
243   }
244 
245   // Kill all entries matching the given offset.
KillOffset(int offset)246   void KillOffset(int offset) {
247     int field = FieldOf(offset);
248     if (field >= 0 && field < fields_.length()) {
249       fields_[field] = NULL;
250     }
251   }
252 
253   // Kill all entries aliasing the given store.
KillStore(HStoreNamedField * s)254   void KillStore(HStoreNamedField* s) {
255     int field = FieldOf(s->access());
256     if (field >= 0) {
257       KillFieldInternal(s->object()->ActualValue(), field, s->value());
258     } else {
259       KillIfMisaligned(s);
260     }
261   }
262 
263   // Kill multiple entries in the case of a misaligned store.
KillIfMisaligned(HStoreNamedField * instr)264   HValue* KillIfMisaligned(HStoreNamedField* instr) {
265     HObjectAccess access = instr->access();
266     if (access.IsInobject()) {
267       int offset = access.offset();
268       if ((offset % kPointerSize) != 0) {
269         // Kill the field containing the first word of the access.
270         HValue* object = instr->object()->ActualValue();
271         int field = offset / kPointerSize;
272         KillFieldInternal(object, field, NULL);
273 
274         // Kill the next field in case of overlap.
275         int size = access.representation().size();
276         int next_field = (offset + size - 1) / kPointerSize;
277         if (next_field != field) KillFieldInternal(object, next_field, NULL);
278       }
279     }
280     return instr;
281   }
282 
283   // Find an entry for the given object and field pair.
Find(HValue * object,int field)284   HFieldApproximation* Find(HValue* object, int field) {
285     // Search for a field approximation for this object.
286     HFieldApproximation* approx = fields_[field];
287     while (approx != NULL) {
288       if (aliasing_->MustAlias(object, approx->object_)) return approx;
289       approx = approx->next_;
290     }
291     return NULL;
292   }
293 
294   // Find or create an entry for the given object and field pair.
FindOrCreate(HValue * object,int field)295   HFieldApproximation* FindOrCreate(HValue* object, int field) {
296     EnsureFields(field + 1);
297 
298     // Search for a field approximation for this object.
299     HFieldApproximation* approx = fields_[field];
300     int count = 0;
301     while (approx != NULL) {
302       if (aliasing_->MustAlias(object, approx->object_)) return approx;
303       count++;
304       approx = approx->next_;
305     }
306 
307     if (count >= kMaxTrackedObjects) {
308       // Pull the last entry off the end and repurpose it for this object.
309       approx = ReuseLastApproximation(field);
310     } else {
311       // Allocate a new entry.
312       approx = new(zone_) HFieldApproximation();
313     }
314 
315     // Insert the entry at the head of the list.
316     approx->object_ = object;
317     approx->last_load_ = NULL;
318     approx->last_value_ = NULL;
319     approx->next_ = fields_[field];
320     fields_[field] = approx;
321 
322     return approx;
323   }
324 
325   // Kill all entries for a given field that _may_ alias the given object
326   // and do _not_ have the given value.
KillFieldInternal(HValue * object,int field,HValue * value)327   void KillFieldInternal(HValue* object, int field, HValue* value) {
328     if (field >= fields_.length()) return;  // Nothing to do.
329 
330     HFieldApproximation* approx = fields_[field];
331     HFieldApproximation* prev = NULL;
332     while (approx != NULL) {
333       if (aliasing_->MayAlias(object, approx->object_)) {
334         if (!Equal(approx->last_value_, value)) {
335           // Kill an aliasing entry that doesn't agree on the value.
336           if (prev != NULL) {
337             prev->next_ = approx->next_;
338           } else {
339             fields_[field] = approx->next_;
340           }
341           approx = approx->next_;
342           continue;
343         }
344       }
345       prev = approx;
346       approx = approx->next_;
347     }
348   }
349 
Equal(HValue * a,HValue * b)350   bool Equal(HValue* a, HValue* b) {
351     if (a == b) return true;
352     if (a != NULL && b != NULL && a->CheckFlag(HValue::kUseGVN)) {
353       return a->Equals(b);
354     }
355     return false;
356   }
357 
358   // Remove the last approximation for a field so that it can be reused.
359   // We reuse the last entry because it was the first inserted and is thus
360   // farthest away from the current instruction.
ReuseLastApproximation(int field)361   HFieldApproximation* ReuseLastApproximation(int field) {
362     HFieldApproximation* approx = fields_[field];
363     ASSERT(approx != NULL);
364 
365     HFieldApproximation* prev = NULL;
366     while (approx->next_ != NULL) {
367       prev = approx;
368       approx = approx->next_;
369     }
370     if (prev != NULL) prev->next_ = NULL;
371     return approx;
372   }
373 
374   // Compute the field index for the given object access; -1 if not tracked.
FieldOf(HObjectAccess access)375   int FieldOf(HObjectAccess access) {
376     return access.IsInobject() ? FieldOf(access.offset()) : -1;
377   }
378 
379   // Compute the field index for the given in-object offset; -1 if not tracked.
FieldOf(int offset)380   int FieldOf(int offset) {
381     if (offset >= kMaxTrackedFields * kPointerSize) return -1;
382     // TODO(titzer): track misaligned loads in a separate list?
383     if ((offset % kPointerSize) != 0) return -1;  // Ignore misaligned accesses.
384     return offset / kPointerSize;
385   }
386 
387   // Ensure internal storage for the given number of fields.
EnsureFields(int num_fields)388   void EnsureFields(int num_fields) {
389     if (fields_.length() < num_fields) {
390       fields_.AddBlock(NULL, num_fields - fields_.length(), zone_);
391     }
392   }
393 
394   // Print this table to stdout.
Print()395   void Print() {
396     for (int i = 0; i < fields_.length(); i++) {
397       PrintF("  field %d: ", i);
398       for (HFieldApproximation* a = fields_[i]; a != NULL; a = a->next_) {
399         PrintF("[o%d =", a->object_->id());
400         if (a->last_load_ != NULL) PrintF(" L%d", a->last_load_->id());
401         if (a->last_value_ != NULL) PrintF(" v%d", a->last_value_->id());
402         PrintF("] ");
403       }
404       PrintF("\n");
405     }
406   }
407 
408   Zone* zone_;
409   ZoneList<HFieldApproximation*> fields_;
410   HAliasAnalyzer* aliasing_;
411 };
412 
413 
414 // Support for HFlowEngine: collect store effects within loops.
415 class HLoadEliminationEffects : public ZoneObject {
416  public:
HLoadEliminationEffects(Zone * zone)417   explicit HLoadEliminationEffects(Zone* zone)
418     : zone_(zone),
419       maps_stored_(false),
420       fields_stored_(false),
421       elements_stored_(false),
422       stores_(5, zone) { }
423 
Disabled()424   inline bool Disabled() {
425     return false;  // Effects are _not_ disabled.
426   }
427 
428   // Process a possibly side-effecting instruction.
Process(HInstruction * instr,Zone * zone)429   void Process(HInstruction* instr, Zone* zone) {
430     switch (instr->opcode()) {
431       case HValue::kStoreNamedField: {
432         stores_.Add(HStoreNamedField::cast(instr), zone_);
433         break;
434       }
435       case HValue::kOsrEntry: {
436         // Kill everything. Loads must not be hoisted past the OSR entry.
437         maps_stored_ = true;
438         fields_stored_ = true;
439         elements_stored_ = true;
440       }
441       default: {
442         fields_stored_ |= instr->CheckGVNFlag(kChangesInobjectFields);
443         maps_stored_ |= instr->CheckGVNFlag(kChangesMaps);
444         maps_stored_ |= instr->CheckGVNFlag(kChangesElementsKind);
445         elements_stored_ |= instr->CheckGVNFlag(kChangesElementsKind);
446         elements_stored_ |= instr->CheckGVNFlag(kChangesElementsPointer);
447       }
448     }
449   }
450 
451   // Apply these effects to the given load elimination table.
Apply(HLoadEliminationTable * table)452   void Apply(HLoadEliminationTable* table) {
453     if (fields_stored_) {
454       table->Kill();
455       return;
456     }
457     if (maps_stored_) {
458       table->KillOffset(JSObject::kMapOffset);
459     }
460     if (elements_stored_) {
461       table->KillOffset(JSObject::kElementsOffset);
462     }
463 
464     // Kill non-agreeing fields for each store contained in these effects.
465     for (int i = 0; i < stores_.length(); i++) {
466       table->KillStore(stores_[i]);
467     }
468   }
469 
470   // Union these effects with the other effects.
Union(HLoadEliminationEffects * that,Zone * zone)471   void Union(HLoadEliminationEffects* that, Zone* zone) {
472     maps_stored_ |= that->maps_stored_;
473     fields_stored_ |= that->fields_stored_;
474     elements_stored_ |= that->elements_stored_;
475     for (int i = 0; i < that->stores_.length(); i++) {
476       stores_.Add(that->stores_[i], zone);
477     }
478   }
479 
480  private:
481   Zone* zone_;
482   bool maps_stored_ : 1;
483   bool fields_stored_ : 1;
484   bool elements_stored_ : 1;
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 v8::internal
511