Index: src/hydrogen-load-elimination.cc |
diff --git a/src/hydrogen-load-elimination.cc b/src/hydrogen-load-elimination.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..6d01ae573be81d1dd2e86444cfa88af0a9091762 |
--- /dev/null |
+++ b/src/hydrogen-load-elimination.cc |
@@ -0,0 +1,327 @@ |
+// Copyright 2013 the V8 project authors. All rights reserved. |
+// Redistribution and use in source and binary forms, with or without |
+// modification, are permitted provided that the following conditions are |
+// met: |
+// |
+// * Redistributions of source code must retain the above copyright |
+// notice, this list of conditions and the following disclaimer. |
+// * Redistributions in binary form must reproduce the above |
+// copyright notice, this list of conditions and the following |
+// disclaimer in the documentation and/or other materials provided |
+// with the distribution. |
+// * Neither the name of Google Inc. nor the names of its |
+// contributors may be used to endorse or promote products derived |
+// from this software without specific prior written permission. |
+// |
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
+ |
+#include "hydrogen-alias-analysis.h" |
+#include "hydrogen-load-elimination.h" |
+#include "hydrogen-instructions.h" |
+ |
+namespace v8 { |
+namespace internal { |
+ |
+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_; |
+ HLoadNamedField* last_load_; |
+ HValue* last_value_; |
+ HFieldApproximation* next_; |
+}; |
+ |
+ |
+// 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 { |
+ public: |
+ HLoadEliminationTable(Zone* zone, HAliasAnalyzer* aliasing) |
+ : zone_(zone), fields_(kMaxTrackedFields, zone), aliasing_(aliasing) { } |
+ |
+ // 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) { |
+ 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_load_ = instr; |
+ approx->last_value_ = instr; |
+ return instr; |
+ } else { |
+ // Eliminate the load. Reuse previously stored value or load instruction. |
+ return approx->last_value_; |
+ } |
+ } |
+ |
+ // 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) { |
+ int field = FieldOf(instr->access()); |
+ if (field < 0) return instr; |
+ |
+ HValue* object = instr->object()->ActualValue(); |
+ HValue* value = instr->value(); |
+ |
+ // Kill non-equivalent may-alias entries. |
+ KillFieldInternal(object, field, value); |
+ if (instr->has_transition()) { |
+ // A transition store alters the map of the object. |
+ // TODO(titzer): remember the new map (a constant) for the object. |
+ KillFieldInternal(object, FieldOf(JSObject::kMapOffset), NULL); |
+ } |
+ 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_load_ = NULL; |
+ 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; |
+ } |
+ } |
+ |
+ // 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"); |
+ } |
+ } |
+ |
+ private: |
+ // 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_load_ = NULL; |
+ 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) 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]; |
+ ASSERT(approx != NULL); |
+ |
+ HFieldApproximation* prev = NULL; |
+ while (approx->next_ != NULL) { |
+ prev = approx; |
+ approx = approx->next_; |
+ } |
+ if (prev != NULL) prev->next_ = NULL; |
+ 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 in-object offset. |
+ int FieldOf(int offset) { |
+ if (offset >= kMaxTrackedFields * kPointerSize) return -1; |
+ ASSERT((offset % kPointerSize) == 0); // Assume aligned accesses. |
+ return offset / kPointerSize; |
+ } |
+ |
+ 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); |
+ } |
+} |
+ |
+ |
+// 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(); |
+ |
+ 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; |
+ break; |
+ } |
+ 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); |
+ } |
+ } |
+ // Improvements possible: |
+ // - learn from HCheckMaps for field 0 |
+ // - remove unobservable stores (write-after-write) |
+ } |
+ |
+ if (changed && FLAG_trace_load_elimination) { |
+ table.Print(); |
+ } |
+ } |
+} |
+ |
+ |
+} } // namespace v8::internal |