Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(154)

Unified Diff: runtime/vm/flow_graph_optimizer.cc

Issue 395943003: Support allocation sinking for compound objects. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: improve tests Created 6 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « runtime/vm/flow_graph_optimizer.h ('k') | runtime/vm/il_printer.cc » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: runtime/vm/flow_graph_optimizer.cc
diff --git a/runtime/vm/flow_graph_optimizer.cc b/runtime/vm/flow_graph_optimizer.cc
index fa5db4ded883864c43848e90aa74406a86314450..7daf3dc63df416cc1483fcf3f9cd5c9c2681aed9 100644
--- a/runtime/vm/flow_graph_optimizer.cc
+++ b/runtime/vm/flow_graph_optimizer.cc
@@ -5667,103 +5667,38 @@ void LICM::Optimize() {
}
-// Alias represents a family of locations. It is used to capture aliasing
-// between stores and loads. Store can alias another load or store if and only
-// if they have the same alias.
-class Alias : public ValueObject {
- public:
- Alias(const Alias& other) : ValueObject(), alias_(other.alias_) { }
-
- // All indexed load/stores alias each other.
- // TODO(vegorov): incorporate type of array into alias to disambiguate
- // different typed data and normal arrays.
- static Alias UnknownIndex(intptr_t id) {
- return Alias(kUnknownIndexAlias, id);
- }
-
- static Alias ConstantIndex(intptr_t id) {
- ASSERT(id != 0);
- return Alias(kConstantIndex, id);
- }
-
- // Field load/stores alias each other only when they access the same field.
- // AliasedSet assigns ids to a combination of instance and field during
- // the optimization phase.
- static Alias Field(intptr_t id) {
- ASSERT(id != 0);
- return Alias(kFieldAlias, id);
- }
-
- // VMField load/stores alias each other when field offset matches.
- // TODO(vegorov) storing a context variable does not alias loading array
- // length.
- static Alias VMField(intptr_t id) {
- ASSERT(id != 0);
- return Alias(kVMFieldAlias, id);
- }
-
- // Current context load/stores alias each other.
- static Alias CurrentContext() {
- return Alias(kCurrentContextAlias, 0);
- }
-
- // Operation does not alias anything.
- static Alias None() {
- return Alias(kNoneAlias);
- }
-
- bool IsNone() const {
- return alias_ == kNoneAlias;
- }
-
- // Convert this alias to a positive array index.
- intptr_t ToIndex() const {
- ASSERT(!IsNone());
- return alias_;
- }
-
- private:
- enum {
- // Number of bits required to encode Kind value.
- // The payload occupies the rest of the bits, but leaves the MSB (sign bit)
- // empty so that the resulting encoded value is always a positive integer.
- kBitsForKind = 3,
- kBitsForPayload = kWordSize * kBitsPerByte - kBitsForKind - 1,
- };
-
- enum Kind {
- kNoneAlias = -1,
- kCurrentContextAlias = 0,
- kUnknownIndexAlias = 1,
- kFieldAlias = 2,
- kVMFieldAlias = 3,
- kConstantIndex = 4,
- kNumKinds = kConstantIndex + 1
- };
- COMPILE_ASSERT(kNumKinds < ((1 << kBitsForKind) - 1));
-
- explicit Alias(intptr_t alias) : alias_(alias) { }
-
- Alias(Kind kind, uword payload)
- : alias_(KindField::encode(kind) | PayloadField::encode(payload)) { }
-
- uword payload() const {
- return PayloadField::decode(alias_);
- }
-
- Kind kind() const {
- return IsNone() ? kNoneAlias : KindField::decode(alias_);
- }
-
- typedef BitField<Kind, 0, kBitsForKind> KindField;
- typedef BitField<uword, kBitsForKind, kBitsForPayload> PayloadField;
-
- const intptr_t alias_;
-};
-
-
// Place describes an abstract location (e.g. field) that IR can load
// from or store to.
+//
+// Places are also used to describe wild-card locations also known as aliases,
+// that essentially represent sets of places that alias each other. Places A
+// and B are said to alias each other if store into A can affect load from B.
+//
+// We distinguish the following aliases:
+//
+// - for fields
+// - *.f, *.@offs - field inside some object;
+// - X.f, X.@offs - field inside an allocated object X;
+// - for indexed accesses
+// - *[*] - non-constant index inside some object;
+// - *[C] - constant index inside some object;
+// - X[*] - non-constant index inside an allocated object X;
+// - X[C] - constant index inside an allocated object X.
+//
+// Separating allocations from other objects improves precision of the
+// load forwarding pass because of the following two properties:
+//
+// - if X can be proven to have no aliases itself (i.e. there is no other SSA
+// variable that points to X) then no place inside X can be aliased with any
+// wildcard dependent place (*.f, *.@offs, *[*], *[C]);
+// - given allocations X and Y no place inside X can be aliased with any place
+// inside Y even if any of them or both escape.
+//
+// It important to realize that single place can belong to multiple aliases.
+// For example place X.f with aliased allocation X belongs both to X.f and *.f
+// aliases. Likewise X[C] with non-aliased allocation X belongs to X[C] and X[*]
+// aliases.
+//
class Place : public ValueObject {
public:
enum Kind {
@@ -5778,9 +5713,12 @@ class Place : public ValueObject {
// being accessed and offset to the field.
kVMField,
- // Indexed location.
+ // Indexed location with a non-constant index.
kIndexed,
+ // Indexed location with a constant index.
+ kConstantIndexed,
+
// Current context.
kContext
};
@@ -5852,21 +5790,19 @@ class Place : public ValueObject {
case Instruction::kLoadIndexed: {
LoadIndexedInstr* load_indexed = instr->AsLoadIndexed();
- kind_ = kIndexed;
representation_ = load_indexed->representation();
instance_ = load_indexed->array()->definition()->OriginalDefinition();
- index_ = load_indexed->index()->definition();
+ SetIndex(load_indexed->index()->definition());
*is_load = true;
break;
}
case Instruction::kStoreIndexed: {
StoreIndexedInstr* store_indexed = instr->AsStoreIndexed();
- kind_ = kIndexed;
representation_ = store_indexed->
RequiredInputRepresentation(StoreIndexedInstr::kValuePos);
instance_ = store_indexed->array()->definition()->OriginalDefinition();
- index_ = store_indexed->index()->definition();
+ SetIndex(store_indexed->index()->definition());
*is_store = true;
break;
}
@@ -5891,20 +5827,75 @@ class Place : public ValueObject {
}
}
+ // Create object representing *[*] alias.
+ static Place* CreateAnyInstanceAnyIndexAlias(Isolate* isolate,
+ intptr_t id) {
+ return Wrap(isolate, Place(kIndexed, NULL, 0), id);
+ }
+
+ // Return least generic alias for this place. Given that aliases are
+ // essentially sets of places we define least generic alias as a smallest
+ // alias that contains this place.
+ //
+ // We obtain such alias by a simple transformation:
+ //
+ // - for places that depend on an instance X.f, X.@offs, X[i], X[C]
+ // we drop X if X is not an allocation because in this case X does not
+ // posess an identity obtaining aliases *.f, *.@offs, *[i] and *[C]
+ // respectively;
+ // - for non-constant indexed places X[i] we drop information about the
+ // index obtaining alias X[*].
+ //
+ Place ToAlias() const {
+ return Place(
+ kind_,
+ (DependsOnInstance() && IsAllocation(instance())) ? instance() : NULL,
+ (kind() == kIndexed) ? 0 : raw_selector_);
+ }
+
+ bool DependsOnInstance() const {
+ switch (kind()) {
+ case kField:
+ case kVMField:
+ case kIndexed:
+ case kConstantIndexed:
+ return true;
+
+ case kContext:
+ case kNone:
+ return false;
+ }
+
+ UNREACHABLE();
+ return false;
+ }
+
+ // Given instance dependent alias X.f, X.@offs, X[C], X[*] return
+ // wild-card dependent alias *.f, *.@offs, *[C] or *[*] respectively.
+ Place CopyWithoutInstance() const {
+ ASSERT(DependsOnInstance());
+ return Place(kind_, NULL, raw_selector_);
+ }
+
+ // Given alias X[C] or *[C] return X[*] and *[*] respectively.
+ Place CopyWithoutIndex() const {
+ ASSERT(kind_ == kConstantIndexed);
+ return Place(kIndexed, instance_, 0);
+ }
+
intptr_t id() const { return id_; }
- void set_id(intptr_t id) { id_ = id; }
Kind kind() const { return kind_; }
Representation representation() const { return representation_; }
Definition* instance() const {
- ASSERT((kind_ == kField) || (kind_ == kVMField) || (kind_ == kIndexed));
+ ASSERT(DependsOnInstance());
return instance_;
}
void set_instance(Definition* def) {
- ASSERT((kind_ == kField) || (kind_ == kVMField) || (kind_ == kIndexed));
+ ASSERT(DependsOnInstance());
instance_ = def->OriginalDefinition();
}
@@ -5923,6 +5914,20 @@ class Place : public ValueObject {
return index_;
}
+ intptr_t index_constant() const {
+ ASSERT(kind_ == kConstantIndexed);
+ return index_constant_;
+ }
+
+ static const char* DefinitionName(Definition* def) {
+ if (def == NULL) {
+ return "*";
+ } else {
+ return Isolate::Current()->current_zone()->PrintToString(
+ "v%" Pd, def->ssa_temp_index());
+ }
+ }
+
const char* ToCString() const {
switch (kind_) {
case kNone:
@@ -5930,25 +5935,32 @@ class Place : public ValueObject {
case kField: {
const char* field_name = String::Handle(field().name()).ToCString();
- if (instance() == NULL) {
- return field_name;
+ if (field().is_static()) {
+ return Isolate::Current()->current_zone()->PrintToString(
+ "<%s>", field_name);
+ } else {
+ return Isolate::Current()->current_zone()->PrintToString(
+ "<%s.%s>", DefinitionName(instance()), field_name);
}
- return Isolate::Current()->current_zone()->PrintToString(
- "<v%" Pd ".%s>", instance()->ssa_temp_index(), field_name);
}
- case kVMField: {
+ case kVMField:
return Isolate::Current()->current_zone()->PrintToString(
- "<v%" Pd "@%" Pd ">",
- instance()->ssa_temp_index(), offset_in_bytes());
- }
+ "<%s.@%" Pd ">",
+ DefinitionName(instance()),
+ offset_in_bytes());
- case kIndexed: {
+ case kIndexed:
return Isolate::Current()->current_zone()->PrintToString(
- "<v%" Pd "[v%" Pd "]>",
- instance()->ssa_temp_index(),
- index()->ssa_temp_index());
- }
+ "<%s[%s]>",
+ DefinitionName(instance()),
+ DefinitionName(index()));
+
+ case kConstantIndexed:
+ return Isolate::Current()->current_zone()->PrintToString(
+ "<%s[%" Pd "]>",
+ DefinitionName(instance()),
+ index_constant());
case kContext:
return "<context>";
@@ -5966,18 +5978,35 @@ class Place : public ValueObject {
representation_ * 15 + FieldHashcode();
}
- bool Equals(Place* other) const {
+ bool Equals(const Place* other) const {
return (kind_ == other->kind_) &&
(representation_ == other->representation_) &&
(instance_ == other->instance_) &&
SameField(other);
}
- // Create a zone allocated copy of this place.
- static Place* Wrap(Isolate* isolate, const Place& place);
+ // Create a zone allocated copy of this place and assign given id to it.
+ static Place* Wrap(Isolate* isolate, const Place& place, intptr_t id);
+
+ static bool IsAllocation(Definition* defn) {
+ // TODO(vegorov): add CreateContext to this list.
+ return (defn != NULL) &&
+ (defn->IsAllocateObject() ||
+ defn->IsCreateArray() ||
+ (defn->IsStaticCall() &&
+ defn->AsStaticCall()->IsRecognizedFactory()));
+ }
private:
- bool SameField(Place* other) const {
+ Place(Kind kind, Definition* instance, intptr_t selector)
+ : kind_(kind),
+ representation_(kNoRepresentation),
+ instance_(instance),
+ raw_selector_(selector),
+ id_(0) {
+ }
+
+ bool SameField(const Place* other) const {
return (kind_ == kField) ? (field().raw() == other->field().raw())
: (offset_in_bytes_ == other->offset_in_bytes_);
}
@@ -5987,6 +6016,17 @@ class Place : public ValueObject {
: offset_in_bytes_;
}
+ void SetIndex(Definition* index) {
+ ConstantInstr* index_constant = index->AsConstant();
+ if ((index_constant != NULL) && index_constant->value().IsSmi()) {
+ kind_ = kConstantIndexed;
+ index_constant_ = Smi::Cast(index_constant->value()).Value();
+ } else {
+ kind_ = kIndexed;
+ index_ = index;
+ }
+ }
+
Kind kind_;
Representation representation_;
Definition* instance_;
@@ -5994,6 +6034,7 @@ class Place : public ValueObject {
intptr_t raw_selector_;
const Field* field_;
intptr_t offset_in_bytes_;
+ intptr_t index_constant_;
Definition* index_;
};
@@ -6012,8 +6053,10 @@ class ZonePlace : public ZoneAllocated {
};
-Place* Place::Wrap(Isolate* isolate, const Place& place) {
- return (new(isolate) ZonePlace(place))->place();
+Place* Place::Wrap(Isolate* isolate, const Place& place, intptr_t id) {
+ Place* wrapped = (new(isolate) ZonePlace(place))->place();
+ wrapped->id_ = id;
+ return wrapped;
}
@@ -6073,141 +6116,38 @@ class AliasedSet : public ZoneAllocated {
: isolate_(isolate),
places_(*places),
phi_moves_(phi_moves),
- sets_(),
- aliased_by_effects_(new(isolate) BitVector(places->length())),
- max_field_id_(0),
- field_ids_(),
- max_index_id_(0),
- index_ids_(),
- max_unknown_index_id_(0),
- unknown_index_ids_(),
- max_vm_field_id_(0),
- vm_field_ids_() { }
-
- Alias ComputeAlias(Place* place) {
- switch (place->kind()) {
- case Place::kIndexed:
- if (place->index()->IsConstant()) {
- const Object& index = place->index()->AsConstant()->value();
- if (index.IsSmi()) {
- return Alias::ConstantIndex(
- GetInstanceIndexId(place->instance(),
- Smi::Cast(index).Value()));
- }
- }
- return Alias::UnknownIndex(GetUnknownIndexId(place->instance()));
- case Place::kField:
- return Alias::Field(
- GetInstanceFieldId(place->instance(), place->field()));
- case Place::kVMField:
- return Alias::VMField(
- GetInstanceVMFieldId(place->instance(), place->offset_in_bytes()));
- case Place::kContext:
- return Alias::CurrentContext();
- case Place::kNone:
- UNREACHABLE();
- }
-
- UNREACHABLE();
- return Alias::None();
- }
-
- Alias ComputeAliasForStore(Instruction* instr) {
- StoreIndexedInstr* store_indexed = instr->AsStoreIndexed();
- if (store_indexed != NULL) {
- Definition* instance = store_indexed->array()->definition();
- if (store_indexed->index()->definition()->IsConstant()) {
- const Object& index =
- store_indexed->index()->definition()->AsConstant()->value();
- if (index.IsSmi()) {
- return Alias::ConstantIndex(
- GetInstanceIndexId(instance, Smi::Cast(index).Value()));
- }
- }
- return Alias::UnknownIndex(GetUnknownIndexId(instance));
- }
-
- StoreInstanceFieldInstr* store_instance_field =
- instr->AsStoreInstanceField();
- if (store_instance_field != NULL) {
- Definition* instance = store_instance_field->instance()->definition();
- if (!store_instance_field->field().IsNull()) {
- return Alias::Field(
- GetInstanceFieldId(instance, store_instance_field->field()));
- }
- return Alias::VMField(
- GetInstanceVMFieldId(instance,
- store_instance_field->offset_in_bytes()));
- }
-
- if (instr->IsStoreContext()) {
- return Alias::CurrentContext();
- }
-
- StoreStaticFieldInstr* store_static_field = instr->AsStoreStaticField();
- if (store_static_field != NULL) {
- return Alias::Field(GetStaticFieldId(store_static_field->field()));
+ aliases_(5),
+ aliases_map_(),
+ representatives_(),
+ killed_(),
+ aliased_by_effects_(new(isolate) BitVector(places->length())) {
+ InsertAlias(Place::CreateAnyInstanceAnyIndexAlias(isolate_,
+ kAnyInstanceAnyIndexAlias));
+ for (intptr_t i = 0; i < places_.length(); i++) {
+ AddRepresentative(places_[i]);
}
-
- return Alias::None();
+ ComputeKillSets();
}
- BitVector* Get(const Alias alias) {
- const intptr_t idx = alias.ToIndex();
- BitVector* ret = (idx < sets_.length()) ? sets_[idx] : NULL;
- return ret;
+ intptr_t LookupAliasId(const Place& alias) {
+ const Place* result = aliases_map_.Lookup(&alias);
+ return (result != NULL) ? result->id() : kNoAlias;
}
- void AddRepresentative(Place* place) {
- if (!place->IsFinalField()) {
- AddIdForAlias(ComputeAlias(place), place->id());
- if (!IsIndependentFromEffects(place)) {
- aliased_by_effects_->Add(place->id());
+ bool IsStore(Instruction* instr, BitVector** killed) {
+ bool is_load = false, is_store = false;
+ Place place(instr, &is_load, &is_store);
+ if (is_store && (place.kind() != Place::kNone)) {
+ const intptr_t alias_id = LookupAliasId(place.ToAlias());
+ if (alias_id != kNoAlias) {
+ *killed = GetKilledSet(alias_id);
}
}
+ return is_store;
}
- void EnsureAliasingForUnknownIndices() {
- // Ids start at 1 because the hash-map uses 0 for element not found.
- for (intptr_t unknown_index_id = 1;
- unknown_index_id <= max_unknown_index_id_;
- unknown_index_id++) {
- BitVector* unknown_index = Get(Alias::UnknownIndex(unknown_index_id));
- if (unknown_index == NULL) {
- return;
- }
-
- // Constant indexes alias all non-constant indexes.
- // Non-constant indexes alias all constant indexes.
- // First update alias set for const-indices, then
- // update set for all indices. Ids start at 1.
- for (intptr_t id = 1; id <= max_index_id_; id++) {
- BitVector* const_indexes = Get(Alias::ConstantIndex(id));
- if (const_indexes != NULL) {
- const_indexes->AddAll(unknown_index);
- }
- }
-
- for (intptr_t id = 1; id <= max_index_id_; id++) {
- BitVector* const_indexes = Get(Alias::ConstantIndex(id));
- if (const_indexes != NULL) {
- unknown_index->AddAll(const_indexes);
- }
- }
- }
- }
-
- void AddIdForAlias(const Alias alias, intptr_t place_id) {
- const intptr_t idx = alias.ToIndex();
- while (sets_.length() <= idx) {
- sets_.Add(NULL);
- }
-
- if (sets_[idx] == NULL) {
- sets_[idx] = new(isolate_) BitVector(max_place_id());
- }
-
- sets_[idx]->Add(place_id);
+ BitVector* GetKilledSet(intptr_t alias) {
+ return (alias < killed_.length()) ? killed_[alias] : NULL;
}
intptr_t max_place_id() const { return places().length(); }
@@ -6234,158 +6174,209 @@ class AliasedSet : public ZoneAllocated {
const PhiPlaceMoves* phi_moves() const { return phi_moves_; }
- // Returns true if the result of an allocation instruction can be aliased by
- // some other SSA variable and false otherwise. Currently simply checks if
- // this value is stored in a field, escapes to another function or
- // participates in a phi.
- static bool CanBeAliased(Definition* alloc) {
- ASSERT(alloc->IsAllocateObject() ||
- alloc->IsCreateArray() ||
- (alloc->IsStaticCall() &&
- alloc->AsStaticCall()->IsRecognizedFactory()));
- if (alloc->Identity() == kIdentityUnknown) {
- bool escapes = false;
- for (Value* use = alloc->input_use_list();
- use != NULL;
- use = use->next_use()) {
- Instruction* instr = use->instruction();
- if (instr->IsPushArgument() ||
- (instr->IsStoreInstanceField()
- && (use->use_index() != StoreInstanceFieldInstr::kInstancePos)) ||
- (instr->IsStoreIndexed()
- && (use->use_index() == StoreIndexedInstr::kValuePos)) ||
- instr->IsStoreStaticField() ||
- instr->IsPhi() ||
- instr->IsAssertAssignable() ||
- instr->IsRedefinition()) {
- escapes = true;
- break;
- }
- }
-
- alloc->SetIdentity(escapes ? kIdentityAliased : kIdentityNotAliased);
+ void RollbackAliasedIdentites() {
+ for (intptr_t i = 0; i < identity_rollback_.length(); ++i) {
+ identity_rollback_[i]->SetIdentity(AliasIdentity::Unknown());
}
-
- return alloc->Identity() != kIdentityNotAliased;
}
- private:
- // Get id assigned to the given field. Assign a new id if the field is seen
- // for the first time.
- intptr_t GetFieldId(intptr_t instance_id, const Field& field) {
- intptr_t id = field_ids_.Lookup(FieldIdPair::Key(instance_id, &field));
- if (id == 0) {
- id = ++max_field_id_;
- field_ids_.Insert(FieldIdPair(FieldIdPair::Key(instance_id, &field), id));
+ // Returns false if the result of an allocation instruction can't be aliased
+ // by another SSA variable and true otherwise.
+ bool CanBeAliased(Definition* alloc) {
+ if (!Place::IsAllocation(alloc)) {
+ return true;
}
- return id;
- }
- intptr_t GetIndexId(intptr_t instance_id, intptr_t index) {
- intptr_t id = index_ids_.Lookup(
- ConstantIndexIdPair::Key(instance_id, index));
- if (id == 0) {
- // Zero is used to indicate element not found. The first id is one.
- id = ++max_index_id_;
- index_ids_.Insert(ConstantIndexIdPair(
- ConstantIndexIdPair::Key(instance_id, index), id));
+ if (alloc->Identity().IsUnknown()) {
+ ComputeAliasing(alloc);
}
- return id;
+
+ return !alloc->Identity().IsNotAliased();
}
+ private:
enum {
- kAnyInstance = -1
+ kNoAlias = 0,
+
+ // Artificial alias that is used to collect all representatives of the
+ // *[C], X[C] aliases for arbitrary C.
+ kAnyConstantIndexedAlias = 1,
+
+ // Artificial alias that is used to collect all representatives of
+ // *[C] alias for arbitrary C.
+ kUnknownInstanceConstantIndexedAlias = 2,
+
+ // Artificial alias that is used to collect all representatives of
+ // X[*] alias for all X.
+ kAnyAllocationIndexedAlias = 3,
+
+ // *[*] alias.
+ kAnyInstanceAnyIndexAlias = 4
};
- // Get or create an identifier for an instance field belonging to the
- // given instance.
- // The space of identifiers assigned to instance fields is split into
- // parts based on the instance that contains the field.
- // If compiler can prove that instance has a single SSA name in the compiled
- // function then we use that SSA name to distinguish fields of this object
- // from the same fields in other objects.
- // If multiple SSA names can point to the same object then we use
- // kAnyInstance instead of a concrete SSA name.
- intptr_t GetInstanceFieldId(Definition* defn, const Field& field) {
- ASSERT(field.is_static() == (defn == NULL));
+ // Compute least generic alias for the place and assign alias id to it.
+ void AddRepresentative(Place* place) {
+ if (!place->IsFinalField()) {
+ const Place* alias = CanonicalizeAlias(place->ToAlias());
+ EnsureSet(&representatives_, alias->id())->Add(place->id());
+
+ // Update cumulative representative sets that are used during
+ // killed sets computation.
+ if (alias->kind() == Place::kConstantIndexed) {
+ if (CanBeAliased(alias->instance())) {
+ EnsureSet(&representatives_, kAnyConstantIndexedAlias)->
+ Add(place->id());
+ }
- intptr_t instance_id = kAnyInstance;
+ if (alias->instance() == NULL) {
+ EnsureSet(&representatives_, kUnknownInstanceConstantIndexedAlias)->
+ Add(place->id());
+ }
+ } else if ((alias->kind() == Place::kIndexed) &&
+ CanBeAliased(place->instance())) {
+ EnsureSet(&representatives_, kAnyAllocationIndexedAlias)->
+ Add(place->id());
+ }
- if (defn != NULL) {
- AllocateObjectInstr* alloc = defn->AsAllocateObject();
- if ((alloc != NULL) && !CanBeAliased(alloc)) {
- instance_id = alloc->ssa_temp_index();
- ASSERT(instance_id != kAnyInstance);
+ if (!IsIndependentFromEffects(place)) {
+ aliased_by_effects_->Add(place->id());
}
}
-
- return GetFieldId(instance_id, field);
}
- intptr_t GetInstanceVMFieldId(Definition* defn, intptr_t offset) {
- intptr_t instance_id = kAnyInstance;
-
- ASSERT(defn != NULL);
- if ((defn->IsAllocateObject() ||
- defn->IsCreateArray() ||
- (defn->IsStaticCall() &&
- defn->AsStaticCall()->IsRecognizedFactory())) &&
- !CanBeAliased(defn)) {
- instance_id = defn->ssa_temp_index();
- ASSERT(instance_id != kAnyInstance);
+ void ComputeKillSets() {
+ for (intptr_t i = 0; i < aliases_.length(); ++i) {
+ const Place* alias = aliases_[i];
+ // Add all representatives to the kill set.
+ AddAllRepresentatives(alias->id(), alias->id());
+ ComputeKillSet(alias);
}
- intptr_t id = vm_field_ids_.Lookup(VMFieldIdPair::Key(instance_id, offset));
- if (id == 0) {
- id = ++max_vm_field_id_;
- vm_field_ids_.Insert(
- VMFieldIdPair(VMFieldIdPair::Key(instance_id, offset), id));
+ if (FLAG_trace_load_optimization) {
+ OS::Print("Aliases KILL sets:\n");
+ for (intptr_t i = 0; i < aliases_.length(); ++i) {
+ const Place* alias = aliases_[i];
+ BitVector* kill = GetKilledSet(alias->id());
+
+ OS::Print("%s: ", alias->ToCString());
+ if (kill != NULL) {
+ PrintSet(kill);
+ }
+ OS::Print("\n");
+ }
}
- return id;
}
- intptr_t GetInstanceIndexId(Definition* defn, intptr_t index) {
- intptr_t instance_id = kAnyInstance;
+ void InsertAlias(const Place* alias) {
+ aliases_map_.Insert(alias);
+ aliases_.Add(alias);
+ }
- ASSERT(defn != NULL);
- if ((defn->IsCreateArray() ||
- (defn->IsStaticCall() &&
- defn->AsStaticCall()->IsRecognizedFactory())) &&
- !CanBeAliased(defn)) {
- instance_id = defn->ssa_temp_index();
- ASSERT(instance_id != kAnyInstance);
+ const Place* CanonicalizeAlias(const Place& alias) {
+ const Place* canonical = aliases_map_.Lookup(&alias);
+ if (canonical == NULL) {
+ canonical = Place::Wrap(isolate_,
+ alias,
+ kAnyInstanceAnyIndexAlias + aliases_.length());
+ InsertAlias(canonical);
}
+ return canonical;
+ }
- return GetIndexId(instance_id, index);
+ BitVector* GetRepresentativesSet(intptr_t alias) {
+ return (alias < representatives_.length()) ? representatives_[alias] : NULL;
}
- intptr_t GetUnknownIndexId(Definition* defn) {
- intptr_t instance_id = kAnyInstance;
+ BitVector* EnsureSet(GrowableArray<BitVector*>* sets,
+ intptr_t alias) {
+ while (sets->length() <= alias) {
+ sets->Add(NULL);
+ }
- ASSERT(defn != NULL);
- if ((defn->IsCreateArray() ||
- (defn->IsStaticCall() &&
- defn->AsStaticCall()->IsRecognizedFactory())) &&
- !CanBeAliased(defn)) {
- instance_id = defn->ssa_temp_index();
- ASSERT(instance_id != kAnyInstance);
+ BitVector* set = (*sets)[alias];
+ if (set == NULL) {
+ (*sets)[alias] = set = new(isolate_) BitVector(max_place_id());
}
+ return set;
+ }
+
+ void AddAllRepresentatives(const Place* to, intptr_t from) {
+ AddAllRepresentatives(to->id(), from);
+ }
- intptr_t id = unknown_index_ids_.Lookup(
- UnknownIndexIdPair::Key(instance_id));
- if (id == 0) {
- // Zero is used to indicate element not found. The first id is one.
- id = ++max_unknown_index_id_;
- unknown_index_ids_.Insert(
- UnknownIndexIdPair(UnknownIndexIdPair::Key(instance_id), id));
+ void AddAllRepresentatives(intptr_t to, intptr_t from) {
+ BitVector* from_set = GetRepresentativesSet(from);
+ if (from_set != NULL) {
+ EnsureSet(&killed_, to)->AddAll(from_set);
}
- return id;
}
- // Get or create an identifier for a static field.
- intptr_t GetStaticFieldId(const Field& field) {
- ASSERT(field.is_static());
- return GetFieldId(kAnyInstance, field);
+ void CrossAlias(const Place* to, const Place& from) {
+ const intptr_t from_id = LookupAliasId(from);
+ if (from_id == kNoAlias) {
+ return;
+ }
+ CrossAlias(to, from_id);
+ }
+
+ void CrossAlias(const Place* to, intptr_t from) {
+ AddAllRepresentatives(to->id(), from);
+ AddAllRepresentatives(from, to->id());
+ }
+
+ // When computing kill sets we let less generic alias insert its
+ // representatives into more generic alias'es kill set. For example
+ // when visiting alias X[*] instead of searching for all aliases X[C]
+ // and inserting their representatives into kill set for X[*] we update
+ // kill set for X[*] each time we visit new X[C] for some C.
+ // There is an exception however: if both aliases are parametric like *[C]
+ // and X[*] which cross alias when X is an aliased allocation then we use
+ // artificial aliases that contain all possible representatives for the given
+ // alias for any value of the parameter to compute resulting kill set.
+ void ComputeKillSet(const Place* alias) {
+ switch (alias->kind()) {
+ case Place::kIndexed: // Either *[*] or X[*] alias.
+ if (alias->instance() == NULL) {
+ // *[*] aliases with X[*], X[C], *[C].
+ AddAllRepresentatives(alias, kAnyConstantIndexedAlias);
+ AddAllRepresentatives(alias, kAnyAllocationIndexedAlias);
+ } else if (CanBeAliased(alias->instance())) {
+ // X[*] aliases with X[C].
+ // If X can be aliased then X[*] also aliases with *[C], *[*].
+ CrossAlias(alias, kAnyInstanceAnyIndexAlias);
+ AddAllRepresentatives(alias, kUnknownInstanceConstantIndexedAlias);
+ }
+ break;
+
+ case Place::kConstantIndexed: // Either X[C] or *[C] alias.
+ if (alias->instance() == NULL) {
+ // *[C] aliases with X[C], X[*], *[*].
+ AddAllRepresentatives(alias, kAnyAllocationIndexedAlias);
+ CrossAlias(alias, kAnyInstanceAnyIndexAlias);
+ } else {
+ // X[C] aliases with X[*].
+ // If X can be aliased then X[C] also aliases with *[C], *[*].
+ CrossAlias(alias, alias->CopyWithoutIndex());
+ if (CanBeAliased(alias->instance())) {
+ CrossAlias(alias, alias->CopyWithoutInstance());
+ CrossAlias(alias, kAnyInstanceAnyIndexAlias);
+ }
+ }
+ break;
+
+ case Place::kField:
+ case Place::kVMField:
+ if (CanBeAliased(alias->instance())) {
+ // X.f or X.@offs alias with *.f and *.@offs respectively.
+ CrossAlias(alias, alias->CopyWithoutInstance());
+ }
+
+ case Place::kContext:
+ return;
+
+ case Place::kNone:
+ UNREACHABLE();
+ }
}
// Returns true if the given load is unaffected by external side-effects.
@@ -6408,154 +6399,127 @@ class AliasedSet : public ZoneAllocated {
return true;
}
- if (((place->kind() == Place::kField) ||
+ return ((place->kind() == Place::kField) ||
(place->kind() == Place::kVMField)) &&
- (place->instance() != NULL)) {
- AllocateObjectInstr* alloc = place->instance()->AsAllocateObject();
- return (alloc != NULL) && !CanBeAliased(alloc);
- }
-
- return false;
+ !CanBeAliased(place->instance());
}
- class FieldIdPair {
- public:
- struct Key {
- Key(intptr_t instance_id, const Field* field)
- : instance_id_(instance_id), field_(field) { }
-
- intptr_t instance_id_;
- const Field* field_;
- };
-
- typedef intptr_t Value;
- typedef FieldIdPair Pair;
-
- FieldIdPair(Key key, Value value) : key_(key), value_(value) { }
-
- static Key KeyOf(Pair kv) {
- return kv.key_;
- }
-
- static Value ValueOf(Pair kv) {
- return kv.value_;
- }
-
- static intptr_t Hashcode(Key key) {
- return String::Handle(key.field_->name()).Hash();
- }
-
- static inline bool IsKeyEqual(Pair kv, Key key) {
- return (KeyOf(kv).field_->raw() == key.field_->raw()) &&
- (KeyOf(kv).instance_id_ == key.instance_id_);
- }
-
- private:
- Key key_;
- Value value_;
- };
-
- class ConstantIndexIdPair {
- public:
- struct Key {
- Key(intptr_t instance_id, intptr_t index)
- : instance_id_(instance_id), index_(index) { }
-
- intptr_t instance_id_;
- intptr_t index_;
- };
- typedef intptr_t Value;
- typedef ConstantIndexIdPair Pair;
-
- ConstantIndexIdPair(Key key, Value value) : key_(key), value_(value) { }
-
- static Key KeyOf(ConstantIndexIdPair kv) {
- return kv.key_;
- }
-
- static Value ValueOf(ConstantIndexIdPair kv) {
- return kv.value_;
- }
+ // Returns true if there are direct loads from the given place.
+ bool HasLoadsFromPlace(Definition* defn, const Place* place) {
+ ASSERT((place->kind() == Place::kField) ||
+ (place->kind() == Place::kVMField));
+ ASSERT(place->instance() == defn);
- static intptr_t Hashcode(Key key) {
- return (key.instance_id_ + 1) * 1024 + key.index_;
- }
+ for (Value* use = defn->input_use_list();
+ use != NULL;
+ use = use->next_use()) {
+ bool is_load = false, is_store;
+ Place load_place(use->instruction(), &is_load, &is_store);
- static inline bool IsKeyEqual(ConstantIndexIdPair kv, Key key) {
- return (KeyOf(kv).index_ == key.index_)
- && (KeyOf(kv).instance_id_ == key.instance_id_);
+ if (is_load && load_place.Equals(place)) {
+ return true;
+ }
}
- private:
- Key key_;
- Value value_;
- };
-
- class UnknownIndexIdPair {
- public:
- typedef intptr_t Key;
- typedef intptr_t Value;
- typedef UnknownIndexIdPair Pair;
-
- UnknownIndexIdPair(Key key, Value value) : key_(key), value_(value) { }
+ return false;
+ }
- static Key KeyOf(UnknownIndexIdPair kv) {
- return kv.key_;
- }
+ // Check if any use of the definition can create an alias.
+ // Can add more objects into aliasing_worklist_.
+ bool AnyUseCreatesAlias(Definition* defn) {
+ for (Value* use = defn->input_use_list();
+ use != NULL;
+ use = use->next_use()) {
+ Instruction* instr = use->instruction();
+ if (instr->IsPushArgument() ||
+ (instr->IsStoreIndexed()
+ && (use->use_index() == StoreIndexedInstr::kValuePos)) ||
+ instr->IsStoreStaticField() ||
+ instr->IsPhi() ||
+ instr->IsAssertAssignable() ||
+ instr->IsRedefinition()) {
+ return true;
+ } else if ((instr->IsStoreInstanceField()
+ && (use->use_index() != StoreInstanceFieldInstr::kInstancePos))) {
+ ASSERT(use->use_index() == StoreInstanceFieldInstr::kValuePos);
+ // If we store this value into an object that is not aliased itself
+ // and we never load again then the store does not create an alias.
+ StoreInstanceFieldInstr* store = instr->AsStoreInstanceField();
+ Definition* instance = store->instance()->definition();
+ if (instance->IsAllocateObject() && !instance->Identity().IsAliased()) {
+ bool is_load, is_store;
+ Place store_place(instr, &is_load, &is_store);
+
+ if (!HasLoadsFromPlace(instance, &store_place)) {
+ // No loads found that match this store. If it is yet unknown if
+ // the object is not aliased then optimistically assume this but
+ // add it to the worklist to check its uses transitively.
+ if (instance->Identity().IsUnknown()) {
+ instance->SetIdentity(AliasIdentity::NotAliased());
+ aliasing_worklist_.Add(instance);
+ }
+ continue;
+ }
+ }
- static Value ValueOf(UnknownIndexIdPair kv) {
- return kv.value_;
+ return true;
+ }
}
+ return false;
+ }
- static intptr_t Hashcode(Key key) {
- return key + 1;
+ // Mark any value stored into the given object as potentially aliased.
+ void MarkStoredValuesEscaping(Definition* defn) {
+ if (!defn->IsAllocateObject()) {
+ return;
}
- static inline bool IsKeyEqual(UnknownIndexIdPair kv, Key key) {
- return KeyOf(kv) == key;
+ // Find all stores into this object.
+ for (Value* use = defn->input_use_list();
+ use != NULL;
+ use = use->next_use()) {
+ if ((use->use_index() == StoreInstanceFieldInstr::kInstancePos) &&
+ use->instruction()->IsStoreInstanceField()) {
+ StoreInstanceFieldInstr* store =
+ use->instruction()->AsStoreInstanceField();
+ Definition* value = store->value()->definition();
+ if (value->Identity().IsNotAliased()) {
+ value->SetIdentity(AliasIdentity::Aliased());
+ identity_rollback_.Add(value);
+
+ // Add to worklist to propagate the mark transitively.
+ aliasing_worklist_.Add(value);
+ }
+ }
}
+ }
- private:
- Key key_;
- Value value_;
- };
-
- class VMFieldIdPair {
- public:
- struct Key {
- Key(intptr_t instance_id, intptr_t offset)
- : instance_id_(instance_id), offset_(offset) { }
-
- intptr_t instance_id_;
- intptr_t offset_;
- };
-
- typedef intptr_t Value;
- typedef VMFieldIdPair Pair;
+ // Determine if the given definition can't be aliased.
+ void ComputeAliasing(Definition* alloc) {
+ ASSERT(alloc->Identity().IsUnknown());
+ ASSERT(aliasing_worklist_.is_empty());
- VMFieldIdPair(Key key, Value value) : key_(key), value_(value) { }
+ alloc->SetIdentity(AliasIdentity::NotAliased());
+ aliasing_worklist_.Add(alloc);
- static Key KeyOf(Pair kv) {
- return kv.key_;
- }
+ while (!aliasing_worklist_.is_empty()) {
+ Definition* defn = aliasing_worklist_.RemoveLast();
- static Value ValueOf(Pair kv) {
- return kv.value_;
- }
-
- static intptr_t Hashcode(Key key) {
- return (key.instance_id_ + 1) * 1024 + key.offset_;
- }
+ // If the definition in the worklist was optimistically marked as
+ // not-aliased check that optimistic assumption still holds: check if
+ // any of its uses can create an alias.
+ if (!defn->Identity().IsAliased() && AnyUseCreatesAlias(defn)) {
+ defn->SetIdentity(AliasIdentity::Aliased());
+ identity_rollback_.Add(defn);
+ }
- static inline bool IsKeyEqual(Pair kv, Key key) {
- return (KeyOf(kv).offset_ == key.offset_) &&
- (KeyOf(kv).instance_id_ == key.instance_id_);
+ // If the allocation site is marked as aliased conservatively mark
+ // any values stored into the object aliased too.
+ if (defn->Identity().IsAliased()) {
+ MarkStoredValuesEscaping(defn);
+ }
}
-
- private:
- Key key_;
- Value value_;
- };
+ }
Isolate* isolate_;
@@ -6563,24 +6527,33 @@ class AliasedSet : public ZoneAllocated {
const PhiPlaceMoves* phi_moves_;
- // Maps alias index to a set of ssa indexes corresponding to loads with the
- // given alias.
- GrowableArray<BitVector*> sets_;
+ // A list of all seen aliases and a map that allows looking up canonical
+ // alias object.
+ GrowableArray<const Place*> aliases_;
+ DirectChainedHashMap<PointerKeyValueTrait<const Place> > aliases_map_;
- BitVector* aliased_by_effects_;
+ // Maps alias id to set of ids of places representing the alias.
+ // Place represents an alias if this alias is least generic alias for
+ // the place.
+ // (see ToAlias for the definition of least generic alias).
+ GrowableArray<BitVector*> representatives_;
- // Table mapping static field to their id used during optimization pass.
- intptr_t max_field_id_;
- DirectChainedHashMap<FieldIdPair> field_ids_;
+ // Maps alias id to set of ids of places aliased.
+ GrowableArray<BitVector*> killed_;
- intptr_t max_index_id_;
- DirectChainedHashMap<ConstantIndexIdPair> index_ids_;
+ // Set of ids of places that can be affected by side-effects other than
+ // explicit stores (i.e. through calls).
+ BitVector* aliased_by_effects_;
- intptr_t max_unknown_index_id_;
- DirectChainedHashMap<UnknownIndexIdPair> unknown_index_ids_;
+ // Worklist used during alias analysis.
+ GrowableArray<Definition*> aliasing_worklist_;
- intptr_t max_vm_field_id_;
- DirectChainedHashMap<VMFieldIdPair> vm_field_ids_;
+ // List of definitions that had their identity set to Aliased. At the end
+ // of load optimization their identity will be rolled back to Unknown to
+ // avoid treating them as Aliased at later stages without checking first
+ // as optimizations can potentially eliminate instructions leading to
+ // aliasing.
+ GrowableArray<Definition*> identity_rollback_;
};
@@ -6643,8 +6616,7 @@ static PhiPlaceMoves* ComputePhiMoves(
Place* result = map->Lookup(&input_place);
if (result == NULL) {
- input_place.set_id(places->length());
- result = Place::Wrap(isolate, input_place);
+ result = Place::Wrap(isolate, input_place, places->length());
map->Insert(result);
places->Add(result);
if (FLAG_trace_optimization) {
@@ -6698,8 +6670,7 @@ static AliasedSet* NumberPlaces(
Place* result = map->Lookup(&place);
if (result == NULL) {
- place.set_id(places->length());
- result = Place::Wrap(isolate, place);
+ result = Place::Wrap(isolate, place, places->length());
map->Insert(result);
places->Add(result);
@@ -6724,15 +6695,7 @@ static AliasedSet* NumberPlaces(
PhiPlaceMoves* phi_moves = ComputePhiMoves(map, places);
// Build aliasing sets mapping aliases to loads.
- AliasedSet* aliased_set = new(isolate) AliasedSet(isolate, places, phi_moves);
- for (intptr_t i = 0; i < places->length(); i++) {
- Place* place = (*places)[i];
- aliased_set->AddRepresentative(place);
- }
-
- aliased_set->EnsureAliasingForUnknownIndices();
-
- return aliased_set;
+ return new(isolate) AliasedSet(isolate, places, phi_moves);
}
@@ -6766,6 +6729,10 @@ class LoadOptimizer : public ValueObject {
}
}
+ ~LoadOptimizer() {
+ aliased_set_->RollbackAliasedIdentites();
+ }
+
Isolate* isolate() const { return graph_->isolate(); }
static bool OptimizeGraph(FlowGraph* graph) {
@@ -6831,11 +6798,8 @@ class LoadOptimizer : public ValueObject {
instr_it.Advance()) {
Instruction* instr = instr_it.Current();
- const Alias alias = aliased_set_->ComputeAliasForStore(instr);
- if (!alias.IsNone()) {
- // Interfering stores kill only loads from the same offset.
- BitVector* killed = aliased_set_->Get(alias);
-
+ BitVector* killed = NULL;
+ if (aliased_set_->IsStore(instr, &killed)) {
if (killed != NULL) {
kill->AddAll(killed);
// There is no need to clear out_values when clearing GEN set
@@ -6899,7 +6863,7 @@ class LoadOptimizer : public ValueObject {
// TODO(vegorov): record null-values at least for not final fields of
// escaping object.
AllocateObjectInstr* alloc = instr->AsAllocateObject();
- if ((alloc != NULL) && !AliasedSet::CanBeAliased(alloc)) {
+ if ((alloc != NULL) && !aliased_set_->CanBeAliased(alloc)) {
for (Value* use = alloc->input_use_list();
use != NULL;
use = use->next_use()) {
@@ -7691,8 +7655,8 @@ class StoreOptimizer : public LivenessAnalysis {
// Handle loads.
Definition* defn = instr->AsDefinition();
if ((defn != NULL) && IsLoadEliminationCandidate(defn)) {
- const Alias alias = aliased_set_->ComputeAlias(&place);
- live_in->AddAll(aliased_set_->Get(alias));
+ const intptr_t alias = aliased_set_->LookupAliasId(place.ToAlias());
+ live_in->AddAll(aliased_set_->GetKilledSet(alias));
continue;
}
}
@@ -9883,16 +9847,63 @@ void FlowGraphOptimizer::EliminateEnvironments() {
}
+enum SafeUseCheck { kOptimisticCheck, kStrictCheck };
+
+// Check if the use is safe for allocation sinking. Allocation sinking
+// candidates can only be used at store instructions:
+//
+// - any store into the allocation candidate itself is unconditionally safe
+// as it just changes the rematerialization state of this candidate;
+// - store into another object is only safe if another object is allocation
+// candidate.
+//
+// We use a simple fix-point algorithm to discover the set of valid candidates
+// (see CollectCandidates method), that's why this IsSafeUse can operate in two
+// modes:
+//
+// - optimistic, when every allocation is assumed to be an allocation
+// sinking candidate;
+// - strict, when only marked allocations are assumed to be allocation
+// sinking candidates.
+//
+// Fix-point algorithm in CollectCandiates first collects a set of allocations
+// optimistically and then checks each collected candidate strictly and unmarks
+// invalid candidates transitively until only strictly valid ones remain.
+static bool IsSafeUse(Value* use, SafeUseCheck check_type) {
+ if (use->instruction()->IsMaterializeObject()) {
+ return true;
+ }
+
+ StoreInstanceFieldInstr* store = use->instruction()->AsStoreInstanceField();
+ if (store != NULL) {
+ if (use == store->value()) {
+ Definition* instance = store->instance()->definition();
+ return instance->IsAllocateObject() &&
+ ((check_type == kOptimisticCheck) ||
+ instance->Identity().IsAllocationSinkingCandidate());
+ }
+ return true;
+ }
+
+ return false;
+}
+
+
// Right now we are attempting to sink allocation only into
// deoptimization exit. So candidate should only be used in StoreInstanceField
// instructions that write into fields of the allocated object.
// We do not support materialization of the object that has type arguments.
-static bool IsAllocationSinkingCandidate(AllocateObjectInstr* alloc) {
+static bool IsAllocationSinkingCandidate(AllocateObjectInstr* alloc,
+ SafeUseCheck check_type) {
for (Value* use = alloc->input_use_list();
use != NULL;
use = use->next_use()) {
- if (!(use->instruction()->IsStoreInstanceField() &&
- (use->use_index() == 0))) {
+ if (!IsSafeUse(use, check_type)) {
+ if (FLAG_trace_optimization) {
+ OS::Print("use of %s at %s is unsafe for allocation sinking\n",
+ alloc->ToCString(),
+ use->instruction()->ToCString());
+ }
return false;
}
}
@@ -9901,10 +9912,22 @@ static bool IsAllocationSinkingCandidate(AllocateObjectInstr* alloc) {
}
+// If the given use is a store into an object then return an object we are
+// storing into.
+static Definition* StoreInto(Value* use) {
+ StoreInstanceFieldInstr* store = use->instruction()->AsStoreInstanceField();
+ if (store != NULL) {
+ return store->instance()->definition();
+ }
+
+ return NULL;
+}
+
+
// Remove the given allocation from the graph. It is not observable.
// If deoptimization occurs the object will be materialized.
-static void EliminateAllocation(AllocateObjectInstr* alloc) {
- ASSERT(IsAllocationSinkingCandidate(alloc));
+void AllocationSinking::EliminateAllocation(AllocateObjectInstr* alloc) {
+ ASSERT(IsAllocationSinkingCandidate(alloc, kStrictCheck));
if (FLAG_trace_optimization) {
OS::Print("removing allocation from the graph: v%" Pd "\n",
@@ -9921,7 +9944,13 @@ static void EliminateAllocation(AllocateObjectInstr* alloc) {
// There should be no environment uses. The pass replaced them with
// MaterializeObject instructions.
- ASSERT(alloc->env_use_list() == NULL);
+#ifdef DEBUG
+ for (Value* use = alloc->env_use_list();
+ use != NULL;
+ use = use->next_use()) {
+ ASSERT(use->instruction()->IsMaterializeObject());
+ }
+#endif
ASSERT(alloc->input_use_list() == NULL);
alloc->RemoveFromGraph();
if (alloc->ArgumentCount() > 0) {
@@ -9933,30 +9962,204 @@ static void EliminateAllocation(AllocateObjectInstr* alloc) {
}
-void AllocationSinking::Optimize() {
- GrowableArray<AllocateObjectInstr*> candidates(5);
-
- // Collect sinking candidates.
- const GrowableArray<BlockEntryInstr*>& postorder = flow_graph_->postorder();
- for (BlockIterator block_it(postorder);
+// Find allocation instructions that can be potentially eliminated and
+// rematerialized at deoptimization exits if needed. See IsSafeUse
+// for the description of algorithm used below.
+void AllocationSinking::CollectCandidates() {
+ // Optimistically collect all potential candidates.
+ for (BlockIterator block_it = flow_graph_->reverse_postorder_iterator();
!block_it.Done();
block_it.Advance()) {
BlockEntryInstr* block = block_it.Current();
for (ForwardInstructionIterator it(block); !it.Done(); it.Advance()) {
AllocateObjectInstr* alloc = it.Current()->AsAllocateObject();
- if ((alloc != NULL) && IsAllocationSinkingCandidate(alloc)) {
- if (FLAG_trace_optimization) {
- OS::Print("discovered allocation sinking candidate: v%" Pd "\n",
- alloc->ssa_temp_index());
+ if ((alloc != NULL) &&
+ IsAllocationSinkingCandidate(alloc, kOptimisticCheck)) {
+ alloc->SetIdentity(AliasIdentity::AllocationSinkingCandidate());
+ candidates_.Add(alloc);
+ }
+ }
+ }
+
+ // Transitively unmark all candidates that are not strictly valid.
+ bool changed;
+ do {
+ changed = false;
+ for (intptr_t i = 0; i < candidates_.length(); i++) {
+ AllocateObjectInstr* alloc = candidates_[i];
+ if (alloc->Identity().IsAllocationSinkingCandidate()) {
+ if (!IsAllocationSinkingCandidate(alloc, kStrictCheck)) {
+ alloc->SetIdentity(AliasIdentity::Unknown());
+ changed = true;
}
+ }
+ }
+ } while (changed);
+
+ // Shrink the list of candidates removing all unmarked ones.
+ intptr_t j = 0;
+ for (intptr_t i = 0; i < candidates_.length(); i++) {
+ AllocateObjectInstr* alloc = candidates_[i];
+ if (alloc->Identity().IsAllocationSinkingCandidate()) {
+ if (FLAG_trace_optimization) {
+ OS::Print("discovered allocation sinking candidate: v%" Pd "\n",
+ alloc->ssa_temp_index());
+ }
+
+ if (j != i) {
+ candidates_[j] = alloc;
+ }
+ j++;
+ }
+ }
+ candidates_.TruncateTo(j);
+}
- // All sinking candidate are known to be not aliased.
- alloc->SetIdentity(kIdentityNotAliased);
- candidates.Add(alloc);
+// If materialization references an allocation sinking candidate then replace
+// this reference with a materialization which should have been computed for
+// this side-exit. CollectAllExits should have collected this exit.
+void AllocationSinking::NormalizeMaterializations() {
+ for (intptr_t i = 0; i < candidates_.length(); i++) {
+ Definition* alloc = candidates_[i];
+
+ Value* next_use;
+ for (Value* use = alloc->input_use_list();
+ use != NULL;
+ use = next_use) {
+ next_use = use->next_use();
+ if (use->instruction()->IsMaterializeObject()) {
+ use->BindTo(MaterializationFor(alloc, use->instruction()));
}
}
}
+}
+
+
+// We transitively insert materializations at each deoptimization exit that
+// might see the given allocation (see ExitsCollector). Some of this
+// materializations are not actually used and some fail to compute because
+// they are inserted in the block that is not dominated by the allocation.
+// Remove them unused materializations from the graph.
+void AllocationSinking::RemoveUnusedMaterializations() {
+ intptr_t j = 0;
+ for (intptr_t i = 0; i < materializations_.length(); i++) {
+ MaterializeObjectInstr* mat = materializations_[i];
+ if ((mat->input_use_list() == NULL) && (mat->env_use_list() == NULL)) {
+ // Check if this materialization failed to compute and remove any
+ // unforwarded loads. There were no loads from any allocation sinking
+ // candidate in the beggining so it is safe to assume that any encountered
+ // load was inserted by CreateMaterializationAt.
+ for (intptr_t i = 0; i < mat->InputCount(); i++) {
+ LoadFieldInstr* load = mat->InputAt(i)->definition()->AsLoadField();
+ if ((load != NULL) &&
+ (load->instance()->definition() == mat->allocation())) {
+ load->ReplaceUsesWith(flow_graph_->constant_null());
+ load->RemoveFromGraph();
+ }
+ }
+ mat->RemoveFromGraph();
+ } else {
+ if (j != i) {
+ materializations_[j] = mat;
+ }
+ j++;
+ }
+ }
+ materializations_.TruncateTo(j);
+}
+
+
+// Some candidates might stop being eligible for allocation sinking after
+// the load forwarding because they flow into phis that load forwarding
+// inserts. Discover such allocations and remove them from the list
+// of allocation sinking candidates undoing all changes that we did
+// in preparation for sinking these allocations.
+void AllocationSinking::DiscoverFailedCandidates() {
+ // Transitively unmark all candidates that are not strictly valid.
+ bool changed;
+ do {
+ changed = false;
+ for (intptr_t i = 0; i < candidates_.length(); i++) {
+ AllocateObjectInstr* alloc = candidates_[i];
+ if (alloc->Identity().IsAllocationSinkingCandidate()) {
+ if (!IsAllocationSinkingCandidate(alloc, kStrictCheck)) {
+ alloc->SetIdentity(AliasIdentity::Unknown());
+ changed = true;
+ }
+ }
+ }
+ } while (changed);
+
+ // Remove all failed candidates from the candidates list.
+ intptr_t j = 0;
+ for (intptr_t i = 0; i < candidates_.length(); i++) {
+ AllocateObjectInstr* alloc = candidates_[i];
+ if (!alloc->Identity().IsAllocationSinkingCandidate()) {
+ if (FLAG_trace_optimization) {
+ OS::Print("allocation v%" Pd " can't be eliminated\n",
+ alloc->ssa_temp_index());
+ }
+
+#ifdef DEBUG
+ for (Value* use = alloc->env_use_list();
+ use != NULL;
+ use = use->next_use()) {
+ ASSERT(use->instruction()->IsMaterializeObject());
+ }
+#endif
+
+ // All materializations will be removed from the graph. Remove inserted
+ // loads first and detach materializations from allocation's environment
+ // use list: we will reconstruct it when we start removing
+ // materializations.
+ alloc->set_env_use_list(NULL);
+ for (Value* use = alloc->input_use_list();
+ use != NULL;
+ use = use->next_use()) {
+ if (use->instruction()->IsLoadField()) {
+ LoadFieldInstr* load = use->instruction()->AsLoadField();
+ load->ReplaceUsesWith(flow_graph_->constant_null());
+ load->RemoveFromGraph();
+ } else {
+ ASSERT(use->instruction()->IsMaterializeObject() ||
+ use->instruction()->IsPhi() ||
+ use->instruction()->IsStoreInstanceField());
+ }
+ }
+ } else {
+ if (j != i) {
+ candidates_[j] = alloc;
+ }
+ j++;
+ }
+ }
+
+ if (j != candidates_.length()) { // Something was removed from candidates.
+ intptr_t k = 0;
+ for (intptr_t i = 0; i < materializations_.length(); i++) {
+ MaterializeObjectInstr* mat = materializations_[i];
+ if (!mat->allocation()->Identity().IsAllocationSinkingCandidate()) {
+ // Restore environment uses of the allocation that were replaced
+ // by this materialization and drop materialization.
+ mat->ReplaceUsesWith(mat->allocation());
+ mat->RemoveFromGraph();
+ } else {
+ if (k != i) {
+ materializations_[k] = mat;
+ }
+ k++;
+ }
+ }
+ materializations_.TruncateTo(k);
+ }
+
+ candidates_.TruncateTo(j);
+}
+
+
+void AllocationSinking::Optimize() {
+ CollectCandidates();
// Insert MaterializeObject instructions that will describe the state of the
// object at all deoptimization points. Each inserted materialization looks
@@ -9965,8 +10168,8 @@ void AllocationSinking::Optimize() {
// ...
// v_N <- LoadField(v_0, field_N)
// v_{N+1} <- MaterializeObject(field_1 = v_1, ..., field_N = v_{N})
- for (intptr_t i = 0; i < candidates.length(); i++) {
- InsertMaterializations(candidates[i]);
+ for (intptr_t i = 0; i < candidates_.length(); i++) {
+ InsertMaterializations(candidates_[i]);
}
// Run load forwarding to eliminate LoadField instructions inserted above.
@@ -9977,15 +10180,19 @@ void AllocationSinking::Optimize() {
// external effects from calls.
LoadOptimizer::OptimizeGraph(flow_graph_);
- if (FLAG_trace_optimization) {
- FlowGraphPrinter::PrintGraph("Sinking", flow_graph_);
- }
+ NormalizeMaterializations();
+
+ RemoveUnusedMaterializations();
+
+ // If any candidates are no longer eligible for allocation sinking abort
+ // the optimization for them and undo any changes we did in preparation.
+ DiscoverFailedCandidates();
// At this point we have computed the state of object at each deoptimization
// point and we can eliminate it. Loads inserted above were forwarded so there
// are no uses of the allocation just as in the begging of the pass.
- for (intptr_t i = 0; i < candidates.length(); i++) {
- EliminateAllocation(candidates[i]);
+ for (intptr_t i = 0; i < candidates_.length(); i++) {
+ EliminateAllocation(candidates_[i]);
}
// Process materializations and unbox their arguments: materializations
@@ -10010,35 +10217,62 @@ void AllocationSinking::Optimize() {
// as part of the environment not as a real instruction.
void AllocationSinking::DetachMaterializations() {
for (intptr_t i = 0; i < materializations_.length(); i++) {
- ASSERT(materializations_[i]->input_use_list() == NULL);
materializations_[i]->previous()->LinkTo(materializations_[i]->next());
}
}
// Add a field/offset to the list of fields if it is not yet present there.
-static void AddSlot(ZoneGrowableArray<const Object*>* slots,
+static bool AddSlot(ZoneGrowableArray<const Object*>* slots,
const Object& slot) {
for (intptr_t i = 0; i < slots->length(); i++) {
if ((*slots)[i]->raw() == slot.raw()) {
- return;
+ return false;
}
}
slots->Add(&slot);
+ return true;
}
-// Add given instruction to the list of the instructions if it is not yet
-// present there.
-static void AddInstruction(GrowableArray<Instruction*>* exits,
- Instruction* exit) {
- ASSERT(!exit->IsGraphEntry());
- for (intptr_t i = 0; i < exits->length(); i++) {
- if ((*exits)[i] == exit) {
- return;
+// Find deoptimization exit for the given materialization assuming that all
+// materializations are emitted right before the instruction which is a
+// deoptimization exit.
+static Instruction* ExitForMaterialization(MaterializeObjectInstr* mat) {
+ while (mat->next()->IsMaterializeObject()) {
+ mat = mat->next()->AsMaterializeObject();
+ }
+ return mat->next();
+}
+
+
+// Given the deoptimization exit find first materialization that was inserted
+// before it.
+static Instruction* FirstMaterializationAt(Instruction* exit) {
+ while (exit->previous()->IsMaterializeObject()) {
+ exit = exit->previous();
+ }
+ return exit;
+}
+
+
+// Given the allocation and deoptimization exit try to find MaterializeObject
+// instruction corresponding to this allocation at this exit.
+MaterializeObjectInstr* AllocationSinking::MaterializationFor(
+ Definition* alloc, Instruction* exit) {
+ if (exit->IsMaterializeObject()) {
+ exit = ExitForMaterialization(exit->AsMaterializeObject());
+ }
+
+ for (MaterializeObjectInstr* mat = exit->previous()->AsMaterializeObject();
+ mat != NULL;
+ mat = mat->previous()->AsMaterializeObject()) {
+ if (mat->allocation() == alloc) {
+ return mat;
}
}
- exits->Add(exit);
+
+ return NULL;
}
@@ -10052,6 +10286,11 @@ void AllocationSinking::CreateMaterializationAt(
ZoneGrowableArray<Value*>* values =
new(I) ZoneGrowableArray<Value*>(slots.length());
+ // All loads should be inserted before the first materialization so that
+ // IR follows the following pattern: loads, materializations, deoptimizing
+ // instruction.
+ Instruction* load_point = FirstMaterializationAt(exit);
+
// Insert load instruction for every field.
for (intptr_t i = 0; i < slots.length(); i++) {
LoadFieldInstr* load = slots[i]->IsField()
@@ -10066,12 +10305,12 @@ void AllocationSinking::CreateMaterializationAt(
AbstractType::ZoneHandle(I),
alloc->token_pos());
flow_graph_->InsertBefore(
- exit, load, NULL, FlowGraph::kValue);
+ load_point, load, NULL, FlowGraph::kValue);
values->Add(new(I) Value(load));
}
MaterializeObjectInstr* mat =
- new(I) MaterializeObjectInstr(cls, slots, values);
+ new(I) MaterializeObjectInstr(alloc, cls, slots, values);
flow_graph_->InsertBefore(exit, mat, NULL, FlowGraph::kValue);
// Replace all mentions of this allocation with a newly inserted
@@ -10089,11 +10328,81 @@ void AllocationSinking::CreateMaterializationAt(
}
}
+ // Mark MaterializeObject as an environment use of this allocation.
+ // This will allow us to discover it when we are looking for deoptimization
+ // exits for another allocation that potentially flows into this one.
+ Value* val = new(I) Value(alloc);
+ val->set_instruction(mat);
+ alloc->AddEnvUse(val);
+
// Record inserted materialization.
materializations_.Add(mat);
}
+// Add given instruction to the list of the instructions if it is not yet
+// present there.
+template<typename T>
+void AddInstruction(GrowableArray<T*>* list, T* value) {
+ ASSERT(!value->IsGraphEntry());
+ for (intptr_t i = 0; i < list->length(); i++) {
+ if ((*list)[i] == value) {
+ return;
+ }
+ }
+ list->Add(value);
+}
+
+
+// Transitively collect all deoptimization exits that might need this allocation
+// rematerialized. It is not enough to collect only environment uses of this
+// allocation because it can flow into other objects that will be
+// dematerialized and that are referenced by deopt environments that
+// don't contain this allocation explicitly.
+void AllocationSinking::ExitsCollector::Collect(Definition* alloc) {
+ for (Value* use = alloc->env_use_list();
+ use != NULL;
+ use = use->next_use()) {
+ if (use->instruction()->IsMaterializeObject()) {
+ AddInstruction(&exits_, ExitForMaterialization(
+ use->instruction()->AsMaterializeObject()));
+ } else {
+ AddInstruction(&exits_, use->instruction());
+ }
+ }
+
+ // Check if this allocation is stored into any other allocation sinking
+ // candidate and put it on worklist so that we conservatively collect all
+ // exits for that candidate as well because they potentially might see
+ // this object.
+ for (Value* use = alloc->input_use_list();
+ use != NULL;
+ use = use->next_use()) {
+ Definition* obj = StoreInto(use);
+ if ((obj != NULL) && (obj != alloc)) {
+ AddInstruction(&worklist_, obj);
+ }
+ }
+}
+
+
+void AllocationSinking::ExitsCollector::CollectTransitively(Definition* alloc) {
+ exits_.TruncateTo(0);
+ worklist_.TruncateTo(0);
+
+ worklist_.Add(alloc);
+
+ // Note: worklist potentially will grow while we are iterating over it.
+ // We are not removing allocations from the worklist not to waste space on
+ // the side maintaining BitVector of already processed allocations: worklist
+ // is expected to be very small thus linear search in it is just as effecient
+ // as a bitvector.
+ for (intptr_t i = 0; i < worklist_.length(); i++) {
+ Collect(worklist_[i]);
+ }
+}
+
+
void AllocationSinking::InsertMaterializations(AllocateObjectInstr* alloc) {
// Collect all fields that are written for this instance.
ZoneGrowableArray<const Object*>* slots =
@@ -10103,10 +10412,12 @@ void AllocationSinking::InsertMaterializations(AllocateObjectInstr* alloc) {
use != NULL;
use = use->next_use()) {
StoreInstanceFieldInstr* store = use->instruction()->AsStoreInstanceField();
- if (!store->field().IsNull()) {
- AddSlot(slots, store->field());
- } else {
- AddSlot(slots, Smi::ZoneHandle(I, Smi::New(store->offset_in_bytes())));
+ if ((store != NULL) && (store->instance()->definition() == alloc)) {
+ if (!store->field().IsNull()) {
+ AddSlot(slots, store->field());
+ } else {
+ AddSlot(slots, Smi::ZoneHandle(I, Smi::New(store->offset_in_bytes())));
+ }
}
}
@@ -10117,16 +10428,12 @@ void AllocationSinking::InsertMaterializations(AllocateObjectInstr* alloc) {
}
// Collect all instructions that mention this object in the environment.
- GrowableArray<Instruction*> exits(10);
- for (Value* use = alloc->env_use_list();
- use != NULL;
- use = use->next_use()) {
- AddInstruction(&exits, use->instruction());
- }
+ exits_collector_.CollectTransitively(alloc);
// Insert materializations at environment uses.
- for (intptr_t i = 0; i < exits.length(); i++) {
- CreateMaterializationAt(exits[i], alloc, alloc->cls(), *slots);
+ for (intptr_t i = 0; i < exits_collector_.exits().length(); i++) {
+ CreateMaterializationAt(
+ exits_collector_.exits()[i], alloc, alloc->cls(), *slots);
}
}
« no previous file with comments | « runtime/vm/flow_graph_optimizer.h ('k') | runtime/vm/il_printer.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698