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       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.
    264   void Kill() {
    265     fields_.Rewind(0);
    266   }
    267 
    268   // Kill all entries matching the given 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.
    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.
    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.
    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.
    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.
    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 
    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.
    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.
    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.
    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.
    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.
    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:
    437   explicit HLoadEliminationEffects(Zone* zone)
    438     : zone_(zone), stores_(5, zone) { }
    439 
    440   inline bool Disabled() {
    441     return false;  // Effects are _not_ disabled.
    442   }
    443 
    444   // Process a possibly side-effecting instruction.
    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.
    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.
    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.
    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