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/hydrogen-alias-analysis.h" 6 #include "src/hydrogen-flow-engine.h" 7 #include "src/hydrogen-instructions.h" 8 #include "src/hydrogen-load-elimination.h" 9 10 namespace v8 { 11 namespace internal { 12 13 #define GLOBAL true 14 #define TRACE(x) if (FLAG_trace_load_elimination) PrintF x 15 16 static const int kMaxTrackedFields = 16; 17 static const int kMaxTrackedObjects = 5; 18 19 // An element in the field approximation list. 20 class HFieldApproximation : public ZoneObject { 21 public: // Just a data blob. 22 HValue* object_; 23 HValue* last_value_; 24 HFieldApproximation* next_; 25 26 // Recursively copy the entire linked list of field approximations. 27 HFieldApproximation* Copy(Zone* zone) { 28 HFieldApproximation* copy = new(zone) HFieldApproximation(); 29 copy->object_ = this->object_; 30 copy->last_value_ = this->last_value_; 31 copy->next_ = this->next_ == NULL ? NULL : this->next_->Copy(zone); 32 return copy; 33 } 34 }; 35 36 37 // The main datastructure used during load/store elimination. Each in-object 38 // field is tracked separately. For each field, store a list of known field 39 // values for known objects. 40 class HLoadEliminationTable : public ZoneObject { 41 public: 42 HLoadEliminationTable(Zone* zone, HAliasAnalyzer* aliasing) 43 : zone_(zone), fields_(kMaxTrackedFields, zone), aliasing_(aliasing) { } 44 45 // The main processing of instructions. 46 HLoadEliminationTable* Process(HInstruction* instr, Zone* zone) { 47 switch (instr->opcode()) { 48 case HValue::kLoadNamedField: { 49 HLoadNamedField* l = HLoadNamedField::cast(instr); 50 TRACE((" process L%d field %d (o%d)\n", 51 instr->id(), 52 FieldOf(l->access()), 53 l->object()->ActualValue()->id())); 54 HValue* result = load(l); 55 if (result != instr && l->CanBeReplacedWith(result)) { 56 // The load can be replaced with a previous load or a value. 57 TRACE((" replace L%d -> v%d\n", instr->id(), result->id())); 58 instr->DeleteAndReplaceWith(result); 59 } 60 break; 61 } 62 case HValue::kStoreNamedField: { 63 HStoreNamedField* s = HStoreNamedField::cast(instr); 64 TRACE((" process S%d field %d (o%d) = v%d\n", 65 instr->id(), 66 FieldOf(s->access()), 67 s->object()->ActualValue()->id(), 68 s->value()->id())); 69 HValue* result = store(s); 70 if (result == NULL) { 71 // The store is redundant. Remove it. 72 TRACE((" remove S%d\n", instr->id())); 73 instr->DeleteAndReplaceWith(NULL); 74 } 75 break; 76 } 77 case HValue::kTransitionElementsKind: { 78 HTransitionElementsKind* t = HTransitionElementsKind::cast(instr); 79 HValue* object = t->object()->ActualValue(); 80 KillFieldInternal(object, FieldOf(JSArray::kElementsOffset), NULL); 81 KillFieldInternal(object, FieldOf(JSObject::kMapOffset), NULL); 82 break; 83 } 84 default: { 85 if (instr->CheckChangesFlag(kInobjectFields)) { 86 TRACE((" kill-all i%d\n", instr->id())); 87 Kill(); 88 break; 89 } 90 if (instr->CheckChangesFlag(kMaps)) { 91 TRACE((" kill-maps i%d\n", instr->id())); 92 KillOffset(JSObject::kMapOffset); 93 } 94 if (instr->CheckChangesFlag(kElementsKind)) { 95 TRACE((" kill-elements-kind i%d\n", instr->id())); 96 KillOffset(JSObject::kMapOffset); 97 KillOffset(JSObject::kElementsOffset); 98 } 99 if (instr->CheckChangesFlag(kElementsPointer)) { 100 TRACE((" kill-elements i%d\n", instr->id())); 101 KillOffset(JSObject::kElementsOffset); 102 } 103 if (instr->CheckChangesFlag(kOsrEntries)) { 104 TRACE((" kill-osr i%d\n", instr->id())); 105 Kill(); 106 } 107 } 108 // Improvements possible: 109 // - learn from HCheckMaps for field 0 110 // - remove unobservable stores (write-after-write) 111 // - track cells 112 // - track globals 113 // - track roots 114 } 115 return this; 116 } 117 118 // Support for global analysis with HFlowEngine: Merge given state with 119 // the other incoming state. 120 static HLoadEliminationTable* Merge(HLoadEliminationTable* succ_state, 121 HBasicBlock* succ_block, 122 HLoadEliminationTable* pred_state, 123 HBasicBlock* pred_block, 124 Zone* zone) { 125 DCHECK(pred_state != NULL); 126 if (succ_state == NULL) { 127 return pred_state->Copy(succ_block, pred_block, zone); 128 } else { 129 return succ_state->Merge(succ_block, pred_state, pred_block, zone); 130 } 131 } 132 133 // Support for global analysis with HFlowEngine: Given state merged with all 134 // the other incoming states, prepare it for use. 135 static HLoadEliminationTable* Finish(HLoadEliminationTable* state, 136 HBasicBlock* block, 137 Zone* zone) { 138 DCHECK(state != NULL); 139 return state; 140 } 141 142 private: 143 // Copy state to successor block. 144 HLoadEliminationTable* Copy(HBasicBlock* succ, HBasicBlock* from_block, 145 Zone* zone) { 146 HLoadEliminationTable* copy = 147 new(zone) HLoadEliminationTable(zone, aliasing_); 148 copy->EnsureFields(fields_.length()); 149 for (int i = 0; i < fields_.length(); i++) { 150 copy->fields_[i] = fields_[i] == NULL ? NULL : fields_[i]->Copy(zone); 151 } 152 if (FLAG_trace_load_elimination) { 153 TRACE((" copy-to B%d\n", succ->block_id())); 154 copy->Print(); 155 } 156 return copy; 157 } 158 159 // Merge this state with the other incoming state. 160 HLoadEliminationTable* Merge(HBasicBlock* succ, HLoadEliminationTable* that, 161 HBasicBlock* that_block, Zone* zone) { 162 if (that->fields_.length() < fields_.length()) { 163 // Drop fields not in the other table. 164 fields_.Rewind(that->fields_.length()); 165 } 166 for (int i = 0; i < fields_.length(); i++) { 167 // Merge the field approximations for like fields. 168 HFieldApproximation* approx = fields_[i]; 169 HFieldApproximation* prev = NULL; 170 while (approx != NULL) { 171 // TODO(titzer): Merging is O(N * M); sort? 172 HFieldApproximation* other = that->Find(approx->object_, i); 173 if (other == NULL || !Equal(approx->last_value_, other->last_value_)) { 174 // Kill an entry that doesn't agree with the other value. 175 if (prev != NULL) { 176 prev->next_ = approx->next_; 177 } else { 178 fields_[i] = approx->next_; 179 } 180 approx = approx->next_; 181 continue; 182 } 183 prev = approx; 184 approx = approx->next_; 185 } 186 } 187 if (FLAG_trace_load_elimination) { 188 TRACE((" merge-to B%d\n", succ->block_id())); 189 Print(); 190 } 191 return this; 192 } 193 194 friend class HLoadEliminationEffects; // Calls Kill() and others. 195 friend class HLoadEliminationPhase; 196 197 private: 198 // Process a load instruction, updating internal table state. If a previous 199 // load or store for this object and field exists, return the new value with 200 // which the load should be replaced. Otherwise, return {instr}. 201 HValue* load(HLoadNamedField* instr) { 202 // There must be no loads from non observable in-object properties. 203 DCHECK(!instr->access().IsInobject() || 204 instr->access().existing_inobject_property()); 205 206 int field = FieldOf(instr->access()); 207 if (field < 0) return instr; 208 209 HValue* object = instr->object()->ActualValue(); 210 HFieldApproximation* approx = FindOrCreate(object, field); 211 212 if (approx->last_value_ == NULL) { 213 // Load is not redundant. Fill out a new entry. 214 approx->last_value_ = instr; 215 return instr; 216 } else if (approx->last_value_->block()->EqualToOrDominates( 217 instr->block())) { 218 // Eliminate the load. Reuse previously stored value or load instruction. 219 return approx->last_value_; 220 } else { 221 return instr; 222 } 223 } 224 225 // Process a store instruction, updating internal table state. If a previous 226 // store to the same object and field makes this store redundant (e.g. because 227 // the stored values are the same), return NULL indicating that this store 228 // instruction is redundant. Otherwise, return {instr}. 229 HValue* store(HStoreNamedField* instr) { 230 if (instr->access().IsInobject() && 231 !instr->access().existing_inobject_property()) { 232 TRACE((" skipping non existing property initialization store\n")); 233 return instr; 234 } 235 236 int field = FieldOf(instr->access()); 237 if (field < 0) return KillIfMisaligned(instr); 238 239 HValue* object = instr->object()->ActualValue(); 240 HValue* value = instr->value(); 241 242 if (instr->has_transition()) { 243 // A transition introduces a new field and alters the map of the object. 244 // Since the field in the object is new, it cannot alias existing entries. 245 // TODO(titzer): introduce a constant for the new map and remember it. 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 // TODO(titzer): track misaligned loads in a separate list? 405 if ((offset % kPointerSize) != 0) return -1; // Ignore misaligned accesses. 406 return offset / kPointerSize; 407 } 408 409 // Ensure internal storage for the given number of fields. 410 void EnsureFields(int num_fields) { 411 if (fields_.length() < num_fields) { 412 fields_.AddBlock(NULL, num_fields - fields_.length(), zone_); 413 } 414 } 415 416 // Print this table to stdout. 417 void Print() { 418 for (int i = 0; i < fields_.length(); i++) { 419 PrintF(" field %d: ", i); 420 for (HFieldApproximation* a = fields_[i]; a != NULL; a = a->next_) { 421 PrintF("[o%d =", a->object_->id()); 422 if (a->last_value_ != NULL) PrintF(" v%d", a->last_value_->id()); 423 PrintF("] "); 424 } 425 PrintF("\n"); 426 } 427 } 428 429 Zone* zone_; 430 ZoneList<HFieldApproximation*> fields_; 431 HAliasAnalyzer* aliasing_; 432 }; 433 434 435 // Support for HFlowEngine: collect store effects within loops. 436 class HLoadEliminationEffects : public ZoneObject { 437 public: 438 explicit HLoadEliminationEffects(Zone* zone) 439 : zone_(zone), stores_(5, zone) { } 440 441 inline bool Disabled() { 442 return false; // Effects are _not_ disabled. 443 } 444 445 // Process a possibly side-effecting instruction. 446 void Process(HInstruction* instr, Zone* zone) { 447 if (instr->IsStoreNamedField()) { 448 stores_.Add(HStoreNamedField::cast(instr), zone_); 449 } else { 450 flags_.Add(instr->ChangesFlags()); 451 } 452 } 453 454 // Apply these effects to the given load elimination table. 455 void Apply(HLoadEliminationTable* table) { 456 // Loads must not be hoisted past the OSR entry, therefore we kill 457 // everything if we see an OSR entry. 458 if (flags_.Contains(kInobjectFields) || flags_.Contains(kOsrEntries)) { 459 table->Kill(); 460 return; 461 } 462 if (flags_.Contains(kElementsKind) || flags_.Contains(kMaps)) { 463 table->KillOffset(JSObject::kMapOffset); 464 } 465 if (flags_.Contains(kElementsKind) || flags_.Contains(kElementsPointer)) { 466 table->KillOffset(JSObject::kElementsOffset); 467 } 468 469 // Kill non-agreeing fields for each store contained in these effects. 470 for (int i = 0; i < stores_.length(); i++) { 471 table->KillStore(stores_[i]); 472 } 473 } 474 475 // Union these effects with the other effects. 476 void Union(HLoadEliminationEffects* that, Zone* zone) { 477 flags_.Add(that->flags_); 478 for (int i = 0; i < that->stores_.length(); i++) { 479 stores_.Add(that->stores_[i], zone); 480 } 481 } 482 483 private: 484 Zone* zone_; 485 GVNFlagSet flags_; 486 ZoneList<HStoreNamedField*> stores_; 487 }; 488 489 490 // The main routine of the analysis phase. Use the HFlowEngine for either a 491 // local or a global analysis. 492 void HLoadEliminationPhase::Run() { 493 HFlowEngine<HLoadEliminationTable, HLoadEliminationEffects> 494 engine(graph(), zone()); 495 HAliasAnalyzer aliasing; 496 HLoadEliminationTable* table = 497 new(zone()) HLoadEliminationTable(zone(), &aliasing); 498 499 if (GLOBAL) { 500 // Perform a global analysis. 501 engine.AnalyzeDominatedBlocks(graph()->blocks()->at(0), table); 502 } else { 503 // Perform only local analysis. 504 for (int i = 0; i < graph()->blocks()->length(); i++) { 505 table->Kill(); 506 engine.AnalyzeOneBlock(graph()->blocks()->at(i), table); 507 } 508 } 509 } 510 511 } } // namespace v8::internal 512