// 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