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 = ©->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