Index: src/hydrogen-load-elimination.cc |
diff --git a/src/hydrogen-load-elimination.cc b/src/hydrogen-load-elimination.cc |
index 6d01ae573be81d1dd2e86444cfa88af0a9091762..f33ebe2fabcb61aed1429792618e745e58de73ce 100644 |
--- a/src/hydrogen-load-elimination.cc |
+++ b/src/hydrogen-load-elimination.cc |
@@ -28,10 +28,14 @@ |
#include "hydrogen-alias-analysis.h" |
#include "hydrogen-load-elimination.h" |
#include "hydrogen-instructions.h" |
+#include "hydrogen-flow-engine.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; |
@@ -42,17 +46,145 @@ class HFieldApproximation : public ZoneObject { |
HLoadNamedField* last_load_; |
HValue* last_value_; |
HFieldApproximation* next_; |
+ |
+ // Recursively copy the entire linked list of field approximations. |
+ HFieldApproximation* Copy(Zone* zone) { |
+ if (this == NULL) return NULL; |
+ HFieldApproximation* copy = new(zone) HFieldApproximation(); |
+ copy->object_ = this->object_; |
+ copy->last_load_ = this->last_load_; |
+ copy->last_value_ = this->last_value_; |
+ copy->next_ = 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 BASE_EMBEDDED { |
+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) { |
+ // 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; |
+ } |
+ default: { |
+ if (instr->CheckGVNFlag(kChangesInobjectFields)) { |
+ TRACE((" kill-all i%d\n", instr->id())); |
+ Kill(); |
+ break; |
+ } |
+ if (instr->CheckGVNFlag(kChangesMaps)) { |
+ TRACE((" kill-maps i%d\n", instr->id())); |
+ KillOffset(JSObject::kMapOffset); |
+ } |
+ if (instr->CheckGVNFlag(kChangesElementsKind)) { |
+ TRACE((" kill-elements-kind i%d\n", instr->id())); |
+ KillOffset(JSObject::kMapOffset); |
+ KillOffset(JSObject::kElementsOffset); |
+ } |
+ if (instr->CheckGVNFlag(kChangesElementsPointer)) { |
+ TRACE((" kill-elements i%d\n", instr->id())); |
+ KillOffset(JSObject::kElementsOffset); |
+ } |
+ if (instr->CheckGVNFlag(kChangesOsrEntries)) { |
+ 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: Copy state to sucessor block. |
+ HLoadEliminationTable* Copy(HBasicBlock* succ, 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]->Copy(zone); |
+ } |
+ if (FLAG_trace_load_elimination) { |
+ TRACE((" copy-to B%d\n", succ->block_id())); |
+ copy->Print(); |
+ } |
+ return copy; |
+ } |
+ |
+ // Support for global analysis with HFlowEngine: Merge this state with |
+ // the other incoming state. |
+ HLoadEliminationTable* Merge(HBasicBlock* succ, |
+ HLoadEliminationTable* that, 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_; |
+ } |
+ } |
+ 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}. |
@@ -118,28 +250,17 @@ class HLoadEliminationTable BASE_EMBEDDED { |
} |
} |
- // Compute the field index for the given object access; -1 if not tracked. |
- int FieldOf(HObjectAccess access) { |
- // Only track kMaxTrackedFields in-object fields. |
- if (!access.IsInobject()) return -1; |
- return FieldOf(access.offset()); |
- } |
- |
- // 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_load_ != NULL) PrintF(" L%d", a->last_load_->id()); |
- if (a->last_value_ != NULL) PrintF(" v%d", a->last_value_->id()); |
- PrintF("] "); |
- } |
- PrintF("\n"); |
+ // 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; |
} |
- private: |
// Find or create an entry for the given object and field pair. |
HFieldApproximation* FindOrCreate(HValue* object, int field) { |
EnsureFields(field + 1); |
@@ -218,110 +339,143 @@ class HLoadEliminationTable BASE_EMBEDDED { |
return approx; |
} |
- // Ensure internal storage for the given number of fields. |
- void EnsureFields(int num_fields) { |
- while (fields_.length() < num_fields) fields_.Add(NULL, zone_); |
+ // 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. |
+ // Compute the field index for the given in-object offset; -1 if not tracked. |
int FieldOf(int offset) { |
if (offset >= kMaxTrackedFields * kPointerSize) return -1; |
ASSERT((offset % kPointerSize) == 0); // Assume aligned 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_load_ != NULL) PrintF(" L%d", a->last_load_->id()); |
+ if (a->last_value_ != NULL) PrintF(" v%d", a->last_value_->id()); |
+ PrintF("] "); |
+ } |
+ PrintF("\n"); |
+ } |
+ } |
+ |
Zone* zone_; |
ZoneList<HFieldApproximation*> fields_; |
HAliasAnalyzer* aliasing_; |
}; |
-void HLoadEliminationPhase::Run() { |
- for (int i = 0; i < graph()->blocks()->length(); i++) { |
- HBasicBlock* block = graph()->blocks()->at(i); |
- EliminateLoads(block); |
+// Support for HFlowEngine: collect store effects within loops. |
+class HLoadEliminationEffects : public ZoneObject { |
+ public: |
+ explicit HLoadEliminationEffects(Zone* zone) |
+ : zone_(zone), |
+ maps_stored_(false), |
+ fields_stored_(false), |
+ elements_stored_(false), |
+ stores_(5, zone) { } |
+ |
+ inline bool Disabled() { |
+ return false; // Effects are _not_ disabled. |
} |
-} |
- |
- |
-// For code de-uglification. |
-#define TRACE(x) if (FLAG_trace_load_elimination) PrintF x |
- |
- |
-// Eliminate loads and stores local to a block. |
-void HLoadEliminationPhase::EliminateLoads(HBasicBlock* block) { |
- HAliasAnalyzer aliasing; |
- HLoadEliminationTable table(zone(), &aliasing); |
- |
- TRACE(("-- load-elim B%d -------------------------------------------------\n", |
- block->block_id())); |
- |
- for (HInstructionIterator it(block); !it.Done(); it.Advance()) { |
- bool changed = false; |
- HInstruction* instr = it.Current(); |
+ // Process a possibly side-effecting instruction. |
+ void Process(HInstruction* instr, Zone* zone) { |
switch (instr->opcode()) { |
- case HValue::kLoadNamedField: { |
- HLoadNamedField* load = HLoadNamedField::cast(instr); |
- TRACE((" process L%d field %d (o%d)\n", |
- instr->id(), |
- table.FieldOf(load->access()), |
- load->object()->ActualValue()->id())); |
- HValue* result = table.load(load); |
- if (result != instr) { |
- // 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); |
- } |
- changed = true; |
- break; |
- } |
case HValue::kStoreNamedField: { |
- HStoreNamedField* store = HStoreNamedField::cast(instr); |
- TRACE((" process S%d field %d (o%d) = v%d\n", |
- instr->id(), |
- table.FieldOf(store->access()), |
- store->object()->ActualValue()->id(), |
- store->value()->id())); |
- HValue* result = table.store(store); |
- if (result == NULL) { |
- // The store is redundant. Remove it. |
- TRACE((" remove S%d\n", instr->id())); |
- instr->DeleteAndReplaceWith(NULL); |
- } |
- changed = true; |
+ stores_.Add(HStoreNamedField::cast(instr), zone_); |
break; |
} |
+ case HValue::kOsrEntry: { |
+ // Kill everything. Loads must not be hoisted past the OSR entry. |
+ maps_stored_ = true; |
+ fields_stored_ = true; |
+ elements_stored_ = true; |
+ } |
default: { |
- if (instr->CheckGVNFlag(kChangesInobjectFields)) { |
- TRACE((" kill-all i%d\n", instr->id())); |
- table.Kill(); |
- continue; |
- } |
- if (instr->CheckGVNFlag(kChangesMaps)) { |
- TRACE((" kill-maps i%d\n", instr->id())); |
- table.KillOffset(JSObject::kMapOffset); |
- } |
- if (instr->CheckGVNFlag(kChangesElementsKind)) { |
- TRACE((" kill-elements-kind i%d\n", instr->id())); |
- table.KillOffset(JSObject::kMapOffset); |
- table.KillOffset(JSObject::kElementsOffset); |
- } |
- if (instr->CheckGVNFlag(kChangesElementsPointer)) { |
- TRACE((" kill-elements i%d\n", instr->id())); |
- table.KillOffset(JSObject::kElementsOffset); |
- } |
+ fields_stored_ |= instr->CheckGVNFlag(kChangesInobjectFields); |
+ maps_stored_ |= instr->CheckGVNFlag(kChangesMaps); |
+ maps_stored_ |= instr->CheckGVNFlag(kChangesElementsKind); |
+ elements_stored_ |= instr->CheckGVNFlag(kChangesElementsKind); |
+ elements_stored_ |= instr->CheckGVNFlag(kChangesElementsPointer); |
} |
- // Improvements possible: |
- // - learn from HCheckMaps for field 0 |
- // - remove unobservable stores (write-after-write) |
} |
+ } |
- if (changed && FLAG_trace_load_elimination) { |
- table.Print(); |
+ // Apply these effects to the given load elimination table. |
+ void Apply(HLoadEliminationTable* table) { |
+ if (fields_stored_) { |
+ table->Kill(); |
+ return; |
+ } |
+ if (maps_stored_) { |
+ table->KillOffset(JSObject::kMapOffset); |
+ } |
+ if (elements_stored_) { |
+ table->KillOffset(JSObject::kElementsOffset); |
+ } |
+ |
+ // Kill non-agreeing fields for each store contained in these effects. |
+ for (int i = 0; i < stores_.length(); i++) { |
+ HStoreNamedField* s = stores_[i]; |
+ int field = table->FieldOf(s->access()); |
+ if (field >= 0) { |
+ table->KillFieldInternal(s->object()->ActualValue(), field, s->value()); |
+ } |
+ } |
+ } |
+ |
+ // Union these effects with the other effects. |
+ void Union(HLoadEliminationEffects* that, Zone* zone) { |
+ maps_stored_ |= that->maps_stored_; |
+ fields_stored_ |= that->fields_stored_; |
+ elements_stored_ |= that->elements_stored_; |
+ for (int i = 0; i < that->stores_.length(); i++) { |
+ stores_.Add(that->stores_[i], zone); |
} |
} |
-} |
+ private: |
+ Zone* zone_; |
+ bool maps_stored_ : 1; |
+ bool fields_stored_ : 1; |
+ bool elements_stored_ : 1; |
+ 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 v8::internal |