Home | History | Annotate | Download | only in src
      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-check-elimination.h"
     29 #include "hydrogen-alias-analysis.h"
     30 #include "hydrogen-flow-engine.h"
     31 
     32 #define GLOBAL 1
     33 
     34 // Only collect stats in debug mode.
     35 #if DEBUG
     36 #define INC_STAT(x) phase_->x++
     37 #else
     38 #define INC_STAT(x)
     39 #endif
     40 
     41 // For code de-uglification.
     42 #define TRACE(x) if (FLAG_trace_check_elimination) PrintF x
     43 
     44 namespace v8 {
     45 namespace internal {
     46 
     47 typedef UniqueSet<Map>* MapSet;
     48 
     49 struct HCheckTableEntry {
     50   HValue* object_;  // The object being approximated. NULL => invalid entry.
     51   HValue* check_;   // The last check instruction.
     52   MapSet maps_;     // The set of known maps for the object.
     53 };
     54 
     55 
     56 // The main datastructure used during check elimination, which stores a
     57 // set of known maps for each object.
     58 class HCheckTable : public ZoneObject {
     59  public:
     60   static const int kMaxTrackedObjects = 10;
     61 
     62   explicit HCheckTable(HCheckEliminationPhase* phase)
     63     : phase_(phase),
     64       cursor_(0),
     65       size_(0) {
     66   }
     67 
     68   // The main processing of instructions.
     69   HCheckTable* Process(HInstruction* instr, Zone* zone) {
     70     switch (instr->opcode()) {
     71       case HValue::kCheckMaps: {
     72         ReduceCheckMaps(HCheckMaps::cast(instr));
     73         break;
     74       }
     75       case HValue::kCheckValue: {
     76         ReduceCheckValue(HCheckValue::cast(instr));
     77         break;
     78       }
     79       case HValue::kLoadNamedField: {
     80         ReduceLoadNamedField(HLoadNamedField::cast(instr));
     81         break;
     82       }
     83       case HValue::kStoreNamedField: {
     84         ReduceStoreNamedField(HStoreNamedField::cast(instr));
     85         break;
     86       }
     87       case HValue::kCompareMap: {
     88         ReduceCompareMap(HCompareMap::cast(instr));
     89         break;
     90       }
     91       case HValue::kTransitionElementsKind: {
     92         ReduceTransitionElementsKind(
     93             HTransitionElementsKind::cast(instr));
     94         break;
     95       }
     96       case HValue::kCheckMapValue: {
     97         ReduceCheckMapValue(HCheckMapValue::cast(instr));
     98         break;
     99       }
    100       case HValue::kCheckHeapObject: {
    101         ReduceCheckHeapObject(HCheckHeapObject::cast(instr));
    102         break;
    103       }
    104       default: {
    105         // If the instruction changes maps uncontrollably, drop everything.
    106         if (instr->CheckGVNFlag(kChangesMaps) ||
    107             instr->CheckGVNFlag(kChangesOsrEntries)) {
    108           Kill();
    109         }
    110       }
    111       // Improvements possible:
    112       // - eliminate redundant HCheckSmi, HCheckInstanceType instructions
    113       // - track which values have been HCheckHeapObject'd
    114     }
    115 
    116     return this;
    117   }
    118 
    119   // Global analysis: Copy state to successor block.
    120   HCheckTable* Copy(HBasicBlock* succ, Zone* zone) {
    121     HCheckTable* copy = new(phase_->zone()) HCheckTable(phase_);
    122     for (int i = 0; i < size_; i++) {
    123       HCheckTableEntry* old_entry = &entries_[i];
    124       HCheckTableEntry* new_entry = &copy->entries_[i];
    125       // TODO(titzer): keep the check if this block dominates the successor?
    126       new_entry->object_ = old_entry->object_;
    127       new_entry->check_ = NULL;
    128       new_entry->maps_ = old_entry->maps_->Copy(phase_->zone());
    129     }
    130     if (succ->predecessors()->length() == 1) {
    131       HControlInstruction* end = succ->predecessors()->at(0)->end();
    132       if (end->IsCompareMap() && end->SuccessorAt(0) == succ) {
    133         // Learn on the true branch of if(CompareMap(x)).
    134         HCompareMap* cmp = HCompareMap::cast(end);
    135         HValue* object = cmp->value()->ActualValue();
    136         HCheckTableEntry* entry = copy->Find(object);
    137         if (entry == NULL) {
    138           copy->Insert(object, cmp->map());
    139         } else {
    140           MapSet list = new(phase_->zone()) UniqueSet<Map>();
    141           list->Add(cmp->map(), phase_->zone());
    142           entry->maps_ = list;
    143         }
    144       }
    145       // TODO(titzer): is it worthwhile to learn on false branch too?
    146     }
    147     return copy;
    148   }
    149 
    150   // Global analysis: Merge this state with the other incoming state.
    151   HCheckTable* Merge(HBasicBlock* succ, HCheckTable* that, Zone* zone) {
    152     if (that->size_ == 0) {
    153       // If the other state is empty, simply reset.
    154       size_ = 0;
    155       cursor_ = 0;
    156       return this;
    157     }
    158     bool compact = false;
    159     for (int i = 0; i < size_; i++) {
    160       HCheckTableEntry* this_entry = &entries_[i];
    161       HCheckTableEntry* that_entry = that->Find(this_entry->object_);
    162       if (that_entry == NULL) {
    163         this_entry->object_ = NULL;
    164         compact = true;
    165       } else {
    166         this_entry->maps_ = this_entry->maps_->Union(
    167             that_entry->maps_, phase_->zone());
    168         if (this_entry->check_ != that_entry->check_) this_entry->check_ = NULL;
    169         ASSERT(this_entry->maps_->size() > 0);
    170       }
    171     }
    172     if (compact) Compact();
    173     return this;
    174   }
    175 
    176   void ReduceCheckMaps(HCheckMaps* instr) {
    177     HValue* object = instr->value()->ActualValue();
    178     HCheckTableEntry* entry = Find(object);
    179     if (entry != NULL) {
    180       // entry found;
    181       MapSet a = entry->maps_;
    182       MapSet i = instr->map_set().Copy(phase_->zone());
    183       if (a->IsSubset(i)) {
    184         // The first check is more strict; the second is redundant.
    185         if (entry->check_ != NULL) {
    186           instr->DeleteAndReplaceWith(entry->check_);
    187           INC_STAT(redundant_);
    188         } else {
    189           instr->DeleteAndReplaceWith(instr->value());
    190           INC_STAT(removed_);
    191         }
    192         return;
    193       }
    194       i = i->Intersect(a, phase_->zone());
    195       if (i->size() == 0) {
    196         // Intersection is empty; probably megamorphic, which is likely to
    197         // deopt anyway, so just leave things as they are.
    198         INC_STAT(empty_);
    199       } else {
    200         // TODO(titzer): replace the first check with a more strict check
    201         INC_STAT(narrowed_);
    202       }
    203     } else {
    204       // No entry; insert a new one.
    205       Insert(object, instr, instr->map_set().Copy(phase_->zone()));
    206     }
    207   }
    208 
    209   void ReduceCheckValue(HCheckValue* instr) {
    210     // Canonicalize HCheckValues; they might have their values load-eliminated.
    211     HValue* value = instr->Canonicalize();
    212     if (value == NULL) {
    213       instr->DeleteAndReplaceWith(instr->value());
    214       INC_STAT(removed_);
    215     } else if (value != instr) {
    216       instr->DeleteAndReplaceWith(value);
    217       INC_STAT(redundant_);
    218     }
    219   }
    220 
    221   void ReduceLoadNamedField(HLoadNamedField* instr) {
    222     // Reduce a load of the map field when it is known to be a constant.
    223     if (!IsMapAccess(instr->access())) return;
    224 
    225     HValue* object = instr->object()->ActualValue();
    226     MapSet maps = FindMaps(object);
    227     if (maps == NULL || maps->size() != 1) return;  // Not a constant.
    228 
    229     Unique<Map> map = maps->at(0);
    230     HConstant* constant = HConstant::CreateAndInsertBefore(
    231         instr->block()->graph()->zone(), map, true, instr);
    232     instr->DeleteAndReplaceWith(constant);
    233     INC_STAT(loads_);
    234   }
    235 
    236   void ReduceCheckMapValue(HCheckMapValue* instr) {
    237     if (!instr->map()->IsConstant()) return;  // Nothing to learn.
    238 
    239     HValue* object = instr->value()->ActualValue();
    240     // Match a HCheckMapValue(object, HConstant(map))
    241     Unique<Map> map = MapConstant(instr->map());
    242     MapSet maps = FindMaps(object);
    243     if (maps != NULL) {
    244       if (maps->Contains(map)) {
    245         if (maps->size() == 1) {
    246           // Object is known to have exactly this map.
    247           instr->DeleteAndReplaceWith(NULL);
    248           INC_STAT(removed_);
    249         } else {
    250           // Only one map survives the check.
    251           maps->Clear();
    252           maps->Add(map, phase_->zone());
    253         }
    254       }
    255     } else {
    256       // No prior information.
    257       Insert(object, map);
    258     }
    259   }
    260 
    261   void ReduceCheckHeapObject(HCheckHeapObject* instr) {
    262     if (FindMaps(instr->value()->ActualValue()) != NULL) {
    263       // If the object has known maps, it's definitely a heap object.
    264       instr->DeleteAndReplaceWith(instr->value());
    265       INC_STAT(removed_cho_);
    266     }
    267   }
    268 
    269   void ReduceStoreNamedField(HStoreNamedField* instr) {
    270     HValue* object = instr->object()->ActualValue();
    271     if (instr->has_transition()) {
    272       // This store transitions the object to a new map.
    273       Kill(object);
    274       Insert(object, MapConstant(instr->transition()));
    275     } else if (IsMapAccess(instr->access())) {
    276       // This is a store directly to the map field of the object.
    277       Kill(object);
    278       if (!instr->value()->IsConstant()) return;
    279       Insert(object, MapConstant(instr->value()));
    280     } else {
    281       // If the instruction changes maps, it should be handled above.
    282       CHECK(!instr->CheckGVNFlag(kChangesMaps));
    283     }
    284   }
    285 
    286   void ReduceCompareMap(HCompareMap* instr) {
    287     MapSet maps = FindMaps(instr->value()->ActualValue());
    288     if (maps == NULL) return;
    289     if (maps->Contains(instr->map())) {
    290       if (maps->size() == 1) {
    291         // TODO(titzer): replace with goto true branch
    292         INC_STAT(compares_true_);
    293       }
    294     } else {
    295       // TODO(titzer): replace with goto false branch
    296       INC_STAT(compares_false_);
    297     }
    298   }
    299 
    300   void ReduceTransitionElementsKind(HTransitionElementsKind* instr) {
    301     MapSet maps = FindMaps(instr->object()->ActualValue());
    302     // Can only learn more about an object that already has a known set of maps.
    303     if (maps == NULL) return;
    304     if (maps->Contains(instr->original_map())) {
    305       // If the object has the original map, it will be transitioned.
    306       maps->Remove(instr->original_map());
    307       maps->Add(instr->transitioned_map(), phase_->zone());
    308     } else {
    309       // Object does not have the given map, thus the transition is redundant.
    310       instr->DeleteAndReplaceWith(instr->object());
    311       INC_STAT(transitions_);
    312     }
    313   }
    314 
    315   // Kill everything in the table.
    316   void Kill() {
    317     size_ = 0;
    318     cursor_ = 0;
    319   }
    320 
    321   // Kill everything in the table that may alias {object}.
    322   void Kill(HValue* object) {
    323     bool compact = false;
    324     for (int i = 0; i < size_; i++) {
    325       HCheckTableEntry* entry = &entries_[i];
    326       ASSERT(entry->object_ != NULL);
    327       if (phase_->aliasing_->MayAlias(entry->object_, object)) {
    328         entry->object_ = NULL;
    329         compact = true;
    330       }
    331     }
    332     if (compact) Compact();
    333     ASSERT(Find(object) == NULL);
    334   }
    335 
    336   void Compact() {
    337     // First, compact the array in place.
    338     int max = size_, dest = 0, old_cursor = cursor_;
    339     for (int i = 0; i < max; i++) {
    340       if (entries_[i].object_ != NULL) {
    341         if (dest != i) entries_[dest] = entries_[i];
    342         dest++;
    343       } else {
    344         if (i < old_cursor) cursor_--;
    345         size_--;
    346       }
    347     }
    348     ASSERT(size_ == dest);
    349     ASSERT(cursor_ <= size_);
    350 
    351     // Preserve the age of the entries by moving the older entries to the end.
    352     if (cursor_ == size_) return;  // Cursor already points at end.
    353     if (cursor_ != 0) {
    354       // | L = oldest |   R = newest   |       |
    355       //              ^ cursor         ^ size  ^ MAX
    356       HCheckTableEntry tmp_entries[kMaxTrackedObjects];
    357       int L = cursor_;
    358       int R = size_ - cursor_;
    359 
    360       OS::MemMove(&tmp_entries[0], &entries_[0], L * sizeof(HCheckTableEntry));
    361       OS::MemMove(&entries_[0], &entries_[L], R * sizeof(HCheckTableEntry));
    362       OS::MemMove(&entries_[R], &tmp_entries[0], L * sizeof(HCheckTableEntry));
    363     }
    364 
    365     cursor_ = size_;  // Move cursor to end.
    366   }
    367 
    368   void Print() {
    369     for (int i = 0; i < size_; i++) {
    370       HCheckTableEntry* entry = &entries_[i];
    371       ASSERT(entry->object_ != NULL);
    372       PrintF("  checkmaps-table @%d: object #%d ", i, entry->object_->id());
    373       if (entry->check_ != NULL) {
    374         PrintF("check #%d ", entry->check_->id());
    375       }
    376       MapSet list = entry->maps_;
    377       PrintF("%d maps { ", list->size());
    378       for (int j = 0; j < list->size(); j++) {
    379         if (j > 0) PrintF(", ");
    380         PrintF("%" V8PRIxPTR, list->at(j).Hashcode());
    381       }
    382       PrintF(" }\n");
    383     }
    384   }
    385 
    386  private:
    387   HCheckTableEntry* Find(HValue* object) {
    388     for (int i = size_ - 1; i >= 0; i--) {
    389       // Search from most-recently-inserted to least-recently-inserted.
    390       HCheckTableEntry* entry = &entries_[i];
    391       ASSERT(entry->object_ != NULL);
    392       if (phase_->aliasing_->MustAlias(entry->object_, object)) return entry;
    393     }
    394     return NULL;
    395   }
    396 
    397   MapSet FindMaps(HValue* object) {
    398     HCheckTableEntry* entry = Find(object);
    399     return entry == NULL ? NULL : entry->maps_;
    400   }
    401 
    402   void Insert(HValue* object, Unique<Map> map) {
    403     MapSet list = new(phase_->zone()) UniqueSet<Map>();
    404     list->Add(map, phase_->zone());
    405     Insert(object, NULL, list);
    406   }
    407 
    408   void Insert(HValue* object, HCheckMaps* check, MapSet maps) {
    409     HCheckTableEntry* entry = &entries_[cursor_++];
    410     entry->object_ = object;
    411     entry->check_ = check;
    412     entry->maps_ = maps;
    413     // If the table becomes full, wrap around and overwrite older entries.
    414     if (cursor_ == kMaxTrackedObjects) cursor_ = 0;
    415     if (size_ < kMaxTrackedObjects) size_++;
    416   }
    417 
    418   bool IsMapAccess(HObjectAccess access) {
    419     return access.IsInobject() && access.offset() == JSObject::kMapOffset;
    420   }
    421 
    422   Unique<Map> MapConstant(HValue* value) {
    423     return Unique<Map>::cast(HConstant::cast(value)->GetUnique());
    424   }
    425 
    426   friend class HCheckMapsEffects;
    427 
    428   HCheckEliminationPhase* phase_;
    429   HCheckTableEntry entries_[kMaxTrackedObjects];
    430   int16_t cursor_;  // Must be <= kMaxTrackedObjects
    431   int16_t size_;    // Must be <= kMaxTrackedObjects
    432   // TODO(titzer): STATIC_ASSERT kMaxTrackedObjects < max(cursor_)
    433 };
    434 
    435 
    436 // Collects instructions that can cause effects that invalidate information
    437 // needed for check elimination.
    438 class HCheckMapsEffects : public ZoneObject {
    439  public:
    440   explicit HCheckMapsEffects(Zone* zone)
    441     : maps_stored_(false),
    442       stores_(5, zone) { }
    443 
    444   inline bool Disabled() {
    445     return false;  // Effects are _not_ disabled.
    446   }
    447 
    448   // Process a possibly side-effecting instruction.
    449   void Process(HInstruction* instr, Zone* zone) {
    450     switch (instr->opcode()) {
    451       case HValue::kStoreNamedField: {
    452         stores_.Add(HStoreNamedField::cast(instr), zone);
    453         break;
    454       }
    455       case HValue::kOsrEntry: {
    456         // Kill everything. Loads must not be hoisted past the OSR entry.
    457         maps_stored_ = true;
    458       }
    459       default: {
    460         maps_stored_ |= (instr->CheckGVNFlag(kChangesMaps) |
    461                          instr->CheckGVNFlag(kChangesElementsKind));
    462       }
    463     }
    464   }
    465 
    466   // Apply these effects to the given check elimination table.
    467   void Apply(HCheckTable* table) {
    468     if (maps_stored_) {
    469       // Uncontrollable map modifications; kill everything.
    470       table->Kill();
    471       return;
    472     }
    473 
    474     // Kill maps for each store contained in these effects.
    475     for (int i = 0; i < stores_.length(); i++) {
    476       HStoreNamedField* s = stores_[i];
    477       if (table->IsMapAccess(s->access()) || s->has_transition()) {
    478         table->Kill(s->object()->ActualValue());
    479       }
    480     }
    481   }
    482 
    483   // Union these effects with the other effects.
    484   void Union(HCheckMapsEffects* that, Zone* zone) {
    485     maps_stored_ |= that->maps_stored_;
    486     for (int i = 0; i < that->stores_.length(); i++) {
    487       stores_.Add(that->stores_[i], zone);
    488     }
    489   }
    490 
    491  private:
    492   bool maps_stored_ : 1;
    493   ZoneList<HStoreNamedField*> stores_;
    494 };
    495 
    496 
    497 // The main routine of the analysis phase. Use the HFlowEngine for either a
    498 // local or a global analysis.
    499 void HCheckEliminationPhase::Run() {
    500   HFlowEngine<HCheckTable, HCheckMapsEffects> engine(graph(), zone());
    501   HCheckTable* table = new(zone()) HCheckTable(this);
    502 
    503   if (GLOBAL) {
    504     // Perform a global analysis.
    505     engine.AnalyzeDominatedBlocks(graph()->blocks()->at(0), table);
    506   } else {
    507     // Perform only local analysis.
    508     for (int i = 0; i < graph()->blocks()->length(); i++) {
    509       table->Kill();
    510       engine.AnalyzeOneBlock(graph()->blocks()->at(i), table);
    511     }
    512   }
    513 
    514   if (FLAG_trace_check_elimination) PrintStats();
    515 }
    516 
    517 
    518 // Are we eliminated yet?
    519 void HCheckEliminationPhase::PrintStats() {
    520 #if DEBUG
    521   #define PRINT_STAT(x) if (x##_ > 0) PrintF(" %-16s = %2d\n", #x, x##_)
    522 #else
    523   #define PRINT_STAT(x)
    524 #endif
    525   PRINT_STAT(redundant);
    526   PRINT_STAT(removed);
    527   PRINT_STAT(removed_cho);
    528   PRINT_STAT(narrowed);
    529   PRINT_STAT(loads);
    530   PRINT_STAT(empty);
    531   PRINT_STAT(compares_true);
    532   PRINT_STAT(compares_false);
    533   PRINT_STAT(transitions);
    534 }
    535 
    536 } }  // namespace v8::internal
    537