// Copyright 2013 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "src/crankshaft/hydrogen-load-elimination.h" #include "src/crankshaft/hydrogen-alias-analysis.h" #include "src/crankshaft/hydrogen-flow-engine.h" #include "src/crankshaft/hydrogen-instructions.h" #include "src/objects-inl.h" namespace v8 { namespace internal { #define GLOBAL true #define TRACE(x) if (FLAG_trace_load_elimination) PrintF x static const int kMaxTrackedFields = 16; static const int kMaxTrackedObjects = 5; // An element in the field approximation list. class HFieldApproximation : public ZoneObject { public: // Just a data blob. HValue* object_; HValue* last_value_; HFieldApproximation* next_; // Recursively copy the entire linked list of field approximations. HFieldApproximation* Copy(Zone* zone) { HFieldApproximation* copy = new(zone) HFieldApproximation(); copy->object_ = this->object_; copy->last_value_ = this->last_value_; copy->next_ = this->next_ == NULL ? NULL : this->next_->Copy(zone); return copy; } }; // The main datastructure used during load/store elimination. Each in-object // field is tracked separately. For each field, store a list of known field // values for known objects. class HLoadEliminationTable : public ZoneObject { public: HLoadEliminationTable(Zone* zone, HAliasAnalyzer* aliasing) : zone_(zone), fields_(kMaxTrackedFields, zone), aliasing_(aliasing) { } // The main processing of instructions. HLoadEliminationTable* Process(HInstruction* instr, Zone* zone) { switch (instr->opcode()) { case HValue::kLoadNamedField: { HLoadNamedField* l = HLoadNamedField::cast(instr); TRACE((" process L%d field %d (o%d)\n", instr->id(), FieldOf(l->access()), l->object()->ActualValue()->id())); HValue* result = load(l); if (result != instr && l->CanBeReplacedWith(result)) { // The load can be replaced with a previous load or a value. TRACE((" replace L%d -> v%d\n", instr->id(), result->id())); instr->DeleteAndReplaceWith(result); } break; } case HValue::kStoreNamedField: { HStoreNamedField* s = HStoreNamedField::cast(instr); TRACE((" process S%d field %d (o%d) = v%d\n", instr->id(), FieldOf(s->access()), s->object()->ActualValue()->id(), s->value()->id())); HValue* result = store(s); if (result == NULL) { // The store is redundant. Remove it. TRACE((" remove S%d\n", instr->id())); instr->DeleteAndReplaceWith(NULL); } break; } case HValue::kTransitionElementsKind: { HTransitionElementsKind* t = HTransitionElementsKind::cast(instr); HValue* object = t->object()->ActualValue(); KillFieldInternal(object, FieldOf(JSArray::kElementsOffset), NULL); KillFieldInternal(object, FieldOf(JSObject::kMapOffset), NULL); break; } default: { if (instr->CheckChangesFlag(kInobjectFields)) { TRACE((" kill-all i%d\n", instr->id())); Kill(); break; } if (instr->CheckChangesFlag(kMaps)) { TRACE((" kill-maps i%d\n", instr->id())); KillOffset(JSObject::kMapOffset); } if (instr->CheckChangesFlag(kElementsKind)) { TRACE((" kill-elements-kind i%d\n", instr->id())); KillOffset(JSObject::kMapOffset); KillOffset(JSObject::kElementsOffset); } if (instr->CheckChangesFlag(kElementsPointer)) { TRACE((" kill-elements i%d\n", instr->id())); KillOffset(JSObject::kElementsOffset); } if (instr->CheckChangesFlag(kOsrEntries)) { TRACE((" kill-osr i%d\n", instr->id())); Kill(); } } // Improvements possible: // - learn from HCheckMaps for field 0 // - remove unobservable stores (write-after-write) // - track cells // - track globals // - track roots } return this; } // Support for global analysis with HFlowEngine: Merge given state with // the other incoming state. static HLoadEliminationTable* Merge(HLoadEliminationTable* succ_state, HBasicBlock* succ_block, HLoadEliminationTable* pred_state, HBasicBlock* pred_block, Zone* zone) { DCHECK(pred_state != NULL); if (succ_state == NULL) { return pred_state->Copy(succ_block, pred_block, zone); } else { return succ_state->Merge(succ_block, pred_state, pred_block, zone); } } // Support for global analysis with HFlowEngine: Given state merged with all // the other incoming states, prepare it for use. static HLoadEliminationTable* Finish(HLoadEliminationTable* state, HBasicBlock* block, Zone* zone) { DCHECK(state != NULL); return state; } private: // Copy state to successor block. HLoadEliminationTable* Copy(HBasicBlock* succ, HBasicBlock* from_block, Zone* zone) { HLoadEliminationTable* copy = new(zone) HLoadEliminationTable(zone, aliasing_); copy->EnsureFields(fields_.length()); for (int i = 0; i < fields_.length(); i++) { copy->fields_[i] = fields_[i] == NULL ? NULL : fields_[i]->Copy(zone); } if (FLAG_trace_load_elimination) { TRACE((" copy-to B%d\n", succ->block_id())); copy->Print(); } return copy; } // Merge this state with the other incoming state. HLoadEliminationTable* Merge(HBasicBlock* succ, HLoadEliminationTable* that, HBasicBlock* that_block, Zone* zone) { if (that->fields_.length() < fields_.length()) { // Drop fields not in the other table. fields_.Rewind(that->fields_.length()); } for (int i = 0; i < fields_.length(); i++) { // Merge the field approximations for like fields. HFieldApproximation* approx = fields_[i]; HFieldApproximation* prev = NULL; while (approx != NULL) { // TODO(titzer): Merging is O(N * M); sort? HFieldApproximation* other = that->Find(approx->object_, i); if (other == NULL || !Equal(approx->last_value_, other->last_value_)) { // Kill an entry that doesn't agree with the other value. if (prev != NULL) { prev->next_ = approx->next_; } else { fields_[i] = approx->next_; } approx = approx->next_; continue; } prev = approx; approx = approx->next_; } } if (FLAG_trace_load_elimination) { TRACE((" merge-to B%d\n", succ->block_id())); Print(); } return this; } friend class HLoadEliminationEffects; // Calls Kill() and others. friend class HLoadEliminationPhase; private: // Process a load instruction, updating internal table state. If a previous // load or store for this object and field exists, return the new value with // which the load should be replaced. Otherwise, return {instr}. HValue* load(HLoadNamedField* instr) { // There must be no loads from non observable in-object properties. DCHECK(!instr->access().IsInobject() || instr->access().existing_inobject_property()); int field = FieldOf(instr->access()); if (field < 0) return instr; HValue* object = instr->object()->ActualValue(); HFieldApproximation* approx = FindOrCreate(object, field); if (approx->last_value_ == NULL) { // Load is not redundant. Fill out a new entry. approx->last_value_ = instr; return instr; } else if (approx->last_value_->block()->EqualToOrDominates( instr->block())) { // Eliminate the load. Reuse previously stored value or load instruction. return approx->last_value_; } else { return instr; } } // Process a store instruction, updating internal table state. If a previous // store to the same object and field makes this store redundant (e.g. because // the stored values are the same), return NULL indicating that this store // instruction is redundant. Otherwise, return {instr}. HValue* store(HStoreNamedField* instr) { if (instr->access().IsInobject() && !instr->access().existing_inobject_property()) { TRACE((" skipping non existing property initialization store\n")); return instr; } int field = FieldOf(instr->access()); if (field < 0) return KillIfMisaligned(instr); HValue* object = instr->object()->ActualValue(); HValue* value = instr->value(); if (instr->has_transition()) { // A transition introduces a new field and alters the map of the object. // Since the field in the object is new, it cannot alias existing entries. KillFieldInternal(object, FieldOf(JSObject::kMapOffset), NULL); } else { // Kill non-equivalent may-alias entries. KillFieldInternal(object, field, value); } HFieldApproximation* approx = FindOrCreate(object, field); if (Equal(approx->last_value_, value)) { // The store is redundant because the field already has this value. return NULL; } else { // The store is not redundant. Update the entry. approx->last_value_ = value; return instr; } } // Kill everything in this table. void Kill() { fields_.Rewind(0); } // Kill all entries matching the given offset. void KillOffset(int offset) { int field = FieldOf(offset); if (field >= 0 && field < fields_.length()) { fields_[field] = NULL; } } // Kill all entries aliasing the given store. void KillStore(HStoreNamedField* s) { int field = FieldOf(s->access()); if (field >= 0) { KillFieldInternal(s->object()->ActualValue(), field, s->value()); } else { KillIfMisaligned(s); } } // Kill multiple entries in the case of a misaligned store. HValue* KillIfMisaligned(HStoreNamedField* instr) { HObjectAccess access = instr->access(); if (access.IsInobject()) { int offset = access.offset(); if ((offset % kPointerSize) != 0) { // Kill the field containing the first word of the access. HValue* object = instr->object()->ActualValue(); int field = offset / kPointerSize; KillFieldInternal(object, field, NULL); // Kill the next field in case of overlap. int size = access.representation().size(); int next_field = (offset + size - 1) / kPointerSize; if (next_field != field) KillFieldInternal(object, next_field, NULL); } } return instr; } // Find an entry for the given object and field pair. HFieldApproximation* Find(HValue* object, int field) { // Search for a field approximation for this object. HFieldApproximation* approx = fields_[field]; while (approx != NULL) { if (aliasing_->MustAlias(object, approx->object_)) return approx; approx = approx->next_; } return NULL; } // Find or create an entry for the given object and field pair. HFieldApproximation* FindOrCreate(HValue* object, int field) { EnsureFields(field + 1); // Search for a field approximation for this object. HFieldApproximation* approx = fields_[field]; int count = 0; while (approx != NULL) { if (aliasing_->MustAlias(object, approx->object_)) return approx; count++; approx = approx->next_; } if (count >= kMaxTrackedObjects) { // Pull the last entry off the end and repurpose it for this object. approx = ReuseLastApproximation(field); } else { // Allocate a new entry. approx = new(zone_) HFieldApproximation(); } // Insert the entry at the head of the list. approx->object_ = object; approx->last_value_ = NULL; approx->next_ = fields_[field]; fields_[field] = approx; return approx; } // Kill all entries for a given field that _may_ alias the given object // and do _not_ have the given value. void KillFieldInternal(HValue* object, int field, HValue* value) { if (field >= fields_.length()) return; // Nothing to do. HFieldApproximation* approx = fields_[field]; HFieldApproximation* prev = NULL; while (approx != NULL) { if (aliasing_->MayAlias(object, approx->object_)) { if (!Equal(approx->last_value_, value)) { // Kill an aliasing entry that doesn't agree on the value. if (prev != NULL) { prev->next_ = approx->next_; } else { fields_[field] = approx->next_; } approx = approx->next_; continue; } } prev = approx; approx = approx->next_; } } bool Equal(HValue* a, HValue* b) { if (a == b) return true; if (a != NULL && b != NULL && a->CheckFlag(HValue::kUseGVN)) { return a->Equals(b); } return false; } // Remove the last approximation for a field so that it can be reused. // We reuse the last entry because it was the first inserted and is thus // farthest away from the current instruction. HFieldApproximation* ReuseLastApproximation(int field) { HFieldApproximation* approx = fields_[field]; DCHECK(approx != NULL); HFieldApproximation* prev = NULL; while (approx->next_ != NULL) { prev = approx; approx = approx->next_; } if (prev != NULL) prev->next_ = NULL; return approx; } // Compute the field index for the given object access; -1 if not tracked. int FieldOf(HObjectAccess access) { return access.IsInobject() ? FieldOf(access.offset()) : -1; } // Compute the field index for the given in-object offset; -1 if not tracked. int FieldOf(int offset) { if (offset >= kMaxTrackedFields * kPointerSize) return -1; if ((offset % kPointerSize) != 0) return -1; // Ignore misaligned accesses. return offset / kPointerSize; } // Ensure internal storage for the given number of fields. void EnsureFields(int num_fields) { if (fields_.length() < num_fields) { fields_.AddBlock(NULL, num_fields - fields_.length(), zone_); } } // Print this table to stdout. void Print() { for (int i = 0; i < fields_.length(); i++) { PrintF(" field %d: ", i); for (HFieldApproximation* a = fields_[i]; a != NULL; a = a->next_) { PrintF("[o%d =", a->object_->id()); if (a->last_value_ != NULL) PrintF(" v%d", a->last_value_->id()); PrintF("] "); } PrintF("\n"); } } Zone* zone_; ZoneList<HFieldApproximation*> fields_; HAliasAnalyzer* aliasing_; }; // Support for HFlowEngine: collect store effects within loops. class HLoadEliminationEffects : public ZoneObject { public: explicit HLoadEliminationEffects(Zone* zone) : zone_(zone), stores_(5, zone) { } inline bool Disabled() { return false; // Effects are _not_ disabled. } // Process a possibly side-effecting instruction. void Process(HInstruction* instr, Zone* zone) { if (instr->IsStoreNamedField()) { stores_.Add(HStoreNamedField::cast(instr), zone_); } else { flags_.Add(instr->ChangesFlags()); } } // Apply these effects to the given load elimination table. void Apply(HLoadEliminationTable* table) { // Loads must not be hoisted past the OSR entry, therefore we kill // everything if we see an OSR entry. if (flags_.Contains(kInobjectFields) || flags_.Contains(kOsrEntries)) { table->Kill(); return; } if (flags_.Contains(kElementsKind) || flags_.Contains(kMaps)) { table->KillOffset(JSObject::kMapOffset); } if (flags_.Contains(kElementsKind) || flags_.Contains(kElementsPointer)) { table->KillOffset(JSObject::kElementsOffset); } // Kill non-agreeing fields for each store contained in these effects. for (int i = 0; i < stores_.length(); i++) { table->KillStore(stores_[i]); } } // Union these effects with the other effects. void Union(HLoadEliminationEffects* that, Zone* zone) { flags_.Add(that->flags_); for (int i = 0; i < that->stores_.length(); i++) { stores_.Add(that->stores_[i], zone); } } private: Zone* zone_; GVNFlagSet flags_; ZoneList<HStoreNamedField*> stores_; }; // The main routine of the analysis phase. Use the HFlowEngine for either a // local or a global analysis. void HLoadEliminationPhase::Run() { HFlowEngine<HLoadEliminationTable, HLoadEliminationEffects> engine(graph(), zone()); HAliasAnalyzer aliasing; HLoadEliminationTable* table = new(zone()) HLoadEliminationTable(zone(), &aliasing); if (GLOBAL) { // Perform a global analysis. engine.AnalyzeDominatedBlocks(graph()->blocks()->at(0), table); } else { // Perform only local analysis. for (int i = 0; i < graph()->blocks()->length(); i++) { table->Kill(); engine.AnalyzeOneBlock(graph()->blocks()->at(i), table); } } } } // namespace internal } // namespace v8