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
11namespace v8 {
12namespace internal {
13
14#define GLOBAL true
15#define TRACE(x) if (FLAG_trace_load_elimination) PrintF x
16
17static const int kMaxTrackedFields = 16;
18static const int kMaxTrackedObjects = 5;
19
20// An element in the field approximation list.
21class 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.
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.
41class HLoadEliminationTable : public ZoneObject {
42 public:
43  HLoadEliminationTable(Zone* zone, HAliasAnalyzer* aliasing)
44    : zone_(zone), fields_(kMaxTrackedFields, zone), aliasing_(aliasing) { }
45
46  // The main processing of instructions.
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.
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.
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.
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.
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}.
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}.
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      // TODO(titzer): introduce a constant for the new map and remember it.
247      KillFieldInternal(object, FieldOf(JSObject::kMapOffset), NULL);
248    } else {
249      // Kill non-equivalent may-alias entries.
250      KillFieldInternal(object, field, value);
251    }
252    HFieldApproximation* approx = FindOrCreate(object, field);
253
254    if (Equal(approx->last_value_, value)) {
255      // The store is redundant because the field already has this value.
256      return NULL;
257    } else {
258      // The store is not redundant. Update the entry.
259      approx->last_value_ = value;
260      return instr;
261    }
262  }
263
264  // Kill everything in this table.
265  void Kill() {
266    fields_.Rewind(0);
267  }
268
269  // Kill all entries matching the given offset.
270  void KillOffset(int offset) {
271    int field = FieldOf(offset);
272    if (field >= 0 && field < fields_.length()) {
273      fields_[field] = NULL;
274    }
275  }
276
277  // Kill all entries aliasing the given store.
278  void KillStore(HStoreNamedField* s) {
279    int field = FieldOf(s->access());
280    if (field >= 0) {
281      KillFieldInternal(s->object()->ActualValue(), field, s->value());
282    } else {
283      KillIfMisaligned(s);
284    }
285  }
286
287  // Kill multiple entries in the case of a misaligned store.
288  HValue* KillIfMisaligned(HStoreNamedField* instr) {
289    HObjectAccess access = instr->access();
290    if (access.IsInobject()) {
291      int offset = access.offset();
292      if ((offset % kPointerSize) != 0) {
293        // Kill the field containing the first word of the access.
294        HValue* object = instr->object()->ActualValue();
295        int field = offset / kPointerSize;
296        KillFieldInternal(object, field, NULL);
297
298        // Kill the next field in case of overlap.
299        int size = access.representation().size();
300        int next_field = (offset + size - 1) / kPointerSize;
301        if (next_field != field) KillFieldInternal(object, next_field, NULL);
302      }
303    }
304    return instr;
305  }
306
307  // Find an entry for the given object and field pair.
308  HFieldApproximation* Find(HValue* object, int field) {
309    // Search for a field approximation for this object.
310    HFieldApproximation* approx = fields_[field];
311    while (approx != NULL) {
312      if (aliasing_->MustAlias(object, approx->object_)) return approx;
313      approx = approx->next_;
314    }
315    return NULL;
316  }
317
318  // Find or create an entry for the given object and field pair.
319  HFieldApproximation* FindOrCreate(HValue* object, int field) {
320    EnsureFields(field + 1);
321
322    // Search for a field approximation for this object.
323    HFieldApproximation* approx = fields_[field];
324    int count = 0;
325    while (approx != NULL) {
326      if (aliasing_->MustAlias(object, approx->object_)) return approx;
327      count++;
328      approx = approx->next_;
329    }
330
331    if (count >= kMaxTrackedObjects) {
332      // Pull the last entry off the end and repurpose it for this object.
333      approx = ReuseLastApproximation(field);
334    } else {
335      // Allocate a new entry.
336      approx = new(zone_) HFieldApproximation();
337    }
338
339    // Insert the entry at the head of the list.
340    approx->object_ = object;
341    approx->last_value_ = NULL;
342    approx->next_ = fields_[field];
343    fields_[field] = approx;
344
345    return approx;
346  }
347
348  // Kill all entries for a given field that _may_ alias the given object
349  // and do _not_ have the given value.
350  void KillFieldInternal(HValue* object, int field, HValue* value) {
351    if (field >= fields_.length()) return;  // Nothing to do.
352
353    HFieldApproximation* approx = fields_[field];
354    HFieldApproximation* prev = NULL;
355    while (approx != NULL) {
356      if (aliasing_->MayAlias(object, approx->object_)) {
357        if (!Equal(approx->last_value_, value)) {
358          // Kill an aliasing entry that doesn't agree on the value.
359          if (prev != NULL) {
360            prev->next_ = approx->next_;
361          } else {
362            fields_[field] = approx->next_;
363          }
364          approx = approx->next_;
365          continue;
366        }
367      }
368      prev = approx;
369      approx = approx->next_;
370    }
371  }
372
373  bool Equal(HValue* a, HValue* b) {
374    if (a == b) return true;
375    if (a != NULL && b != NULL && a->CheckFlag(HValue::kUseGVN)) {
376      return a->Equals(b);
377    }
378    return false;
379  }
380
381  // Remove the last approximation for a field so that it can be reused.
382  // We reuse the last entry because it was the first inserted and is thus
383  // farthest away from the current instruction.
384  HFieldApproximation* ReuseLastApproximation(int field) {
385    HFieldApproximation* approx = fields_[field];
386    DCHECK(approx != NULL);
387
388    HFieldApproximation* prev = NULL;
389    while (approx->next_ != NULL) {
390      prev = approx;
391      approx = approx->next_;
392    }
393    if (prev != NULL) prev->next_ = NULL;
394    return approx;
395  }
396
397  // Compute the field index for the given object access; -1 if not tracked.
398  int FieldOf(HObjectAccess access) {
399    return access.IsInobject() ? FieldOf(access.offset()) : -1;
400  }
401
402  // Compute the field index for the given in-object offset; -1 if not tracked.
403  int FieldOf(int offset) {
404    if (offset >= kMaxTrackedFields * kPointerSize) return -1;
405    // TODO(titzer): track misaligned loads in a separate list?
406    if ((offset % kPointerSize) != 0) return -1;  // Ignore misaligned accesses.
407    return offset / kPointerSize;
408  }
409
410  // Ensure internal storage for the given number of fields.
411  void EnsureFields(int num_fields) {
412    if (fields_.length() < num_fields) {
413      fields_.AddBlock(NULL, num_fields - fields_.length(), zone_);
414    }
415  }
416
417  // Print this table to stdout.
418  void Print() {
419    for (int i = 0; i < fields_.length(); i++) {
420      PrintF("  field %d: ", i);
421      for (HFieldApproximation* a = fields_[i]; a != NULL; a = a->next_) {
422        PrintF("[o%d =", a->object_->id());
423        if (a->last_value_ != NULL) PrintF(" v%d", a->last_value_->id());
424        PrintF("] ");
425      }
426      PrintF("\n");
427    }
428  }
429
430  Zone* zone_;
431  ZoneList<HFieldApproximation*> fields_;
432  HAliasAnalyzer* aliasing_;
433};
434
435
436// Support for HFlowEngine: collect store effects within loops.
437class HLoadEliminationEffects : public ZoneObject {
438 public:
439  explicit HLoadEliminationEffects(Zone* zone)
440    : zone_(zone), stores_(5, zone) { }
441
442  inline bool Disabled() {
443    return false;  // Effects are _not_ disabled.
444  }
445
446  // Process a possibly side-effecting instruction.
447  void Process(HInstruction* instr, Zone* zone) {
448    if (instr->IsStoreNamedField()) {
449      stores_.Add(HStoreNamedField::cast(instr), zone_);
450    } else {
451      flags_.Add(instr->ChangesFlags());
452    }
453  }
454
455  // Apply these effects to the given load elimination table.
456  void Apply(HLoadEliminationTable* table) {
457    // Loads must not be hoisted past the OSR entry, therefore we kill
458    // everything if we see an OSR entry.
459    if (flags_.Contains(kInobjectFields) || flags_.Contains(kOsrEntries)) {
460      table->Kill();
461      return;
462    }
463    if (flags_.Contains(kElementsKind) || flags_.Contains(kMaps)) {
464      table->KillOffset(JSObject::kMapOffset);
465    }
466    if (flags_.Contains(kElementsKind) || flags_.Contains(kElementsPointer)) {
467      table->KillOffset(JSObject::kElementsOffset);
468    }
469
470    // Kill non-agreeing fields for each store contained in these effects.
471    for (int i = 0; i < stores_.length(); i++) {
472      table->KillStore(stores_[i]);
473    }
474  }
475
476  // Union these effects with the other effects.
477  void Union(HLoadEliminationEffects* that, Zone* zone) {
478    flags_.Add(that->flags_);
479    for (int i = 0; i < that->stores_.length(); i++) {
480      stores_.Add(that->stores_[i], zone);
481    }
482  }
483
484 private:
485  Zone* zone_;
486  GVNFlagSet flags_;
487  ZoneList<HStoreNamedField*> stores_;
488};
489
490
491// The main routine of the analysis phase. Use the HFlowEngine for either a
492// local or a global analysis.
493void HLoadEliminationPhase::Run() {
494  HFlowEngine<HLoadEliminationTable, HLoadEliminationEffects>
495    engine(graph(), zone());
496  HAliasAnalyzer aliasing;
497  HLoadEliminationTable* table =
498      new(zone()) HLoadEliminationTable(zone(), &aliasing);
499
500  if (GLOBAL) {
501    // Perform a global analysis.
502    engine.AnalyzeDominatedBlocks(graph()->blocks()->at(0), table);
503  } else {
504    // Perform only local analysis.
505    for (int i = 0; i < graph()->blocks()->length(); i++) {
506      table->Kill();
507      engine.AnalyzeOneBlock(graph()->blocks()->at(i), table);
508    }
509  }
510}
511
512}  // namespace internal
513}  // namespace v8
514