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