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