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
33namespace v8 {
34namespace internal {
35
36#define GLOBAL true
37#define TRACE(x) if (FLAG_trace_load_elimination) PrintF x
38
39static const int kMaxTrackedFields = 16;
40static const int kMaxTrackedObjects = 5;
41
42// An element in the field approximation list.
43class 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.
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.
66class HLoadEliminationTable : public ZoneObject {
67 public:
68  HLoadEliminationTable(Zone* zone, HAliasAnalyzer* aliasing)
69    : zone_(zone), fields_(kMaxTrackedFields, zone), aliasing_(aliasing) { }
70
71  // The main processing of instructions.
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.
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.
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}.
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}.
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.
241  void Kill() {
242    fields_.Rewind(0);
243  }
244
245  // Kill all entries matching the given 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.
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.
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.
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.
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.
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
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.
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.
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.
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.
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.
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.
415class HLoadEliminationEffects : public ZoneObject {
416 public:
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
424  inline bool Disabled() {
425    return false;  // Effects are _not_ disabled.
426  }
427
428  // Process a possibly side-effecting instruction.
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.
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.
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.
491void 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