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-alias-analysis.h" 29 #include "hydrogen-load-elimination.h" 30 #include "hydrogen-instructions.h" 31 #include "hydrogen-flow-engine.h" 32 33 namespace v8 { 34 namespace internal { 35 36 #define GLOBAL true 37 #define TRACE(x) if (FLAG_trace_load_elimination) PrintF x 38 39 static const int kMaxTrackedFields = 16; 40 static const int kMaxTrackedObjects = 5; 41 42 // An element in the field approximation list. 43 class HFieldApproximation : public ZoneObject { 44 public: // Just a data blob. 45 HValue* object_; 46 HLoadNamedField* last_load_; 47 HValue* last_value_; 48 HFieldApproximation* next_; 49 50 // Recursively copy the entire linked list of field approximations. 51 HFieldApproximation* Copy(Zone* zone) { 52 if (this == NULL) return NULL; 53 HFieldApproximation* copy = new(zone) HFieldApproximation(); 54 copy->object_ = this->object_; 55 copy->last_load_ = this->last_load_; 56 copy->last_value_ = this->last_value_; 57 copy->next_ = this->next_->Copy(zone); 58 return copy; 59 } 60 }; 61 62 63 // The main datastructure used during load/store elimination. Each in-object 64 // field is tracked separately. For each field, store a list of known field 65 // values for known objects. 66 class HLoadEliminationTable : public ZoneObject { 67 public: 68 HLoadEliminationTable(Zone* zone, HAliasAnalyzer* aliasing) 69 : zone_(zone), fields_(kMaxTrackedFields, zone), aliasing_(aliasing) { } 70 71 // The main processing of instructions. 72 HLoadEliminationTable* Process(HInstruction* instr, Zone* zone) { 73 switch (instr->opcode()) { 74 case HValue::kLoadNamedField: { 75 HLoadNamedField* l = HLoadNamedField::cast(instr); 76 TRACE((" process L%d field %d (o%d)\n", 77 instr->id(), 78 FieldOf(l->access()), 79 l->object()->ActualValue()->id())); 80 HValue* result = load(l); 81 if (result != instr) { 82 // The load can be replaced with a previous load or a value. 83 TRACE((" replace L%d -> v%d\n", instr->id(), result->id())); 84 instr->DeleteAndReplaceWith(result); 85 } 86 break; 87 } 88 case HValue::kStoreNamedField: { 89 HStoreNamedField* s = HStoreNamedField::cast(instr); 90 TRACE((" process S%d field %d (o%d) = v%d\n", 91 instr->id(), 92 FieldOf(s->access()), 93 s->object()->ActualValue()->id(), 94 s->value()->id())); 95 HValue* result = store(s); 96 if (result == NULL) { 97 // The store is redundant. Remove it. 98 TRACE((" remove S%d\n", instr->id())); 99 instr->DeleteAndReplaceWith(NULL); 100 } 101 break; 102 } 103 default: { 104 if (instr->CheckGVNFlag(kChangesInobjectFields)) { 105 TRACE((" kill-all i%d\n", instr->id())); 106 Kill(); 107 break; 108 } 109 if (instr->CheckGVNFlag(kChangesMaps)) { 110 TRACE((" kill-maps i%d\n", instr->id())); 111 KillOffset(JSObject::kMapOffset); 112 } 113 if (instr->CheckGVNFlag(kChangesElementsKind)) { 114 TRACE((" kill-elements-kind i%d\n", instr->id())); 115 KillOffset(JSObject::kMapOffset); 116 KillOffset(JSObject::kElementsOffset); 117 } 118 if (instr->CheckGVNFlag(kChangesElementsPointer)) { 119 TRACE((" kill-elements i%d\n", instr->id())); 120 KillOffset(JSObject::kElementsOffset); 121 } 122 if (instr->CheckGVNFlag(kChangesOsrEntries)) { 123 TRACE((" kill-osr i%d\n", instr->id())); 124 Kill(); 125 } 126 } 127 // Improvements possible: 128 // - learn from HCheckMaps for field 0 129 // - remove unobservable stores (write-after-write) 130 // - track cells 131 // - track globals 132 // - track roots 133 } 134 return this; 135 } 136 137 // Support for global analysis with HFlowEngine: Copy state to sucessor block. 138 HLoadEliminationTable* Copy(HBasicBlock* succ, Zone* zone) { 139 HLoadEliminationTable* copy = 140 new(zone) HLoadEliminationTable(zone, aliasing_); 141 copy->EnsureFields(fields_.length()); 142 for (int i = 0; i < fields_.length(); i++) { 143 copy->fields_[i] = fields_[i]->Copy(zone); 144 } 145 if (FLAG_trace_load_elimination) { 146 TRACE((" copy-to B%d\n", succ->block_id())); 147 copy->Print(); 148 } 149 return copy; 150 } 151 152 // Support for global analysis with HFlowEngine: Merge this state with 153 // the other incoming state. 154 HLoadEliminationTable* Merge(HBasicBlock* succ, 155 HLoadEliminationTable* that, Zone* zone) { 156 if (that->fields_.length() < fields_.length()) { 157 // Drop fields not in the other table. 158 fields_.Rewind(that->fields_.length()); 159 } 160 for (int i = 0; i < fields_.length(); i++) { 161 // Merge the field approximations for like fields. 162 HFieldApproximation* approx = fields_[i]; 163 HFieldApproximation* prev = NULL; 164 while (approx != NULL) { 165 // TODO(titzer): Merging is O(N * M); sort? 166 HFieldApproximation* other = that->Find(approx->object_, i); 167 if (other == NULL || !Equal(approx->last_value_, other->last_value_)) { 168 // Kill an entry that doesn't agree with the other value. 169 if (prev != NULL) { 170 prev->next_ = approx->next_; 171 } else { 172 fields_[i] = approx->next_; 173 } 174 approx = approx->next_; 175 continue; 176 } 177 prev = approx; 178 approx = approx->next_; 179 } 180 } 181 return this; 182 } 183 184 friend class HLoadEliminationEffects; // Calls Kill() and others. 185 friend class HLoadEliminationPhase; 186 187 private: 188 // Process a load instruction, updating internal table state. If a previous 189 // load or store for this object and field exists, return the new value with 190 // which the load should be replaced. Otherwise, return {instr}. 191 HValue* load(HLoadNamedField* instr) { 192 int field = FieldOf(instr->access()); 193 if (field < 0) return instr; 194 195 HValue* object = instr->object()->ActualValue(); 196 HFieldApproximation* approx = FindOrCreate(object, field); 197 198 if (approx->last_value_ == NULL) { 199 // Load is not redundant. Fill out a new entry. 200 approx->last_load_ = instr; 201 approx->last_value_ = instr; 202 return instr; 203 } else { 204 // Eliminate the load. Reuse previously stored value or load instruction. 205 return approx->last_value_; 206 } 207 } 208 209 // Process a store instruction, updating internal table state. If a previous 210 // store to the same object and field makes this store redundant (e.g. because 211 // the stored values are the same), return NULL indicating that this store 212 // instruction is redundant. Otherwise, return {instr}. 213 HValue* store(HStoreNamedField* instr) { 214 int field = FieldOf(instr->access()); 215 if (field < 0) return KillIfMisaligned(instr); 216 217 HValue* object = instr->object()->ActualValue(); 218 HValue* value = instr->value(); 219 220 // Kill non-equivalent may-alias entries. 221 KillFieldInternal(object, field, value); 222 if (instr->has_transition()) { 223 // A transition store alters the map of the object. 224 // TODO(titzer): remember the new map (a constant) for the object. 225 KillFieldInternal(object, FieldOf(JSObject::kMapOffset), NULL); 226 } 227 HFieldApproximation* approx = FindOrCreate(object, field); 228 229 if (Equal(approx->last_value_, value)) { 230 // The store is redundant because the field already has this value. 231 return NULL; 232 } else { 233 // The store is not redundant. Update the entry. 234 approx->last_load_ = NULL; 235 approx->last_value_ = value; 236 return instr; 237 } 238 } 239 240 // Kill everything in this table. 241 void Kill() { 242 fields_.Rewind(0); 243 } 244 245 // Kill all entries matching the given offset. 246 void KillOffset(int offset) { 247 int field = FieldOf(offset); 248 if (field >= 0 && field < fields_.length()) { 249 fields_[field] = NULL; 250 } 251 } 252 253 // Kill all entries aliasing the given store. 254 void KillStore(HStoreNamedField* s) { 255 int field = FieldOf(s->access()); 256 if (field >= 0) { 257 KillFieldInternal(s->object()->ActualValue(), field, s->value()); 258 } else { 259 KillIfMisaligned(s); 260 } 261 } 262 263 // Kill multiple entries in the case of a misaligned store. 264 HValue* KillIfMisaligned(HStoreNamedField* instr) { 265 HObjectAccess access = instr->access(); 266 if (access.IsInobject()) { 267 int offset = access.offset(); 268 if ((offset % kPointerSize) != 0) { 269 // Kill the field containing the first word of the access. 270 HValue* object = instr->object()->ActualValue(); 271 int field = offset / kPointerSize; 272 KillFieldInternal(object, field, NULL); 273 274 // Kill the next field in case of overlap. 275 int size = access.representation().size(); 276 int next_field = (offset + size - 1) / kPointerSize; 277 if (next_field != field) KillFieldInternal(object, next_field, NULL); 278 } 279 } 280 return instr; 281 } 282 283 // Find an entry for the given object and field pair. 284 HFieldApproximation* Find(HValue* object, int field) { 285 // Search for a field approximation for this object. 286 HFieldApproximation* approx = fields_[field]; 287 while (approx != NULL) { 288 if (aliasing_->MustAlias(object, approx->object_)) return approx; 289 approx = approx->next_; 290 } 291 return NULL; 292 } 293 294 // Find or create an entry for the given object and field pair. 295 HFieldApproximation* FindOrCreate(HValue* object, int field) { 296 EnsureFields(field + 1); 297 298 // Search for a field approximation for this object. 299 HFieldApproximation* approx = fields_[field]; 300 int count = 0; 301 while (approx != NULL) { 302 if (aliasing_->MustAlias(object, approx->object_)) return approx; 303 count++; 304 approx = approx->next_; 305 } 306 307 if (count >= kMaxTrackedObjects) { 308 // Pull the last entry off the end and repurpose it for this object. 309 approx = ReuseLastApproximation(field); 310 } else { 311 // Allocate a new entry. 312 approx = new(zone_) HFieldApproximation(); 313 } 314 315 // Insert the entry at the head of the list. 316 approx->object_ = object; 317 approx->last_load_ = NULL; 318 approx->last_value_ = NULL; 319 approx->next_ = fields_[field]; 320 fields_[field] = approx; 321 322 return approx; 323 } 324 325 // Kill all entries for a given field that _may_ alias the given object 326 // and do _not_ have the given value. 327 void KillFieldInternal(HValue* object, int field, HValue* value) { 328 if (field >= fields_.length()) return; // Nothing to do. 329 330 HFieldApproximation* approx = fields_[field]; 331 HFieldApproximation* prev = NULL; 332 while (approx != NULL) { 333 if (aliasing_->MayAlias(object, approx->object_)) { 334 if (!Equal(approx->last_value_, value)) { 335 // Kill an aliasing entry that doesn't agree on the value. 336 if (prev != NULL) { 337 prev->next_ = approx->next_; 338 } else { 339 fields_[field] = approx->next_; 340 } 341 approx = approx->next_; 342 continue; 343 } 344 } 345 prev = approx; 346 approx = approx->next_; 347 } 348 } 349 350 bool Equal(HValue* a, HValue* b) { 351 if (a == b) return true; 352 if (a != NULL && b != NULL && a->CheckFlag(HValue::kUseGVN)) { 353 return a->Equals(b); 354 } 355 return false; 356 } 357 358 // Remove the last approximation for a field so that it can be reused. 359 // We reuse the last entry because it was the first inserted and is thus 360 // farthest away from the current instruction. 361 HFieldApproximation* ReuseLastApproximation(int field) { 362 HFieldApproximation* approx = fields_[field]; 363 ASSERT(approx != NULL); 364 365 HFieldApproximation* prev = NULL; 366 while (approx->next_ != NULL) { 367 prev = approx; 368 approx = approx->next_; 369 } 370 if (prev != NULL) prev->next_ = NULL; 371 return approx; 372 } 373 374 // Compute the field index for the given object access; -1 if not tracked. 375 int FieldOf(HObjectAccess access) { 376 return access.IsInobject() ? FieldOf(access.offset()) : -1; 377 } 378 379 // Compute the field index for the given in-object offset; -1 if not tracked. 380 int FieldOf(int offset) { 381 if (offset >= kMaxTrackedFields * kPointerSize) return -1; 382 // TODO(titzer): track misaligned loads in a separate list? 383 if ((offset % kPointerSize) != 0) return -1; // Ignore misaligned accesses. 384 return offset / kPointerSize; 385 } 386 387 // Ensure internal storage for the given number of fields. 388 void EnsureFields(int num_fields) { 389 if (fields_.length() < num_fields) { 390 fields_.AddBlock(NULL, num_fields - fields_.length(), zone_); 391 } 392 } 393 394 // Print this table to stdout. 395 void Print() { 396 for (int i = 0; i < fields_.length(); i++) { 397 PrintF(" field %d: ", i); 398 for (HFieldApproximation* a = fields_[i]; a != NULL; a = a->next_) { 399 PrintF("[o%d =", a->object_->id()); 400 if (a->last_load_ != NULL) PrintF(" L%d", a->last_load_->id()); 401 if (a->last_value_ != NULL) PrintF(" v%d", a->last_value_->id()); 402 PrintF("] "); 403 } 404 PrintF("\n"); 405 } 406 } 407 408 Zone* zone_; 409 ZoneList<HFieldApproximation*> fields_; 410 HAliasAnalyzer* aliasing_; 411 }; 412 413 414 // Support for HFlowEngine: collect store effects within loops. 415 class HLoadEliminationEffects : public ZoneObject { 416 public: 417 explicit HLoadEliminationEffects(Zone* zone) 418 : zone_(zone), 419 maps_stored_(false), 420 fields_stored_(false), 421 elements_stored_(false), 422 stores_(5, zone) { } 423 424 inline bool Disabled() { 425 return false; // Effects are _not_ disabled. 426 } 427 428 // Process a possibly side-effecting instruction. 429 void Process(HInstruction* instr, Zone* zone) { 430 switch (instr->opcode()) { 431 case HValue::kStoreNamedField: { 432 stores_.Add(HStoreNamedField::cast(instr), zone_); 433 break; 434 } 435 case HValue::kOsrEntry: { 436 // Kill everything. Loads must not be hoisted past the OSR entry. 437 maps_stored_ = true; 438 fields_stored_ = true; 439 elements_stored_ = true; 440 } 441 default: { 442 fields_stored_ |= instr->CheckGVNFlag(kChangesInobjectFields); 443 maps_stored_ |= instr->CheckGVNFlag(kChangesMaps); 444 maps_stored_ |= instr->CheckGVNFlag(kChangesElementsKind); 445 elements_stored_ |= instr->CheckGVNFlag(kChangesElementsKind); 446 elements_stored_ |= instr->CheckGVNFlag(kChangesElementsPointer); 447 } 448 } 449 } 450 451 // Apply these effects to the given load elimination table. 452 void Apply(HLoadEliminationTable* table) { 453 if (fields_stored_) { 454 table->Kill(); 455 return; 456 } 457 if (maps_stored_) { 458 table->KillOffset(JSObject::kMapOffset); 459 } 460 if (elements_stored_) { 461 table->KillOffset(JSObject::kElementsOffset); 462 } 463 464 // Kill non-agreeing fields for each store contained in these effects. 465 for (int i = 0; i < stores_.length(); i++) { 466 table->KillStore(stores_[i]); 467 } 468 } 469 470 // Union these effects with the other effects. 471 void Union(HLoadEliminationEffects* that, Zone* zone) { 472 maps_stored_ |= that->maps_stored_; 473 fields_stored_ |= that->fields_stored_; 474 elements_stored_ |= that->elements_stored_; 475 for (int i = 0; i < that->stores_.length(); i++) { 476 stores_.Add(that->stores_[i], zone); 477 } 478 } 479 480 private: 481 Zone* zone_; 482 bool maps_stored_ : 1; 483 bool fields_stored_ : 1; 484 bool elements_stored_ : 1; 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 v8::internal 511