Home | History | Annotate | Download | only in crankshaft
      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.
     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:
     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.
    437 class 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.
    493 void 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