Flutter Engine
The Flutter Engine
redundancy_elimination.cc
Go to the documentation of this file.
1// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
2// for details. All rights reserved. Use of this source code is governed by a
3// BSD-style license that can be found in the LICENSE file.
4
6
7#include <utility>
8
9#include "vm/bit_vector.h"
10#include "vm/class_id.h"
15#include "vm/hash_map.h"
16#include "vm/object_store.h"
17
18namespace dart {
19
20DEFINE_FLAG(bool, dead_store_elimination, true, "Eliminate dead stores");
21DEFINE_FLAG(bool, load_cse, true, "Use redundant load elimination.");
23 optimize_lazy_initializer_calls,
24 true,
25 "Eliminate redundant lazy initializer calls.");
27 trace_load_optimization,
28 false,
29 "Print live sets for load optimization pass.");
30
31// Quick access to the current zone.
32#define Z (zone())
33
34// A set of Instructions used by CSE pass.
35//
36// Instructions are compared as if all redefinitions were removed from the
37// graph, with the exception of LoadField instruction which gets special
38// treatment.
40 public:
41 CSEInstructionSet() : map_() {}
42 explicit CSEInstructionSet(const CSEInstructionSet& other)
43 : ValueObject(), map_(other.map_) {}
44
46 ASSERT(other->AllowsCSE());
47 return map_.LookupValue(other);
48 }
49
50 void Insert(Instruction* instr) {
51 ASSERT(instr->AllowsCSE());
52 return map_.Insert(instr);
53 }
54
55 private:
56 static Definition* OriginalDefinition(Value* value) {
57 return value->definition()->OriginalDefinition();
58 }
59
60 static bool EqualsIgnoringRedefinitions(const Instruction& a,
61 const Instruction& b) {
62 const auto tag = a.tag();
63 if (tag != b.tag()) return false;
64 const auto input_count = a.InputCount();
65 if (input_count != b.InputCount()) return false;
66
67 // We would like to avoid replacing a load from a redefinition with a
68 // load from an original definition because that breaks the dependency
69 // on the redefinition and enables potentially incorrect code motion.
70 if (tag != Instruction::kLoadField) {
71 for (intptr_t i = 0; i < input_count; ++i) {
72 if (OriginalDefinition(a.InputAt(i)) !=
73 OriginalDefinition(b.InputAt(i))) {
74 return false;
75 }
76 }
77 } else {
78 for (intptr_t i = 0; i < input_count; ++i) {
79 if (!a.InputAt(i)->Equals(*b.InputAt(i))) return false;
80 }
81 }
82 return a.AttributesEqual(b);
83 }
84
85 class Trait {
86 public:
87 typedef Instruction* Value;
88 typedef Instruction* Key;
89 typedef Instruction* Pair;
90
91 static Key KeyOf(Pair kv) { return kv; }
92 static Value ValueOf(Pair kv) { return kv; }
93
94 static inline uword Hash(Key key) {
95 uword result = key->tag();
96 for (intptr_t i = 0; i < key->InputCount(); ++i) {
98 result, OriginalDefinition(key->InputAt(i))->ssa_temp_index());
99 }
100 return FinalizeHash(result, kBitsPerInt32 - 1);
101 }
102
103 static inline bool IsKeyEqual(Pair kv, Key key) {
104 return EqualsIgnoringRedefinitions(*kv, *key);
105 }
106 };
107
108 DirectChainedHashMap<Trait> map_;
109};
110
111// Place describes an abstract location (e.g. field) that IR can load
112// from or store to.
113//
114// Places are also used to describe wild-card locations also known as aliases,
115// that essentially represent sets of places that alias each other. Places A
116// and B are said to alias each other if store into A can affect load from B.
117//
118// We distinguish the following aliases:
119//
120// - for fields
121// - *.f - field inside some object;
122// - X.f - field inside an allocated object X;
123// - f - static fields
124//
125// - for indexed accesses
126// - *[*] - non-constant index inside some object;
127// - *[C] - constant index inside some object;
128// - X[*] - non-constant index inside an allocated object X;
129// - X[C] - constant index inside an allocated object X.
130//
131// Constant indexed places are divided into two subcategories:
132//
133// - Access to homogeneous array-like objects: Array, ImmutableArray,
134// OneByteString, TwoByteString. These objects can only be accessed
135// on element by element basis with all elements having the same size.
136// This means X[C] aliases X[K] if and only if C === K.
137// - TypedData accesses. TypedData allow to read one of the primitive
138// data types at the given byte offset. When TypedData is accessed through
139// index operator on a typed array or a typed array view it is guaranteed
140// that the byte offset is always aligned by the element size. We write
141// these accesses as X[C|S], where C is constant byte offset and S is size
142// of the data type. Obviously X[C|S] and X[K|U] alias if and only if either
143// C = RoundDown(K, S) or K = RoundDown(C, U).
144// Note that not all accesses to typed data are aligned: e.g. ByteData
145// allows unaligned access through it's get*/set* methods.
146// Check in Place::SetIndex ensures that we never create a place X[C|S]
147// such that C is not aligned by S.
148//
149// Separating allocations from other objects improves precision of the
150// load forwarding pass because of the following two properties:
151//
152// - if X can be proven to have no aliases itself (i.e. there is no other SSA
153// variable that points to X) then no place inside X can be aliased with any
154// wildcard dependent place (*.f, *.@offs, *[*], *[C]);
155// - given allocations X and Y no place inside X can be aliased with any place
156// inside Y even if any of them or both escape.
157//
158// It is important to realize that single place can belong to multiple aliases.
159// For example place X.f with aliased allocation X belongs both to X.f and *.f
160// aliases. Likewise X[C] with non-aliased allocation X belongs to X[C] and X[*]
161// aliases.
162//
163class Place : public ValueObject {
164 public:
165 enum Kind {
167
168 // Static field location. Is represented as a Field object with a
169 // nullptr instance.
171
172 // Instance field location. It is represented by a pair of instance
173 // and a Slot.
175
176 // Indexed location with a non-constant index.
178
179 // Indexed location with a constant index.
181 };
182
183 // Size of the element accessed by constant index. Size is only important
184 // for TypedData because those accesses can alias even when constant indexes
185 // are not the same: X[0|4] aliases X[0|2] and X[2|2].
187 // If indexed access is not a TypedData access then element size is not
188 // important because there is only a single possible access size depending
189 // on the receiver - X[C] aliases X[K] if and only if C == K.
190 // This is the size set for Array, ImmutableArray, OneByteString and
191 // TwoByteString accesses.
193
194 // 1 byte (Int8List, Uint8List, Uint8ClampedList).
196
197 // 2 bytes (Int16List, Uint16List).
199
200 // 4 bytes (Int32List, Uint32List, Float32List).
202
203 // 8 bytes (Int64List, Uint64List, Float64List).
205
206 // 16 bytes (Int32x4List, Float32x4List, Float64x2List).
208
210 };
211
212 Place(const Place& other)
213 : ValueObject(),
214 flags_(other.flags_),
215 instance_(other.instance_),
217 id_(other.id_) {}
218
219 // Construct a place from instruction if instruction accesses any place.
220 // Otherwise constructs kNone place.
221 Place(Instruction* instr, bool* is_load, bool* is_store)
222 : flags_(0), instance_(nullptr), raw_selector_(0), id_(0) {
223 switch (instr->tag()) {
224 case Instruction::kLoadField: {
225 LoadFieldInstr* load_field = instr->AsLoadField();
226 set_representation(load_field->representation());
227 instance_ = load_field->instance()->definition()->OriginalDefinition();
228 set_kind(kInstanceField);
229 instance_field_ = &load_field->slot();
230 *is_load = true;
231 break;
232 }
233
234 case Instruction::kStoreField: {
235 StoreFieldInstr* store = instr->AsStoreField();
236 set_representation(
237 store->RequiredInputRepresentation(StoreFieldInstr::kValuePos));
238 instance_ = store->instance()->definition()->OriginalDefinition();
239 set_kind(kInstanceField);
240 instance_field_ = &store->slot();
241 *is_store = true;
242 break;
243 }
244
245 case Instruction::kLoadStaticField:
246 set_kind(kStaticField);
247 set_representation(instr->AsLoadStaticField()->representation());
248 static_field_ = &instr->AsLoadStaticField()->field();
249 *is_load = true;
250 break;
251
252 case Instruction::kStoreStaticField:
253 set_kind(kStaticField);
254 set_representation(
255 instr->AsStoreStaticField()->RequiredInputRepresentation(
257 static_field_ = &instr->AsStoreStaticField()->field();
258 *is_store = true;
259 break;
260
261 case Instruction::kLoadIndexed: {
262 LoadIndexedInstr* load_indexed = instr->AsLoadIndexed();
263 // Since the same returned representation is used for arrays with small
264 // elements, use the array element representation instead.
265 //
266 // For example, loads from signed and unsigned byte views of the same
267 // array share the same place if the returned representation is used,
268 // which means a load which sign extends the byte to the native word
269 // size might be replaced with an load that zero extends the byte
270 // instead and vice versa.
272 load_indexed->class_id()));
273 instance_ = load_indexed->array()->definition()->OriginalDefinition();
274 SetIndex(load_indexed->index()->definition()->OriginalDefinition(),
275 load_indexed->index_scale(), load_indexed->class_id());
276 *is_load = true;
277 break;
278 }
279
280 case Instruction::kStoreIndexed: {
281 StoreIndexedInstr* store_indexed = instr->AsStoreIndexed();
282 // Use the array element representation instead of the value
283 // representation for the same reasons as for LoadIndexed above.
285 store_indexed->class_id()));
286 instance_ = store_indexed->array()->definition()->OriginalDefinition();
287 SetIndex(store_indexed->index()->definition()->OriginalDefinition(),
288 store_indexed->index_scale(), store_indexed->class_id());
289 *is_store = true;
290 break;
291 }
292
293 default:
294 break;
295 }
296 }
297
298 // Construct a place from an allocation where the place represents a store to
299 // a slot that corresponds to the given input position.
300 // Otherwise, constructs a kNone place.
301 Place(AllocationInstr* alloc, intptr_t input_pos)
302 : flags_(0), instance_(nullptr), raw_selector_(0), id_(0) {
303 if (const Slot* slot = alloc->SlotForInput(input_pos)) {
304 set_representation(alloc->RequiredInputRepresentation(input_pos));
305 instance_ = alloc;
306 set_kind(kInstanceField);
307 instance_field_ = slot;
308 }
309 }
310
311 // Create object representing *[*] alias.
312 static Place* CreateAnyInstanceAnyIndexAlias(Zone* zone, intptr_t id) {
313 return Wrap(
314 zone,
315 Place(EncodeFlags(kIndexed, kNoRepresentation, kNoSize), nullptr, 0),
316 id);
317 }
318
319 // Return least generic alias for this place. Given that aliases are
320 // essentially sets of places we define least generic alias as a smallest
321 // alias that contains this place.
322 //
323 // We obtain such alias by a simple transformation:
324 //
325 // - for places that depend on an instance X.f, X.@offs, X[i], X[C]
326 // we drop X if X is not an allocation because in this case X does not
327 // possess an identity obtaining aliases *.f, *.@offs, *[i] and *[C]
328 // respectively; also drop instance of X[i] for typed data view
329 // allocations as they may alias other indexed accesses.
330 // - for non-constant indexed places X[i] we drop information about the
331 // index obtaining alias X[*].
332 // - we drop information about representation, but keep element size
333 // if any.
334 //
335 Place ToAlias() const {
336 Definition* alias_instance = nullptr;
339 alias_instance = instance();
340 }
341 return Place(RepresentationBits::update(kNoRepresentation, flags_),
342 alias_instance, (kind() == kIndexed) ? 0 : raw_selector_);
343 }
344
345 bool DependsOnInstance() const {
346 switch (kind()) {
347 case kInstanceField:
348 case kIndexed:
349 case kConstantIndexed:
350 return true;
351
352 case kStaticField:
353 case kNone:
354 return false;
355 }
356
357 UNREACHABLE();
358 return false;
359 }
360
361 // Given instance dependent alias X.f, X.@offs, X[C], X[*] return
362 // wild-card dependent alias *.f, *.@offs, *[C] or *[*] respectively.
365 return Place(flags_, nullptr, raw_selector_);
366 }
367
368 // Given alias X[C] or *[C] return X[*] and *[*] respectively.
371 return Place(EncodeFlags(kIndexed, kNoRepresentation, kNoSize), instance_,
372 0);
373 }
374
375 // Given alias X[ByteOffs|S] and a larger element size S', return
376 // alias X[RoundDown(ByteOffs, S')|S'] - this is the byte offset of a larger
377 // typed array element that contains this typed array element.
378 // In other words this method computes the only possible place with the given
379 // size that can alias this place (due to alignment restrictions).
380 // For example for X[9|k1Byte] and target size k4Bytes we would return
381 // X[8|k4Bytes].
385 ASSERT(element_size() < to);
386 return Place(ElementSizeBits::update(to, flags_), instance_,
387 RoundByteOffset(to, index_constant_));
388 }
389
390 // Given alias X[ByteOffs|S], smaller element size S' and index from 0 to
391 // S/S' - 1 return alias X[ByteOffs + S'*index|S'] - this is the byte offset
392 // of a smaller typed array element which is contained within this typed
393 // array element.
394 // For example X[8|k4Bytes] contains inside X[8|k2Bytes] (index is 0) and
395 // X[10|k2Bytes] (index is 1).
399 ASSERT(element_size() > to);
400 ASSERT(index >= 0);
401 ASSERT(index <
402 ElementSizeMultiplier(element_size()) / ElementSizeMultiplier(to));
403 return Place(ElementSizeBits::update(to, flags_), instance_,
404 ByteOffsetToSmallerElement(to, index, index_constant_));
405 }
406
407 intptr_t id() const { return id_; }
408
409 Kind kind() const { return KindBits::decode(flags_); }
410
412 return RepresentationBits::decode(flags_);
413 }
414
417 return instance_;
418 }
419
422 instance_ = def->OriginalDefinition();
423 }
424
425 const Field& static_field() const {
428 return *static_field_;
429 }
430
431 const Slot& instance_field() const {
433 return *instance_field_;
434 }
435
436 Definition* index() const {
437 ASSERT(kind() == kIndexed);
438 return index_;
439 }
440
442
443 intptr_t index_constant() const {
445 return index_constant_;
446 }
447
448 static const char* DefinitionName(Definition* def) {
449 if (def == nullptr) {
450 return "*";
451 } else {
452 return Thread::Current()->zone()->PrintToString("v%" Pd,
453 def->ssa_temp_index());
454 }
455 }
456
457 const char* ToCString() const {
458 switch (kind()) {
459 case kNone:
460 return "<none>";
461
462 case kStaticField: {
463 const char* field_name =
465 return Thread::Current()->zone()->PrintToString("<%s>", field_name);
466 }
467
468 case kInstanceField:
470 "<%s.%s[%p]>", DefinitionName(instance()), instance_field().Name(),
471 &instance_field());
472
473 case kIndexed:
475 "<%s[%s]>", DefinitionName(instance()), DefinitionName(index()));
476
477 case kConstantIndexed:
478 if (element_size() == kNoSize) {
480 "<%s[%" Pd "]>", DefinitionName(instance()), index_constant());
481 } else {
483 "<%s[%" Pd "|%" Pd "]>", DefinitionName(instance()),
484 index_constant(), ElementSizeMultiplier(element_size()));
485 }
486 }
487 UNREACHABLE();
488 return "<?>";
489 }
490
491 // Fields that are considered immutable by load optimization.
492 // Handle static finals as non-final with precompilation because
493 // they may be reset to uninitialized after compilation.
494 bool IsImmutableField() const {
495 switch (kind()) {
496 case kInstanceField:
497 return instance_field().is_immutable();
498 default:
499 return false;
500 }
501 }
502
503 uword Hash() const {
504 return FinalizeHash(
505 CombineHashes(flags_, reinterpret_cast<uword>(instance_)),
506 kBitsPerInt32 - 1);
507 }
508
509 bool Equals(const Place& other) const {
510 return (flags_ == other.flags_) && (instance_ == other.instance_) &&
511 SameField(other);
512 }
513
514 // Create a zone allocated copy of this place and assign given id to it.
515 static Place* Wrap(Zone* zone, const Place& place, intptr_t id);
516
517 static bool IsAllocation(Definition* defn) {
518 return (defn != nullptr) && (defn->IsAllocation() ||
519 (defn->IsStaticCall() &&
520 defn->AsStaticCall()->IsRecognizedFactory()));
521 }
522
524 if (defn != nullptr) {
525 if (auto* alloc = defn->AsAllocateObject()) {
526 auto const cid = alloc->cls().id();
527 return IsTypedDataViewClassId(cid) ||
529 }
530 }
531 return false;
532 }
533
534 private:
535 Place(uword flags, Definition* instance, intptr_t selector)
536 : flags_(flags), instance_(instance), raw_selector_(selector), id_(0) {}
537
538 bool SameField(const Place& other) const {
539 return (kind() == kStaticField)
540 ? (static_field().Original() == other.static_field().Original())
541 : (raw_selector_ == other.raw_selector_);
542 }
543
544 void set_representation(Representation rep) {
545 flags_ = RepresentationBits::update(rep, flags_);
546 }
547
548 void set_kind(Kind kind) { flags_ = KindBits::update(kind, flags_); }
549
550 void set_element_size(ElementSize scale) {
551 flags_ = ElementSizeBits::update(scale, flags_);
552 }
553
554 void SetIndex(Definition* index, intptr_t scale, classid_t class_id) {
555 ConstantInstr* index_constant = index->AsConstant();
556 if ((index_constant != nullptr) && index_constant->value().IsSmi()) {
557 const intptr_t index_value = Smi::Cast(index_constant->value()).Value();
558
559 // Places only need to scale the index for typed data objects, as other
560 // types of arrays (for which ElementSizeFor returns kNoSize) cannot be
561 // accessed at different scales.
562 const ElementSize size = ElementSizeFor(class_id);
563 if (size == kNoSize) {
564 set_kind(kConstantIndexed);
565 set_element_size(size);
566 index_constant_ = index_value;
567 return;
568 }
569
570 // Guard against potential multiplication overflow and negative indices.
571 if ((0 <= index_value) && (index_value < (kMaxInt32 / scale))) {
572 const intptr_t scaled_index = index_value * scale;
573
574 // If the indexed array is a subclass of PointerBase, then it must be
575 // treated as a view unless the class id of the array is known at
576 // compile time to be an internal typed data object.
577 //
578 // Indexes into untagged pointers only happen for external typed data
579 // objects or dart:ffi's Pointer, but those should also be treated like
580 // views, since anyone who has access to the underlying pointer can
581 // modify the corresponding memory.
582 auto const cid = instance_->Type()->ToCid();
583 const bool can_be_view = instance_->representation() == kUntagged ||
585
586 // Guard against unaligned byte offsets and access through raw
587 // memory pointer (which can be pointing into another typed data).
588 if (!can_be_view &&
589 Utils::IsAligned(scaled_index, ElementSizeMultiplier(size))) {
590 set_kind(kConstantIndexed);
591 set_element_size(size);
592 index_constant_ = scaled_index;
593 return;
594 }
595 }
596
597 // Fallthrough: create generic _[*] place.
598 }
599
600 set_kind(kIndexed);
601 index_ = index;
602 }
603
604 static uword EncodeFlags(Kind kind, Representation rep, ElementSize scale) {
608 }
609
610 static ElementSize ElementSizeFor(classid_t class_id) {
611 // Object arrays and strings do not allow accessing them through
612 // different types. No need to attach scale.
613 if (!IsTypedDataBaseClassId(class_id)) return kNoSize;
614
615 const auto rep =
617 if (!RepresentationUtils::IsUnboxed(rep)) return kNoSize;
618 switch (RepresentationUtils::ValueSize(rep)) {
619 case 1:
620 return k1Byte;
621 case 2:
622 return k2Bytes;
623 case 4:
624 return k4Bytes;
625 case 8:
626 return k8Bytes;
627 case 16:
628 return k16Bytes;
629 default:
630 FATAL("Unhandled value size for representation %s",
632 return kNoSize;
633 }
634 }
635
636 static constexpr intptr_t ElementSizeMultiplier(ElementSize size) {
637 return 1 << (static_cast<intptr_t>(size) - static_cast<intptr_t>(k1Byte));
638 }
639
640 static intptr_t RoundByteOffset(ElementSize size, intptr_t offset) {
641 return offset & ~(ElementSizeMultiplier(size) - 1);
642 }
643
644 static intptr_t ByteOffsetToSmallerElement(ElementSize size,
645 intptr_t index,
646 intptr_t base_offset) {
647 return base_offset + index * ElementSizeMultiplier(size);
648 }
649
650 class KindBits : public BitField<uword, Kind, 0, 3> {};
652 : public BitField<uword, Representation, KindBits::kNextBit, 11> {};
653
654 static constexpr int kNumElementSizeBits = Utils::ShiftForPowerOfTwo(
656 class ElementSizeBits : public BitField<uword,
657 ElementSize,
658 RepresentationBits::kNextBit,
659 kNumElementSizeBits> {};
660
661 uword flags_;
662 Definition* instance_;
663 union {
669 };
670
671 intptr_t id_;
672};
673
674class ZonePlace : public ZoneAllocated {
675 public:
676 explicit ZonePlace(const Place& place) : place_(place) {}
677
678 Place* place() { return &place_; }
679
680 private:
681 Place place_;
682};
683
684Place* Place::Wrap(Zone* zone, const Place& place, intptr_t id) {
685 Place* wrapped = (new (zone) ZonePlace(place))->place();
686 wrapped->id_ = id;
687 return wrapped;
688}
689
690// Correspondence between places connected through outgoing phi moves on the
691// edge that targets join.
693 public:
694 // Record a move from the place with id |from| to the place with id |to| at
695 // the given block.
697 BlockEntryInstr* block,
698 intptr_t from,
699 intptr_t to) {
700 const intptr_t block_num = block->preorder_number();
701 moves_.EnsureLength(block_num + 1, nullptr);
702
703 if (moves_[block_num] == nullptr) {
704 moves_[block_num] = new (zone) ZoneGrowableArray<Move>(5);
705 }
706
707 moves_[block_num]->Add(Move(from, to));
708 }
709
710 class Move {
711 public:
712 Move(intptr_t from, intptr_t to) : from_(from), to_(to) {}
713
714 intptr_t from() const { return from_; }
715 intptr_t to() const { return to_; }
716
717 private:
718 intptr_t from_;
719 intptr_t to_;
720 };
721
723
725 const intptr_t block_num = block->preorder_number();
726 return (block_num < moves_.length()) ? moves_[block_num] : nullptr;
727 }
728
729 private:
731};
732
733// A map from aliases to a set of places sharing the alias. Additionally
734// carries a set of places that can be aliased by side-effects, essentially
735// those that are affected by calls.
736class AliasedSet : public ZoneAllocated {
737 public:
739 PointerSet<Place>* places_map,
742 bool print_traces)
743 : zone_(zone),
744 print_traces_(print_traces),
745 places_map_(places_map),
746 places_(*places),
747 phi_moves_(phi_moves),
748 aliases_(5),
749 aliases_map_(),
750 typed_data_access_sizes_(),
751 representatives_(),
752 killed_(),
753 aliased_by_effects_(new(zone) BitVector(zone, places->length())) {
755 zone_, kAnyInstanceAnyIndexAlias));
756 for (intptr_t i = 0; i < places_.length(); i++) {
757 AddRepresentative(places_[i]);
758 }
759 ComputeKillSets();
760 }
761
762 intptr_t LookupAliasId(const Place& alias) {
763 const Place* result = aliases_map_.LookupValue(&alias);
764 return (result != nullptr) ? result->id() : static_cast<intptr_t>(kNoAlias);
765 }
766
767 BitVector* GetKilledSet(intptr_t alias) {
768 return (alias < killed_.length()) ? killed_[alias] : nullptr;
769 }
770
771 intptr_t max_place_id() const { return places().length(); }
772 bool IsEmpty() const { return max_place_id() == 0; }
773
774 BitVector* aliased_by_effects() const { return aliased_by_effects_; }
775
776 const ZoneGrowableArray<Place*>& places() const { return places_; }
777
778 Place* LookupCanonical(Place* place) const {
779 return places_map_->LookupValue(place);
780 }
781
783 bool comma = false;
784 for (BitVector::Iterator it(set); !it.Done(); it.Advance()) {
785 if (comma) {
786 THR_Print(", ");
787 }
788 THR_Print("%s", places_[it.Current()]->ToCString());
789 comma = true;
790 }
791 }
792
793 const PhiPlaceMoves* phi_moves() const { return phi_moves_; }
794
796 for (intptr_t i = 0; i < identity_rollback_.length(); ++i) {
797 identity_rollback_[i]->SetIdentity(AliasIdentity::Unknown());
798 }
799 }
800
801 // Returns false if the result of an allocation instruction can't be aliased
802 // by another SSA variable and true otherwise.
804 if (!Place::IsAllocation(alloc)) {
805 return true;
806 }
807
808 if (alloc->Identity().IsUnknown()) {
809 ComputeAliasing(alloc);
810 }
811
812 return !alloc->Identity().IsNotAliased();
813 }
814
815 enum { kNoAlias = 0 };
816
817 private:
818 enum {
819 // Artificial alias that is used to collect all representatives of the
820 // *[C], X[C] aliases for arbitrary C.
821 kAnyConstantIndexedAlias = 1,
822
823 // Artificial alias that is used to collect all representatives of
824 // *[C] alias for arbitrary C.
825 kUnknownInstanceConstantIndexedAlias = 2,
826
827 // Artificial alias that is used to collect all representatives of
828 // X[*] alias for all X.
829 kAnyAllocationIndexedAlias = 3,
830
831 // *[*] alias.
832 kAnyInstanceAnyIndexAlias = 4
833 };
834
835 // Compute least generic alias for the place and assign alias id to it.
836 void AddRepresentative(Place* place) {
837 if (!place->IsImmutableField()) {
838 const Place* alias = CanonicalizeAlias(place->ToAlias());
839 EnsureSet(&representatives_, alias->id())->Add(place->id());
840
841 // Update cumulative representative sets that are used during
842 // killed sets computation.
843 if (alias->kind() == Place::kConstantIndexed) {
844 if (CanBeAliased(alias->instance())) {
845 EnsureSet(&representatives_, kAnyConstantIndexedAlias)
846 ->Add(place->id());
847 }
848
849 if (alias->instance() == nullptr) {
850 EnsureSet(&representatives_, kUnknownInstanceConstantIndexedAlias)
851 ->Add(place->id());
852 }
853
854 // Collect all element sizes used to access TypedData arrays in
855 // the function. This is used to skip sizes without representatives
856 // when computing kill sets.
857 if (alias->element_size() != Place::kNoSize) {
858 typed_data_access_sizes_.Add(alias->element_size());
859 }
860 } else if ((alias->kind() == Place::kIndexed) &&
861 CanBeAliased(place->instance())) {
862 EnsureSet(&representatives_, kAnyAllocationIndexedAlias)
863 ->Add(place->id());
864 }
865
866 if (!IsIndependentFromEffects(place)) {
867 aliased_by_effects_->Add(place->id());
868 }
869 }
870 }
871
872 void ComputeKillSets() {
873 for (intptr_t i = 0; i < aliases_.length(); ++i) {
874 const Place* alias = aliases_[i];
875 // Add all representatives to the kill set.
876 AddAllRepresentatives(alias->id(), alias->id());
877 ComputeKillSet(alias);
878 }
879
880 if (FLAG_trace_load_optimization && print_traces_) {
881 THR_Print("Aliases KILL sets:\n");
882 for (intptr_t i = 0; i < aliases_.length(); ++i) {
883 const Place* alias = aliases_[i];
884 BitVector* kill = GetKilledSet(alias->id());
885
886 THR_Print("%s: ", alias->ToCString());
887 if (kill != nullptr) {
888 PrintSet(kill);
889 }
890 THR_Print("\n");
891 }
892 }
893 }
894
895 void InsertAlias(const Place* alias) {
896 aliases_map_.Insert(alias);
897 aliases_.Add(alias);
898 }
899
900 const Place* CanonicalizeAlias(const Place& alias) {
901 const Place* canonical = aliases_map_.LookupValue(&alias);
902 if (canonical == nullptr) {
903 canonical = Place::Wrap(zone_, alias,
904 kAnyInstanceAnyIndexAlias + aliases_.length());
905 InsertAlias(canonical);
906 }
907 ASSERT(aliases_map_.LookupValue(&alias) == canonical);
908 return canonical;
909 }
910
911 BitVector* GetRepresentativesSet(intptr_t alias) {
912 return (alias < representatives_.length()) ? representatives_[alias]
913 : nullptr;
914 }
915
916 BitVector* EnsureSet(GrowableArray<BitVector*>* sets, intptr_t alias) {
917 while (sets->length() <= alias) {
918 sets->Add(nullptr);
919 }
920
921 BitVector* set = (*sets)[alias];
922 if (set == nullptr) {
923 (*sets)[alias] = set = new (zone_) BitVector(zone_, max_place_id());
924 }
925 return set;
926 }
927
928 void AddAllRepresentatives(const Place* to, intptr_t from) {
929 AddAllRepresentatives(to->id(), from);
930 }
931
932 void AddAllRepresentatives(intptr_t to, intptr_t from) {
933 BitVector* from_set = GetRepresentativesSet(from);
934 if (from_set != nullptr) {
935 EnsureSet(&killed_, to)->AddAll(from_set);
936 }
937 }
938
939 void CrossAlias(const Place* to, const Place& from) {
940 const intptr_t from_id = LookupAliasId(from);
941 if (from_id == kNoAlias) {
942 return;
943 }
944 CrossAlias(to, from_id);
945 }
946
947 void CrossAlias(const Place* to, intptr_t from) {
948 AddAllRepresentatives(to->id(), from);
949 AddAllRepresentatives(from, to->id());
950 }
951
952 // When computing kill sets we let less generic alias insert its
953 // representatives into more generic aliases kill set. For example
954 // when visiting alias X[*] instead of searching for all aliases X[C]
955 // and inserting their representatives into kill set for X[*] we update
956 // kill set for X[*] each time we visit new X[C] for some C.
957 // There is an exception however: if both aliases are parametric like *[C]
958 // and X[*] which cross alias when X is an aliased allocation then we use
959 // artificial aliases that contain all possible representatives for the given
960 // alias for any value of the parameter to compute resulting kill set.
961 void ComputeKillSet(const Place* alias) {
962 switch (alias->kind()) {
963 case Place::kIndexed: // Either *[*] or X[*] alias.
964 if (alias->instance() == nullptr) {
965 // *[*] aliases with X[*], X[C], *[C].
966 AddAllRepresentatives(alias, kAnyConstantIndexedAlias);
967 AddAllRepresentatives(alias, kAnyAllocationIndexedAlias);
968 } else if (CanBeAliased(alias->instance())) {
969 // X[*] aliases with X[C].
970 // If X can be aliased then X[*] also aliases with *[C], *[*].
971 CrossAlias(alias, kAnyInstanceAnyIndexAlias);
972 AddAllRepresentatives(alias, kUnknownInstanceConstantIndexedAlias);
973 }
974 break;
975
976 case Place::kConstantIndexed: // Either X[C] or *[C] alias.
977 if (alias->element_size() != Place::kNoSize) {
978 const bool has_aliased_instance =
979 (alias->instance() != nullptr) && CanBeAliased(alias->instance());
980
981 // If this is a TypedData access then X[C|S] aliases larger elements
982 // covering this one X[RoundDown(C, S')|S'] for all S' > S and
983 // all smaller elements being covered by this one X[C'|S'] for
984 // some S' < S and all C' such that C = RoundDown(C', S).
985 // In the loop below it's enough to only propagate aliasing to
986 // larger aliases because propagation is symmetric: smaller aliases
987 // (if there are any) would update kill set for this alias when they
988 // are visited.
989 for (intptr_t i = static_cast<intptr_t>(alias->element_size()) + 1;
991 // Skip element sizes that a guaranteed to have no representatives.
992 if (!typed_data_access_sizes_.Contains(alias->element_size())) {
993 continue;
994 }
995
996 // X[C|S] aliases with X[RoundDown(C, S')|S'] and likewise
997 // *[C|S] aliases with *[RoundDown(C, S')|S'].
998 CrossAlias(alias, alias->ToLargerElement(
999 static_cast<Place::ElementSize>(i)));
1000 }
1001
1002 if (has_aliased_instance) {
1003 // If X is an aliased instance then X[C|S] aliases *[C'|S'] for all
1004 // related combinations of C' and S'.
1005 // Caveat: this propagation is not symmetric (we would not know
1006 // to propagate aliasing from *[C'|S'] to X[C|S] when visiting
1007 // *[C'|S']) and thus we need to handle both element sizes smaller
1008 // and larger than S.
1009 const Place no_instance_alias = alias->CopyWithoutInstance();
1010 for (intptr_t i = Place::k1Byte; i <= Place::kLargestElementSize;
1011 i++) {
1012 // Skip element sizes that a guaranteed to have no
1013 // representatives.
1014 if (!typed_data_access_sizes_.Contains(alias->element_size())) {
1015 continue;
1016 }
1017
1018 const auto other_size = static_cast<Place::ElementSize>(i);
1019 if (other_size > alias->element_size()) {
1020 // X[C|S] aliases all larger elements which cover it:
1021 // *[RoundDown(C, S')|S'] for S' > S.
1022 CrossAlias(alias,
1023 no_instance_alias.ToLargerElement(other_size));
1024 } else if (other_size < alias->element_size()) {
1025 // X[C|S] aliases all sub-elements of smaller size:
1026 // *[C+j*S'|S'] for S' < S and j from 0 to S/S' - 1.
1027 const auto num_smaller_elements =
1028 1 << (alias->element_size() - other_size);
1029 for (intptr_t j = 0; j < num_smaller_elements; j++) {
1030 CrossAlias(alias,
1031 no_instance_alias.ToSmallerElement(other_size, j));
1032 }
1033 }
1034 }
1035 }
1036 }
1037
1038 if (alias->instance() == nullptr) {
1039 // *[C] aliases with X[C], X[*], *[*].
1040 AddAllRepresentatives(alias, kAnyAllocationIndexedAlias);
1041 CrossAlias(alias, kAnyInstanceAnyIndexAlias);
1042 } else {
1043 // X[C] aliases with X[*].
1044 // If X can be aliased then X[C] also aliases with *[C], *[*].
1045 CrossAlias(alias, alias->CopyWithoutIndex());
1046 if (CanBeAliased(alias->instance())) {
1047 CrossAlias(alias, alias->CopyWithoutInstance());
1048 CrossAlias(alias, kAnyInstanceAnyIndexAlias);
1049 }
1050 }
1051 break;
1052
1054 // Nothing to do.
1055 break;
1056
1058 if (CanBeAliased(alias->instance())) {
1059 // X.f alias with *.f.
1060 CrossAlias(alias, alias->CopyWithoutInstance());
1061 }
1062 break;
1063
1064 case Place::kNone:
1065 UNREACHABLE();
1066 }
1067 }
1068
1069 // Returns true if the given load is unaffected by external side-effects.
1070 // This essentially means that no stores to the same location can
1071 // occur in other functions.
1072 bool IsIndependentFromEffects(Place* place) {
1073 if (place->IsImmutableField()) {
1074 return true;
1075 }
1076
1077 return ((place->kind() == Place::kInstanceField) ||
1078 (place->kind() == Place::kConstantIndexed)) &&
1079 (place->instance() != nullptr) && !CanBeAliased(place->instance());
1080 }
1081
1082 // Returns true if there are direct loads from the given place.
1083 bool HasLoadsFromPlace(Definition* defn, const Place* place) {
1084 ASSERT(place->kind() == Place::kInstanceField);
1085
1086 for (Value* use = defn->input_use_list(); use != nullptr;
1087 use = use->next_use()) {
1088 Instruction* instr = use->instruction();
1089 if (UseIsARedefinition(use) &&
1090 HasLoadsFromPlace(instr->Cast<Definition>(), place)) {
1091 return true;
1092 }
1093 bool is_load = false, is_store;
1094 Place load_place(instr, &is_load, &is_store);
1095
1096 if (is_load && load_place.Equals(*place)) {
1097 return true;
1098 }
1099 }
1100
1101 return false;
1102 }
1103
1104 // Returns true if the given [use] is a redefinition (e.g. RedefinitionInstr,
1105 // CheckNull, CheckArrayBound, etc).
1106 static bool UseIsARedefinition(Value* use) {
1107 Instruction* instr = use->instruction();
1108 return instr->IsDefinition() &&
1109 (instr->Cast<Definition>()->RedefinedValue() == use);
1110 }
1111
1112 // Check if any use of the definition can create an alias.
1113 // Can add more objects into aliasing_worklist_.
1114 bool AnyUseCreatesAlias(Definition* defn) {
1115 for (Value* use = defn->input_use_list(); use != nullptr;
1116 use = use->next_use()) {
1117 Instruction* instr = use->instruction();
1118 if (instr->HasUnknownSideEffects() ||
1119 (instr->IsLoadField() &&
1120 instr->AsLoadField()->MayCreateUntaggedAlias()) ||
1121 (instr->IsStoreIndexed() &&
1122 (use->use_index() == StoreIndexedInstr::kValuePos)) ||
1123 instr->IsStoreStaticField() || instr->IsPhi()) {
1124 return true;
1125 } else if (UseIsARedefinition(use) &&
1126 AnyUseCreatesAlias(instr->Cast<Definition>())) {
1127 return true;
1128 } else if (instr->IsStoreField()) {
1129 StoreFieldInstr* store = instr->AsStoreField();
1130
1131 if (store->slot().kind() == Slot::Kind::kTypedDataView_typed_data) {
1132 // Initialization of TypedDataView.typed_data field creates
1133 // aliasing between the view and original typed data,
1134 // as the same data can now be accessed via both typed data
1135 // view and the original typed data.
1136 return true;
1137 }
1138
1139 if (use->use_index() != StoreFieldInstr::kInstancePos) {
1140 ASSERT(use->use_index() == StoreFieldInstr::kValuePos);
1141 // If we store this value into an object that is not aliased itself
1142 // and we never load again then the store does not create an alias.
1143 Definition* instance =
1144 store->instance()->definition()->OriginalDefinition();
1146 !instance->Identity().IsAliased()) {
1147 bool is_load, is_store;
1148 Place store_place(instr, &is_load, &is_store);
1149
1150 if (!HasLoadsFromPlace(instance, &store_place)) {
1151 // No loads found that match this store. If it is yet unknown if
1152 // the object is not aliased then optimistically assume this but
1153 // add it to the worklist to check its uses transitively.
1154 if (instance->Identity().IsUnknown()) {
1155 instance->SetIdentity(AliasIdentity::NotAliased());
1156 aliasing_worklist_.Add(instance);
1157 }
1158 continue;
1159 }
1160 }
1161 return true;
1162 }
1163 } else if (auto* const alloc = instr->AsAllocation()) {
1164 // Treat inputs to an allocation instruction exactly as if they were
1165 // manually stored using a StoreField instruction.
1166 if (alloc->Identity().IsAliased()) {
1167 return true;
1168 }
1169 Place input_place(alloc, use->use_index());
1170 if (HasLoadsFromPlace(alloc, &input_place)) {
1171 return true;
1172 }
1173 if (alloc->Identity().IsUnknown()) {
1174 alloc->SetIdentity(AliasIdentity::NotAliased());
1175 aliasing_worklist_.Add(alloc);
1176 }
1177 }
1178 }
1179 return false;
1180 }
1181
1182 void MarkDefinitionAsAliased(Definition* d) {
1183 auto* const defn = d->OriginalDefinition();
1184 if (defn->Identity().IsNotAliased()) {
1185 defn->SetIdentity(AliasIdentity::Aliased());
1186 identity_rollback_.Add(defn);
1187
1188 // Add to worklist to propagate the mark transitively.
1189 aliasing_worklist_.Add(defn);
1190 }
1191 }
1192
1193 // Mark any value stored into the given object as potentially aliased.
1194 void MarkStoredValuesEscaping(Definition* defn) {
1195 // Find all inputs corresponding to fields if allocating an object.
1196 if (auto* const alloc = defn->AsAllocation()) {
1197 for (intptr_t i = 0; i < alloc->InputCount(); i++) {
1198 if (auto* const slot = alloc->SlotForInput(i)) {
1199 MarkDefinitionAsAliased(alloc->InputAt(i)->definition());
1200 }
1201 }
1202 }
1203 // Find all stores into this object.
1204 for (Value* use = defn->input_use_list(); use != nullptr;
1205 use = use->next_use()) {
1206 auto instr = use->instruction();
1207 if (UseIsARedefinition(use)) {
1208 MarkStoredValuesEscaping(instr->AsDefinition());
1209 continue;
1210 }
1211 if ((use->use_index() == StoreFieldInstr::kInstancePos) &&
1212 instr->IsStoreField()) {
1213 MarkDefinitionAsAliased(instr->AsStoreField()->value()->definition());
1214 }
1215 }
1216 }
1217
1218 // Determine if the given definition can't be aliased.
1219 void ComputeAliasing(Definition* alloc) {
1221 ASSERT(alloc->Identity().IsUnknown());
1222 ASSERT(aliasing_worklist_.is_empty());
1223
1224 alloc->SetIdentity(AliasIdentity::NotAliased());
1225 aliasing_worklist_.Add(alloc);
1226
1227 while (!aliasing_worklist_.is_empty()) {
1228 Definition* defn = aliasing_worklist_.RemoveLast();
1230 // If the definition in the worklist was optimistically marked as
1231 // not-aliased check that optimistic assumption still holds: check if
1232 // any of its uses can create an alias.
1233 if (!defn->Identity().IsAliased() && AnyUseCreatesAlias(defn)) {
1234 defn->SetIdentity(AliasIdentity::Aliased());
1235 identity_rollback_.Add(defn);
1236 }
1237
1238 // If the allocation site is marked as aliased conservatively mark
1239 // any values stored into the object aliased too.
1240 if (defn->Identity().IsAliased()) {
1241 MarkStoredValuesEscaping(defn);
1242 }
1243 }
1244 }
1245
1246 Zone* zone_;
1247
1248 const bool print_traces_;
1249
1250 PointerSet<Place>* places_map_;
1251
1252 const ZoneGrowableArray<Place*>& places_;
1253
1254 const PhiPlaceMoves* phi_moves_;
1255
1256 // A list of all seen aliases and a map that allows looking up canonical
1257 // alias object.
1258 GrowableArray<const Place*> aliases_;
1259 PointerSet<const Place> aliases_map_;
1260
1261 SmallSet<Place::ElementSize> typed_data_access_sizes_;
1262
1263 // Maps alias id to set of ids of places representing the alias.
1264 // Place represents an alias if this alias is least generic alias for
1265 // the place.
1266 // (see ToAlias for the definition of least generic alias).
1267 GrowableArray<BitVector*> representatives_;
1268
1269 // Maps alias id to set of ids of places aliased.
1270 GrowableArray<BitVector*> killed_;
1271
1272 // Set of ids of places that can be affected by side-effects other than
1273 // explicit stores (i.e. through calls).
1274 BitVector* aliased_by_effects_;
1275
1276 // Worklist used during alias analysis.
1277 GrowableArray<Definition*> aliasing_worklist_;
1278
1279 // List of definitions that had their identity set to Aliased. At the end
1280 // of load optimization their identity will be rolled back to Unknown to
1281 // avoid treating them as Aliased at later stages without checking first
1282 // as optimizations can potentially eliminate instructions leading to
1283 // aliasing.
1284 GrowableArray<Definition*> identity_rollback_;
1285};
1286
1288 if (instr->IsStoreIndexed()) {
1289 return instr->AsStoreIndexed()->value()->definition();
1290 }
1291
1292 StoreFieldInstr* store_instance_field = instr->AsStoreField();
1293 if (store_instance_field != nullptr) {
1294 return store_instance_field->value()->definition();
1295 }
1296
1297 StoreStaticFieldInstr* store_static_field = instr->AsStoreStaticField();
1298 if (store_static_field != nullptr) {
1299 return store_static_field->value()->definition();
1300 }
1301
1302 UNREACHABLE(); // Should only be called for supported store instructions.
1303 return nullptr;
1304}
1305
1306static bool IsPhiDependentPlace(Place* place) {
1307 return (place->kind() == Place::kInstanceField) &&
1308 (place->instance() != nullptr) && place->instance()->IsPhi();
1309}
1310
1311// For each place that depends on a phi ensure that equivalent places
1312// corresponding to phi input are numbered and record outgoing phi moves
1313// for each block which establish correspondence between phi dependent place
1314// and phi input's place that is flowing in.
1317 bool print_traces) {
1318 Thread* thread = Thread::Current();
1319 Zone* zone = thread->zone();
1320 PhiPlaceMoves* phi_moves = new (zone) PhiPlaceMoves();
1321
1322 for (intptr_t i = 0; i < places->length(); i++) {
1323 Place* place = (*places)[i];
1324
1325 if (IsPhiDependentPlace(place)) {
1326 PhiInstr* phi = place->instance()->AsPhi();
1327 BlockEntryInstr* block = phi->GetBlock();
1328
1329 if (FLAG_trace_optimization && print_traces) {
1330 THR_Print("phi dependent place %s\n", place->ToCString());
1331 }
1332
1333 Place input_place(*place);
1334 for (intptr_t j = 0; j < phi->InputCount(); j++) {
1335 input_place.set_instance(phi->InputAt(j)->definition());
1336
1337 Place* result = map->LookupValue(&input_place);
1338 if (result == nullptr) {
1339 result = Place::Wrap(zone, input_place, places->length());
1340 map->Insert(result);
1341 places->Add(result);
1342 if (FLAG_trace_optimization && print_traces) {
1343 THR_Print(" adding place %s as %" Pd "\n", result->ToCString(),
1344 result->id());
1345 }
1346 }
1347 phi_moves->CreateOutgoingMove(zone, block->PredecessorAt(j),
1348 result->id(), place->id());
1349 }
1350 }
1351 }
1352
1353 return phi_moves;
1354}
1355
1356DART_FORCE_INLINE static void SetPlaceId(Instruction* instr, intptr_t id) {
1357 instr->SetPassSpecificId(CompilerPass::kCSE, id);
1358}
1359
1360DART_FORCE_INLINE static bool HasPlaceId(const Instruction* instr) {
1361 return instr->HasPassSpecificId(CompilerPass::kCSE);
1362}
1363
1364DART_FORCE_INLINE static intptr_t GetPlaceId(const Instruction* instr) {
1365 ASSERT(HasPlaceId(instr));
1366 return instr->GetPassSpecificId(CompilerPass::kCSE);
1367}
1368
1370
1373 CSEMode mode) {
1374 // Loads representing different expression ids will be collected and
1375 // used to build per offset kill sets.
1376 Zone* zone = graph->zone();
1378
1379 bool has_loads = false;
1380 bool has_stores = false;
1381 for (BlockIterator it = graph->reverse_postorder_iterator(); !it.Done();
1382 it.Advance()) {
1383 BlockEntryInstr* block = it.Current();
1384
1385 for (ForwardInstructionIterator instr_it(block); !instr_it.Done();
1386 instr_it.Advance()) {
1387 Instruction* instr = instr_it.Current();
1388 Place place(instr, &has_loads, &has_stores);
1389 if (place.kind() == Place::kNone) {
1390 continue;
1391 }
1392
1393 Place* result = map->LookupValue(&place);
1394 if (result == nullptr) {
1395 result = Place::Wrap(zone, place, places->length());
1396 map->Insert(result);
1397 places->Add(result);
1398
1399 if (FLAG_trace_optimization && graph->should_print()) {
1400 THR_Print("numbering %s as %" Pd "\n", result->ToCString(),
1401 result->id());
1402 }
1403 }
1404
1405 SetPlaceId(instr, result->id());
1406 }
1407 }
1408
1409 if ((mode == kOptimizeLoads) && !has_loads) {
1410 return nullptr;
1411 }
1412 if ((mode == kOptimizeStores) && !has_stores) {
1413 return nullptr;
1414 }
1415
1416 PhiPlaceMoves* phi_moves =
1417 ComputePhiMoves(map, places, graph->should_print());
1418
1419 // Build aliasing sets mapping aliases to loads.
1420 return new (zone)
1421 AliasedSet(zone, map, places, phi_moves, graph->should_print());
1422}
1423
1424// Load instructions handled by load elimination.
1426 if (instr->IsDefinition() &&
1427 instr->AsDefinition()->MayCreateUnsafeUntaggedPointer()) {
1428 return false;
1429 }
1430 if (auto* load = instr->AsLoadField()) {
1431 if (load->slot().is_weak()) {
1432 return false;
1433 }
1434 }
1435 return instr->IsLoadField() || instr->IsLoadIndexed() ||
1436 instr->IsLoadStaticField();
1437}
1438
1440 intptr_t loop_header_index,
1441 Instruction* instr) {
1442 return IsLoadEliminationCandidate(instr) && (sets != nullptr) &&
1443 HasPlaceId(instr) &&
1444 (*sets)[loop_header_index]->Contains(GetPlaceId(instr));
1445}
1446
1447LICM::LICM(FlowGraph* flow_graph) : flow_graph_(flow_graph) {
1448 ASSERT(flow_graph->is_licm_allowed());
1449}
1450
1451void LICM::Hoist(ForwardInstructionIterator* it,
1452 BlockEntryInstr* pre_header,
1453 Instruction* current) {
1454 if (FLAG_trace_optimization && flow_graph_->should_print()) {
1455 THR_Print("Hoisting instruction %s:%" Pd " from B%" Pd " to B%" Pd "\n",
1456 current->DebugName(), current->GetDeoptId(),
1457 current->GetBlock()->block_id(), pre_header->block_id());
1458 }
1459 // Move the instruction out of the loop.
1460 current->RemoveEnvironment();
1461 if (it != nullptr) {
1463 } else {
1464 current->RemoveFromGraph();
1465 }
1466 GotoInstr* last = pre_header->last_instruction()->AsGoto();
1467 // Using kind kEffect will not assign a fresh ssa temporary index.
1468 flow_graph()->InsertBefore(last, current, last->env(), FlowGraph::kEffect);
1469 // If the hoisted instruction lazy-deopts, it should continue at the start of
1470 // the Goto (of which we copy the deopt-id from).
1471 current->env()->MarkAsLazyDeoptToBeforeDeoptId();
1472 current->env()->MarkAsHoisted();
1473 current->CopyDeoptIdFrom(*last);
1474}
1475
1476void LICM::TrySpecializeSmiPhi(PhiInstr* phi,
1477 BlockEntryInstr* header,
1478 BlockEntryInstr* pre_header) {
1479 if (phi->Type()->ToCid() == kSmiCid) {
1480 return;
1481 }
1482
1483 // Check if there is only a single kDynamicCid input to the phi that
1484 // comes from the pre-header.
1485 const intptr_t kNotFound = -1;
1486 intptr_t non_smi_input = kNotFound;
1487 for (intptr_t i = 0; i < phi->InputCount(); ++i) {
1488 Value* input = phi->InputAt(i);
1489 if (input->Type()->ToCid() != kSmiCid) {
1490 if ((non_smi_input != kNotFound) ||
1491 (input->Type()->ToCid() != kDynamicCid)) {
1492 // There are multiple kDynamicCid inputs or there is an input that is
1493 // known to be non-smi.
1494 return;
1495 } else {
1496 non_smi_input = i;
1497 }
1498 }
1499 }
1500
1501 if ((non_smi_input == kNotFound) ||
1502 (phi->block()->PredecessorAt(non_smi_input) != pre_header)) {
1503 return;
1504 }
1505
1506 CheckSmiInstr* check = nullptr;
1507 for (Value* use = phi->input_use_list();
1508 (use != nullptr) && (check == nullptr); use = use->next_use()) {
1509 check = use->instruction()->AsCheckSmi();
1510 }
1511
1512 if (check == nullptr) {
1513 return;
1514 }
1515
1516 // Host CheckSmi instruction and make this phi smi one.
1517 Hoist(nullptr, pre_header, check);
1518
1519 // Replace value we are checking with phi's input.
1520 check->value()->BindTo(phi->InputAt(non_smi_input)->definition());
1521 check->value()->SetReachingType(phi->InputAt(non_smi_input)->Type());
1522
1523 phi->UpdateType(CompileType::FromCid(kSmiCid));
1524}
1525
1527 if (flow_graph()->function().ProhibitsInstructionHoisting() ||
1528 CompilerState::Current().is_aot()) {
1529 // Do not hoist any: Either deoptimized on a hoisted check,
1530 // or compiling precompiled code where we can't do optimistic
1531 // hoisting of checks.
1532 return;
1533 }
1534
1535 const ZoneGrowableArray<BlockEntryInstr*>& loop_headers =
1536 flow_graph()->GetLoopHierarchy().headers();
1537
1538 for (intptr_t i = 0; i < loop_headers.length(); ++i) {
1539 JoinEntryInstr* header = loop_headers[i]->AsJoinEntry();
1540 // Skip loop that don't have a pre-header block.
1541 BlockEntryInstr* pre_header = header->ImmediateDominator();
1542 if (pre_header == nullptr) continue;
1543
1544 for (PhiIterator it(header); !it.Done(); it.Advance()) {
1545 TrySpecializeSmiPhi(it.Current(), header, pre_header);
1546 }
1547 }
1548}
1549
1551 if (flow_graph()->function().ProhibitsInstructionHoisting()) {
1552 // Do not hoist any.
1553 return;
1554 }
1555
1556 // Compute loops and induction in flow graph.
1557 const LoopHierarchy& loop_hierarchy = flow_graph()->GetLoopHierarchy();
1558 const ZoneGrowableArray<BlockEntryInstr*>& loop_headers =
1559 loop_hierarchy.headers();
1560 loop_hierarchy.ComputeInduction();
1561
1562 ZoneGrowableArray<BitVector*>* loop_invariant_loads =
1563 flow_graph()->loop_invariant_loads();
1564
1565 // Iterate over all loops.
1566 for (intptr_t i = 0; i < loop_headers.length(); ++i) {
1567 BlockEntryInstr* header = loop_headers[i];
1568
1569 // Skip loops that don't have a pre-header block.
1570 BlockEntryInstr* pre_header = header->ImmediateDominator();
1571 if (pre_header == nullptr) {
1572 continue;
1573 }
1574
1575 // Flag that remains true as long as the loop has not seen any instruction
1576 // that may have a "visible" effect (write, throw, or other side-effect).
1577 bool seen_visible_effect = false;
1578
1579 // Iterate over all blocks in the loop.
1580 LoopInfo* loop = header->loop_info();
1581 for (BitVector::Iterator loop_it(loop->blocks()); !loop_it.Done();
1582 loop_it.Advance()) {
1583 BlockEntryInstr* block = flow_graph()->preorder()[loop_it.Current()];
1584
1585 // Preserve the "visible" effect flag as long as the preorder traversal
1586 // sees always-taken blocks. This way, we can only hoist invariant
1587 // may-throw instructions that are always seen during the first iteration.
1588 if (!seen_visible_effect && !loop->IsAlwaysTaken(block)) {
1589 seen_visible_effect = true;
1590 }
1591 // Iterate over all instructions in the block.
1592 for (ForwardInstructionIterator it(block); !it.Done(); it.Advance()) {
1593 Instruction* current = it.Current();
1594
1595 // Treat loads of static final fields specially: we can CSE them but
1596 // we should not move them around unless the field is initialized.
1597 // Otherwise we might move load past the initialization.
1598 if (LoadStaticFieldInstr* load = current->AsLoadStaticField()) {
1599 if (load->AllowsCSE()) {
1600 seen_visible_effect = true;
1601 continue;
1602 }
1603 }
1604
1605 // Determine if we can hoist loop invariant code. Even may-throw
1606 // instructions can be hoisted as long as its exception is still
1607 // the very first "visible" effect of the loop.
1608 bool is_loop_invariant = false;
1609 if ((current->AllowsCSE() ||
1610 IsLoopInvariantLoad(loop_invariant_loads, i, current)) &&
1611 (!seen_visible_effect || !current->MayHaveVisibleEffect())) {
1612 is_loop_invariant = true;
1613 for (intptr_t i = 0; i < current->InputCount(); ++i) {
1614 Definition* input_def = current->InputAt(i)->definition();
1615 if (!input_def->GetBlock()->Dominates(pre_header)) {
1616 is_loop_invariant = false;
1617 break;
1618 }
1619 }
1620 }
1621
1622 // Hoist if all inputs are loop invariant. If not hoisted, any
1623 // instruction that writes, may throw, or has an unknown side
1624 // effect invalidates the first "visible" effect flag.
1625 if (is_loop_invariant) {
1626 Hoist(&it, pre_header, current);
1627 } else if (!seen_visible_effect && current->MayHaveVisibleEffect()) {
1628 seen_visible_effect = true;
1629 }
1630 }
1631 }
1632 }
1633}
1634
1636 // Go through all Allocation instructions and move them down to their
1637 // dominant use when doing so is sound.
1639 for (BlockIterator block_it = graph->reverse_postorder_iterator();
1640 !block_it.Done(); block_it.Advance()) {
1641 BlockEntryInstr* block = block_it.Current();
1642
1643 for (ForwardInstructionIterator instr_it(block); !instr_it.Done();
1644 instr_it.Advance()) {
1645 Definition* def = instr_it.Current()->AsDefinition();
1646 if (def != nullptr && def->IsAllocation() && def->env() == nullptr &&
1647 !moved.HasKey(def)) {
1648 Instruction* use = DominantUse(def);
1649 if (use != nullptr && !use->IsPhi() && IsOneTimeUse(use, def)) {
1650 instr_it.RemoveCurrentFromGraph();
1651 def->InsertBefore(use);
1652 moved.Insert(def);
1653 }
1654 }
1655 }
1656 }
1657}
1658
1659Instruction* DelayAllocations::DominantUse(Definition* def) {
1660 // Find the use that dominates all other uses.
1661
1662 // Collect all uses.
1664 for (Value::Iterator it(def->input_use_list()); !it.Done(); it.Advance()) {
1665 Instruction* use = it.Current()->instruction();
1666 uses.Insert(use);
1667 }
1668 for (Value::Iterator it(def->env_use_list()); !it.Done(); it.Advance()) {
1669 Instruction* use = it.Current()->instruction();
1670 uses.Insert(use);
1671 }
1672
1673 // Returns |true| iff |instr| or any instruction dominating it are either a
1674 // a |def| or a use of a |def|.
1675 auto is_dominated_by_another_use = [&](Instruction* instr) {
1676 while (instr != def) {
1677 if (uses.HasKey(instr)) {
1678 // We hit another use, meaning that this use dominates the given |use|.
1679 return true;
1680 }
1681 if (auto block = instr->AsBlockEntry()) {
1682 instr = block->dominator()->last_instruction();
1683 } else {
1684 instr = instr->previous();
1685 }
1686 }
1687 return false;
1688 };
1689
1690 // Find the dominant use.
1691 Instruction* dominant_use = nullptr;
1692 auto use_it = uses.GetIterator();
1693 while (auto use = use_it.Next()) {
1694 bool dominated_by_another_use = false;
1695
1696 if (auto phi = (*use)->AsPhi()) {
1697 // For phi uses check that the dominant use dominates all
1698 // predecessor blocks corresponding to matching phi inputs.
1699 ASSERT(phi->InputCount() == phi->block()->PredecessorCount());
1700 dominated_by_another_use = true;
1701 for (intptr_t i = 0; i < phi->InputCount(); i++) {
1702 if (phi->InputAt(i)->definition() == def) {
1703 if (!is_dominated_by_another_use(
1704 phi->block()->PredecessorAt(i)->last_instruction())) {
1705 dominated_by_another_use = false;
1706 break;
1707 }
1708 }
1709 }
1710 } else {
1711 // Start with the instruction before the use, then walk backwards through
1712 // blocks in the dominator chain until we hit the definition or
1713 // another use.
1714 dominated_by_another_use =
1715 is_dominated_by_another_use((*use)->previous());
1716 }
1717
1718 if (!dominated_by_another_use) {
1719 if (dominant_use != nullptr) {
1720 // More than one use reached the definition, which means no use
1721 // dominates all other uses.
1722 return nullptr;
1723 }
1724 dominant_use = *use;
1725 }
1726 }
1727
1728 return dominant_use;
1729}
1730
1731bool DelayAllocations::IsOneTimeUse(Instruction* use, Definition* def) {
1732 // Check that this use is always executed at most once for each execution of
1733 // the definition, i.e. that there is no path from the use to itself that
1734 // doesn't pass through the definition.
1735 BlockEntryInstr* use_block = use->GetBlock();
1736 BlockEntryInstr* def_block = def->GetBlock();
1737 if (use_block == def_block) return true;
1738
1739 DirectChainedHashMap<IdentitySetKeyValueTrait<BlockEntryInstr*>> seen;
1740 GrowableArray<BlockEntryInstr*> worklist;
1741 worklist.Add(use_block);
1742
1743 while (!worklist.is_empty()) {
1744 BlockEntryInstr* block = worklist.RemoveLast();
1745 for (intptr_t i = 0; i < block->PredecessorCount(); i++) {
1746 BlockEntryInstr* pred = block->PredecessorAt(i);
1747 if (pred == use_block) return false;
1748 if (pred == def_block) continue;
1749 if (seen.HasKey(pred)) continue;
1750 seen.Insert(pred);
1751 worklist.Add(pred);
1752 }
1753 }
1754 return true;
1755}
1756
1758 public:
1759 LoadOptimizer(FlowGraph* graph, AliasedSet* aliased_set)
1760 : graph_(graph),
1761 aliased_set_(aliased_set),
1762 in_(graph_->preorder().length()),
1763 out_(graph_->preorder().length()),
1764 gen_(graph_->preorder().length()),
1765 kill_(graph_->preorder().length()),
1766 exposed_values_(graph_->preorder().length()),
1767 out_values_(graph_->preorder().length()),
1768 phis_(5),
1769 worklist_(5),
1770 congruency_worklist_(6),
1771 in_worklist_(nullptr),
1772 forwarded_(false) {
1773 const intptr_t num_blocks = graph_->preorder().length();
1774 for (intptr_t i = 0; i < num_blocks; i++) {
1775 out_.Add(nullptr);
1776 gen_.Add(new (Z) BitVector(Z, aliased_set_->max_place_id()));
1777 kill_.Add(new (Z) BitVector(Z, aliased_set_->max_place_id()));
1778 in_.Add(new (Z) BitVector(Z, aliased_set_->max_place_id()));
1779
1780 exposed_values_.Add(nullptr);
1781 out_values_.Add(nullptr);
1782 }
1783 }
1784
1786
1787 Zone* zone() const { return graph_->zone(); }
1788
1789 static bool OptimizeGraph(FlowGraph* graph) {
1790 ASSERT(FLAG_load_cse);
1791
1792 // For now, bail out for large functions to avoid OOM situations.
1793 // TODO(fschneider): Fix the memory consumption issue.
1794 if (graph->is_huge_method()) {
1795 return false;
1796 }
1797
1799 AliasedSet* aliased_set = NumberPlaces(graph, &map, kOptimizeLoads);
1800 if ((aliased_set != nullptr) && !aliased_set->IsEmpty()) {
1801 // If any loads were forwarded return true from Optimize to run load
1802 // forwarding again. This will allow to forward chains of loads.
1803 // This is especially important for context variables as they are built
1804 // as loads from loaded context.
1805 // TODO(vegorov): renumber newly discovered congruences during the
1806 // forwarding to forward chains without running whole pass twice.
1807 LoadOptimizer load_optimizer(graph, aliased_set);
1808 return load_optimizer.Optimize();
1809 }
1810 return false;
1811 }
1812
1813 private:
1814 bool Optimize() {
1815 // Initializer calls should be eliminated before ComputeInitialSets()
1816 // in order to calculate kill sets more precisely.
1817 OptimizeLazyInitialization();
1818
1819 ComputeInitialSets();
1820 ComputeOutSets();
1821 ComputeOutValues();
1822 if (graph_->is_licm_allowed()) {
1823 MarkLoopInvariantLoads();
1824 }
1825 ForwardLoads();
1826 EmitPhis();
1827 return forwarded_;
1828 }
1829
1830 bool CallsInitializer(Instruction* instr) {
1831 if (auto* load_field = instr->AsLoadField()) {
1832 return load_field->calls_initializer();
1833 } else if (auto* load_static = instr->AsLoadStaticField()) {
1834 return load_static->calls_initializer();
1835 }
1836 return false;
1837 }
1838
1839 void ClearCallsInitializer(Instruction* instr) {
1840 if (auto* load_field = instr->AsLoadField()) {
1841 load_field->set_calls_initializer(false);
1842 } else if (auto* load_static = instr->AsLoadStaticField()) {
1843 load_static->set_calls_initializer(false);
1844 } else {
1845 UNREACHABLE();
1846 }
1847 }
1848
1849 bool CanForwardLoadTo(Definition* load, Definition* replacement) {
1850 // Loads which check initialization status can only be replaced if
1851 // we can guarantee that forwarded value is not a sentinel.
1852 return !(CallsInitializer(load) && replacement->Type()->can_be_sentinel());
1853 }
1854
1855 // Returns true if given instruction stores the sentinel value.
1856 // Such a store doesn't initialize corresponding field.
1857 bool IsSentinelStore(Instruction* instr) {
1858 Value* value = nullptr;
1859 if (auto* store_field = instr->AsStoreField()) {
1860 value = store_field->value();
1861 } else if (auto* store_static = instr->AsStoreStaticField()) {
1862 value = store_static->value();
1863 }
1864 return value != nullptr && value->BindsToConstant() &&
1865 (value->BoundConstant().ptr() == Object::sentinel().ptr());
1866 }
1867
1868 // This optimization pass tries to get rid of lazy initializer calls in
1869 // LoadField and LoadStaticField instructions. The "initialized" state of
1870 // places is propagated through the flow graph.
1871 void OptimizeLazyInitialization() {
1872 if (!FLAG_optimize_lazy_initializer_calls) {
1873 return;
1874 }
1875
1876 // 1) Populate 'gen' sets with places which are initialized at each basic
1877 // block. Optimize lazy initializer calls within basic block and
1878 // figure out if there are lazy initializer calls left to optimize.
1879 bool has_lazy_initializer_calls = false;
1880 for (BlockIterator block_it = graph_->reverse_postorder_iterator();
1881 !block_it.Done(); block_it.Advance()) {
1882 BlockEntryInstr* block = block_it.Current();
1883 BitVector* gen = gen_[block->preorder_number()];
1884
1885 for (ForwardInstructionIterator instr_it(block); !instr_it.Done();
1886 instr_it.Advance()) {
1887 Instruction* instr = instr_it.Current();
1888
1889 bool is_load = false, is_store = false;
1890 Place place(instr, &is_load, &is_store);
1891
1892 if (is_store && !IsSentinelStore(instr)) {
1893 gen->Add(GetPlaceId(instr));
1894 } else if (is_load) {
1895 const auto place_id = GetPlaceId(instr);
1896 if (CallsInitializer(instr)) {
1897 if (gen->Contains(place_id)) {
1898 ClearCallsInitializer(instr);
1899 } else {
1900 has_lazy_initializer_calls = true;
1901 }
1902 }
1903 gen->Add(place_id);
1904 }
1905 }
1906
1907 // Spread initialized state through outgoing phis.
1908 PhiPlaceMoves::MovesList phi_moves =
1909 aliased_set_->phi_moves()->GetOutgoingMoves(block);
1910 if (phi_moves != nullptr) {
1911 for (intptr_t i = 0, n = phi_moves->length(); i < n; ++i) {
1912 const intptr_t from = (*phi_moves)[i].from();
1913 const intptr_t to = (*phi_moves)[i].to();
1914 if ((from != to) && gen->Contains(from)) {
1915 gen->Add(to);
1916 }
1917 }
1918 }
1919 }
1920
1921 if (has_lazy_initializer_calls) {
1922 // 2) Propagate initialized state between blocks, calculating
1923 // incoming initialized state. Iterate until reaching fixed point.
1924 BitVector* temp = new (Z) BitVector(Z, aliased_set_->max_place_id());
1925 bool changed = true;
1926 while (changed) {
1927 changed = false;
1928
1929 for (BlockIterator block_it = graph_->reverse_postorder_iterator();
1930 !block_it.Done(); block_it.Advance()) {
1931 BlockEntryInstr* block = block_it.Current();
1932 BitVector* block_in = in_[block->preorder_number()];
1933 BitVector* gen = gen_[block->preorder_number()];
1934
1935 // Incoming initialized state is the intersection of all
1936 // outgoing initialized states of predecessors.
1937 if (block->IsGraphEntry()) {
1938 temp->Clear();
1939 } else {
1940 temp->SetAll();
1941 ASSERT(block->PredecessorCount() > 0);
1942 for (intptr_t i = 0, pred_count = block->PredecessorCount();
1943 i < pred_count; ++i) {
1944 BlockEntryInstr* pred = block->PredecessorAt(i);
1945 BitVector* pred_out = gen_[pred->preorder_number()];
1946 temp->Intersect(pred_out);
1947 }
1948 }
1949
1950 if (!temp->Equals(*block_in)) {
1951 ASSERT(block_in->SubsetOf(*temp));
1952 block_in->AddAll(temp);
1953 gen->AddAll(temp);
1954 changed = true;
1955 }
1956 }
1957 }
1958
1959 // 3) Single pass through basic blocks to optimize lazy
1960 // initializer calls using calculated incoming inter-block
1961 // initialized state.
1962 for (BlockIterator block_it = graph_->reverse_postorder_iterator();
1963 !block_it.Done(); block_it.Advance()) {
1964 BlockEntryInstr* block = block_it.Current();
1965 BitVector* block_in = in_[block->preorder_number()];
1966
1967 for (ForwardInstructionIterator instr_it(block); !instr_it.Done();
1968 instr_it.Advance()) {
1969 Instruction* instr = instr_it.Current();
1970 if (CallsInitializer(instr) &&
1971 block_in->Contains(GetPlaceId(instr))) {
1972 ClearCallsInitializer(instr);
1973 }
1974 }
1975 }
1976 }
1977
1978 // Clear sets which are also used in the main part of load forwarding.
1979 for (intptr_t i = 0, n = graph_->preorder().length(); i < n; ++i) {
1980 gen_[i]->Clear();
1981 in_[i]->Clear();
1982 }
1983 }
1984
1985 // Only forward stores to normal arrays, float64, and simd arrays
1986 // to loads because other array stores (intXX/uintXX/float32)
1987 // may implicitly convert the value stored.
1988 bool CanForwardStore(StoreIndexedInstr* array_store) {
1989 if (array_store == nullptr) return true;
1991 array_store->class_id());
1992 return !RepresentationUtils::IsUnboxedInteger(rep) && rep != kUnboxedFloat;
1993 }
1994
1995 static bool AlreadyPinnedByRedefinition(Definition* replacement,
1996 Definition* redefinition) {
1997 Definition* defn = replacement;
1998 if (auto load_field = replacement->AsLoadField()) {
1999 defn = load_field->instance()->definition();
2000 } else if (auto load_indexed = replacement->AsLoadIndexed()) {
2001 defn = load_indexed->array()->definition();
2002 }
2003
2004 Value* unwrapped;
2005 while ((unwrapped = defn->RedefinedValue()) != nullptr) {
2006 if (defn == redefinition) {
2007 return true;
2008 }
2009 defn = unwrapped->definition();
2010 }
2011
2012 return false;
2013 }
2014
2015 Definition* ReplaceLoad(Definition* load, Definition* replacement) {
2016 // When replacing a load from a generic field or from an array element
2017 // check if instance we are loading from is redefined. If it is then
2018 // we need to ensure that replacement is not going to break the
2019 // dependency chain.
2020 Definition* redef = nullptr;
2021 if (auto load_field = load->AsLoadField()) {
2022 auto instance = load_field->instance()->definition();
2023 if (instance->RedefinedValue() != nullptr) {
2024 if ((load_field->slot().kind() ==
2025 Slot::Kind::kGrowableObjectArray_data ||
2026 (load_field->slot().IsDartField() &&
2027 !AbstractType::Handle(load_field->slot().field().type())
2028 .IsInstantiated()))) {
2029 redef = instance;
2030 }
2031 }
2032 } else if (auto load_indexed = load->AsLoadIndexed()) {
2033 if (load_indexed->class_id() == kArrayCid ||
2034 load_indexed->class_id() == kImmutableArrayCid) {
2035 auto instance = load_indexed->array()->definition();
2036 if (instance->RedefinedValue() != nullptr) {
2037 redef = instance;
2038 }
2039 }
2040 }
2041 if (redef != nullptr && !AlreadyPinnedByRedefinition(replacement, redef)) {
2042 // Original load had a redefined instance and replacement does not
2043 // depend on the same redefinition. Create a redefinition
2044 // of the replacement to keep the dependency chain.
2045 auto replacement_redefinition =
2046 new (zone()) RedefinitionInstr(new (zone()) Value(replacement));
2047 if (redef->IsDominatedBy(replacement)) {
2048 graph_->InsertAfter(redef, replacement_redefinition, /*env=*/nullptr,
2050 } else {
2051 graph_->InsertBefore(load, replacement_redefinition, /*env=*/nullptr,
2053 }
2054 replacement = replacement_redefinition;
2055 }
2056
2057 load->ReplaceUsesWith(replacement);
2058 return replacement;
2059 }
2060
2061 // Compute sets of loads generated and killed by each block.
2062 // Additionally compute upwards exposed and generated loads for each block.
2063 // Exposed loads are those that can be replaced if a corresponding
2064 // reaching load will be found.
2065 // Loads that are locally redundant will be replaced as we go through
2066 // instructions.
2067 void ComputeInitialSets() {
2068 for (BlockIterator block_it = graph_->reverse_postorder_iterator();
2069 !block_it.Done(); block_it.Advance()) {
2070 BlockEntryInstr* block = block_it.Current();
2071 const intptr_t preorder_number = block->preorder_number();
2072
2073 BitVector* kill = kill_[preorder_number];
2074 BitVector* gen = gen_[preorder_number];
2075
2076 ZoneGrowableArray<Definition*>* exposed_values = nullptr;
2077 ZoneGrowableArray<Definition*>* out_values = nullptr;
2078
2079 for (ForwardInstructionIterator instr_it(block); !instr_it.Done();
2080 instr_it.Advance()) {
2081 Instruction* instr = instr_it.Current();
2082
2083 bool is_load = false, is_store = false;
2084 Place place(instr, &is_load, &is_store);
2085
2086 BitVector* killed = nullptr;
2087 if (is_store) {
2088 const intptr_t alias_id =
2089 aliased_set_->LookupAliasId(place.ToAlias());
2090 if (alias_id != AliasedSet::kNoAlias) {
2091 killed = aliased_set_->GetKilledSet(alias_id);
2092 } else if (!place.IsImmutableField()) {
2093 // We encountered unknown alias: this means intrablock load
2094 // forwarding refined parameter of this store, for example
2095 //
2096 // o <- alloc()
2097 // a.f <- o
2098 // u <- a.f
2099 // u.x <- null ;; this store alias is *.x
2100 //
2101 // after intrablock load forwarding
2102 //
2103 // o <- alloc()
2104 // a.f <- o
2105 // o.x <- null ;; this store alias is o.x
2106 //
2107 // In this case we fallback to using place id recorded in the
2108 // instruction that still points to the old place with a more
2109 // generic alias.
2110 const intptr_t old_alias_id = aliased_set_->LookupAliasId(
2111 aliased_set_->places()[GetPlaceId(instr)]->ToAlias());
2112 killed = aliased_set_->GetKilledSet(old_alias_id);
2113 }
2114
2115 // Find canonical place of store.
2116 Place* canonical_place = nullptr;
2117 if (CanForwardStore(instr->AsStoreIndexed())) {
2118 canonical_place = aliased_set_->LookupCanonical(&place);
2119 if (canonical_place != nullptr) {
2120 // Is this a redundant store (stored value already resides
2121 // in this field)?
2122 const intptr_t place_id = canonical_place->id();
2123 if (gen->Contains(place_id)) {
2124 ASSERT((out_values != nullptr) &&
2125 ((*out_values)[place_id] != nullptr));
2126 if ((*out_values)[place_id] == GetStoredValue(instr)) {
2127 if (FLAG_trace_optimization && graph_->should_print()) {
2128 THR_Print("Removing redundant store to place %" Pd
2129 " in block B%" Pd "\n",
2130 GetPlaceId(instr), block->block_id());
2131 }
2132 instr_it.RemoveCurrentFromGraph();
2133 continue;
2134 }
2135 }
2136 }
2137 }
2138
2139 // Update kill/gen/out_values (after inspection of incoming values).
2140 if (killed != nullptr) {
2141 kill->AddAll(killed);
2142 // There is no need to clear out_values when clearing GEN set
2143 // because only those values that are in the GEN set
2144 // will ever be used.
2145 gen->RemoveAll(killed);
2146 }
2147 if (canonical_place != nullptr) {
2148 // Store has a corresponding numbered place that might have a
2149 // load. Try forwarding stored value to it.
2150 gen->Add(canonical_place->id());
2151 if (out_values == nullptr) out_values = CreateBlockOutValues();
2152 (*out_values)[canonical_place->id()] = GetStoredValue(instr);
2153 }
2154
2155 ASSERT(!instr->IsDefinition() ||
2156 !IsLoadEliminationCandidate(instr->AsDefinition()));
2157 continue;
2158 } else if (is_load) {
2159 // Check if this load needs renumbering because of the intrablock
2160 // load forwarding.
2161 const Place* canonical = aliased_set_->LookupCanonical(&place);
2162 if ((canonical != nullptr) &&
2163 (canonical->id() != GetPlaceId(instr->AsDefinition()))) {
2164 SetPlaceId(instr->AsDefinition(), canonical->id());
2165 }
2166 }
2167
2168 // If instruction has effects then kill all loads affected.
2169 if (instr->HasUnknownSideEffects()) {
2170 kill->AddAll(aliased_set_->aliased_by_effects());
2171 // There is no need to clear out_values when removing values from GEN
2172 // set because only those values that are in the GEN set
2173 // will ever be used.
2174 gen->RemoveAll(aliased_set_->aliased_by_effects());
2175 }
2176
2177 Definition* defn = instr->AsDefinition();
2178 if (defn == nullptr) {
2179 continue;
2180 }
2181
2182 if (auto* const alloc = instr->AsAllocation()) {
2183 if (!alloc->ObjectIsInitialized()) {
2184 // Since the allocated object is uninitialized, we can't forward
2185 // any values from it.
2186 continue;
2187 }
2188 for (Value* use = alloc->input_use_list(); use != nullptr;
2189 use = use->next_use()) {
2190 if (use->use_index() != 0) {
2191 // Not a potential immediate load or store, since they take the
2192 // instance as the first input.
2193 continue;
2194 }
2195 intptr_t place_id = -1;
2196 Definition* forward_def = nullptr;
2197 const Slot* slot = nullptr;
2198 if (auto* const load = use->instruction()->AsLoadField()) {
2199 place_id = GetPlaceId(load);
2200 slot = &load->slot();
2201 } else if (auto* const store = use->instruction()->AsStoreField()) {
2202 ASSERT(!alloc->IsArrayAllocation());
2203 place_id = GetPlaceId(store);
2204 slot = &store->slot();
2205 } else if (use->instruction()->IsLoadIndexed() ||
2206 use->instruction()->IsStoreIndexed()) {
2207 if (!alloc->IsArrayAllocation()) {
2208 // Non-array allocations can be accessed with LoadIndexed
2209 // and StoreIndex in the unreachable code.
2210 continue;
2211 }
2212 if (alloc->IsAllocateTypedData()) {
2213 // Typed data payload elements are unboxed and initialized to
2214 // zero, so don't forward a tagged null value.
2215 continue;
2216 }
2217 if (aliased_set_->CanBeAliased(alloc)) {
2218 continue;
2219 }
2220 place_id = GetPlaceId(use->instruction());
2221 if (aliased_set_->places()[place_id]->kind() !=
2223 continue;
2224 }
2225 // Set initial value of array element to null.
2226 forward_def = graph_->constant_null();
2227 } else {
2228 // Not an immediate load or store.
2229 continue;
2230 }
2231
2232 ASSERT(place_id != -1);
2233 if (slot != nullptr) {
2234 ASSERT(forward_def == nullptr);
2235 // Final fields are initialized in constructors. However, at the
2236 // same time we assume that known values of final fields can be
2237 // forwarded across side-effects. For an escaping object, one such
2238 // side effect can be an uninlined constructor invocation. Thus,
2239 // if we add 'null' as known initial values for these fields,
2240 // this null will be incorrectly propagated across any uninlined
2241 // constructor invocation and used instead of the real value.
2242 if (aliased_set_->CanBeAliased(alloc) && slot->IsDartField() &&
2243 slot->is_immutable()) {
2244 continue;
2245 }
2246
2247 const intptr_t pos = alloc->InputForSlot(*slot);
2248 if (pos != -1) {
2249 forward_def = alloc->InputAt(pos)->definition();
2250 } else if (!slot->is_tagged()) {
2251 // Fields that do not contain tagged values should not
2252 // have a tagged null value forwarded for them, similar to
2253 // payloads of typed data arrays.
2254 continue;
2255 } else {
2256 // Fields not provided as an input to the instruction are
2257 // initialized to null during allocation.
2258 forward_def = graph_->constant_null();
2259 }
2260 }
2261
2262 ASSERT(forward_def != nullptr);
2263 gen->Add(place_id);
2264 if (out_values == nullptr) out_values = CreateBlockOutValues();
2265 (*out_values)[place_id] = forward_def;
2266 }
2267 continue;
2268 }
2269
2270 if (!IsLoadEliminationCandidate(defn)) {
2271 continue;
2272 }
2273
2274 const intptr_t place_id = GetPlaceId(defn);
2275 if (gen->Contains(place_id)) {
2276 // This is a locally redundant load.
2277 ASSERT((out_values != nullptr) &&
2278 ((*out_values)[place_id] != nullptr));
2279
2280 Definition* replacement = (*out_values)[place_id];
2281 if (CanForwardLoadTo(defn, replacement)) {
2282 graph_->EnsureSSATempIndex(defn, replacement);
2283 if (FLAG_trace_optimization && graph_->should_print()) {
2284 THR_Print("Replacing load v%" Pd " with v%" Pd "\n",
2285 defn->ssa_temp_index(), replacement->ssa_temp_index());
2286 }
2287
2288 ReplaceLoad(defn, replacement);
2289 instr_it.RemoveCurrentFromGraph();
2290 forwarded_ = true;
2291 continue;
2292 }
2293 } else if (!kill->Contains(place_id)) {
2294 // This is an exposed load: it is the first representative of a
2295 // given expression id and it is not killed on the path from
2296 // the block entry.
2297 if (exposed_values == nullptr) {
2298 const intptr_t kMaxExposedValuesInitialSize = 5;
2299 exposed_values = new (Z) ZoneGrowableArray<Definition*>(
2300 Utils::Minimum(kMaxExposedValuesInitialSize,
2301 aliased_set_->max_place_id()));
2302 }
2303
2304 exposed_values->Add(defn);
2305 }
2306
2307 gen->Add(place_id);
2308
2309 if (out_values == nullptr) out_values = CreateBlockOutValues();
2310 (*out_values)[place_id] = defn;
2311 }
2312
2313 exposed_values_[preorder_number] = exposed_values;
2314 out_values_[preorder_number] = out_values;
2315 }
2316 }
2317
2318 static void PerformPhiMoves(PhiPlaceMoves::MovesList phi_moves,
2319 BitVector* out,
2320 BitVector* forwarded_loads) {
2321 forwarded_loads->Clear();
2322
2323 for (intptr_t i = 0; i < phi_moves->length(); i++) {
2324 const intptr_t from = (*phi_moves)[i].from();
2325 const intptr_t to = (*phi_moves)[i].to();
2326 if (from == to) continue;
2327
2328 if (out->Contains(from)) {
2329 forwarded_loads->Add(to);
2330 }
2331 }
2332
2333 for (intptr_t i = 0; i < phi_moves->length(); i++) {
2334 const intptr_t from = (*phi_moves)[i].from();
2335 const intptr_t to = (*phi_moves)[i].to();
2336 if (from == to) continue;
2337
2338 out->Remove(to);
2339 }
2340
2341 out->AddAll(forwarded_loads);
2342 }
2343
2344 // Compute OUT sets by propagating them iteratively until fix point
2345 // is reached.
2346 void ComputeOutSets() {
2347 BitVector* temp = new (Z) BitVector(Z, aliased_set_->max_place_id());
2348 BitVector* forwarded_loads =
2349 new (Z) BitVector(Z, aliased_set_->max_place_id());
2350 BitVector* temp_out = new (Z) BitVector(Z, aliased_set_->max_place_id());
2351
2352 bool changed = true;
2353 while (changed) {
2354 changed = false;
2355
2356 for (BlockIterator block_it = graph_->reverse_postorder_iterator();
2357 !block_it.Done(); block_it.Advance()) {
2358 BlockEntryInstr* block = block_it.Current();
2359
2360 const intptr_t preorder_number = block->preorder_number();
2361
2362 BitVector* block_in = in_[preorder_number];
2363 BitVector* block_out = out_[preorder_number];
2364 BitVector* block_kill = kill_[preorder_number];
2365 BitVector* block_gen = gen_[preorder_number];
2366
2367 // Compute block_in as the intersection of all out(p) where p
2368 // is a predecessor of the current block.
2369 if (block->IsGraphEntry()) {
2370 temp->Clear();
2371 } else {
2372 temp->SetAll();
2373 ASSERT(block->PredecessorCount() > 0);
2374 for (intptr_t i = 0; i < block->PredecessorCount(); i++) {
2375 BlockEntryInstr* pred = block->PredecessorAt(i);
2376 BitVector* pred_out = out_[pred->preorder_number()];
2377 if (pred_out == nullptr) continue;
2378 PhiPlaceMoves::MovesList phi_moves =
2379 aliased_set_->phi_moves()->GetOutgoingMoves(pred);
2380 if (phi_moves != nullptr) {
2381 // If there are phi moves, perform intersection with
2382 // a copy of pred_out where the phi moves are applied.
2383 temp_out->CopyFrom(pred_out);
2384 PerformPhiMoves(phi_moves, temp_out, forwarded_loads);
2385 pred_out = temp_out;
2386 }
2387 temp->Intersect(pred_out);
2388 }
2389 }
2390
2391 if (!temp->Equals(*block_in) || (block_out == nullptr)) {
2392 // If IN set has changed propagate the change to OUT set.
2393 block_in->CopyFrom(temp);
2394
2395 temp->RemoveAll(block_kill);
2396 temp->AddAll(block_gen);
2397
2398 if ((block_out == nullptr) || !block_out->Equals(*temp)) {
2399 if (block_out == nullptr) {
2400 block_out = out_[preorder_number] =
2401 new (Z) BitVector(Z, aliased_set_->max_place_id());
2402 }
2403 block_out->CopyFrom(temp);
2404 changed = true;
2405 }
2406 }
2407 }
2408 }
2409 }
2410
2411 // Compute out_values mappings by propagating them in reverse postorder once
2412 // through the graph. Generate phis on back edges where eager merge is
2413 // impossible.
2414 // No replacement is done at this point and thus any out_value[place_id] is
2415 // changed at most once: from nullptr to an actual value.
2416 // When merging incoming loads we might need to create a phi.
2417 // These phis are not inserted at the graph immediately because some of them
2418 // might become redundant after load forwarding is done.
2419 void ComputeOutValues() {
2420 GrowableArray<PhiInstr*> pending_phis(5);
2421 ZoneGrowableArray<Definition*>* temp_forwarded_values = nullptr;
2422
2423 for (BlockIterator block_it = graph_->reverse_postorder_iterator();
2424 !block_it.Done(); block_it.Advance()) {
2425 BlockEntryInstr* block = block_it.Current();
2426
2427 const bool can_merge_eagerly = CanMergeEagerly(block);
2428
2429 const intptr_t preorder_number = block->preorder_number();
2430
2431 ZoneGrowableArray<Definition*>* block_out_values =
2432 out_values_[preorder_number];
2433
2434 // If OUT set has changed then we have new values available out of
2435 // the block. Compute these values creating phi where necessary.
2436 for (BitVector::Iterator it(out_[preorder_number]); !it.Done();
2437 it.Advance()) {
2438 const intptr_t place_id = it.Current();
2439
2440 if (block_out_values == nullptr) {
2441 out_values_[preorder_number] = block_out_values =
2442 CreateBlockOutValues();
2443 }
2444
2445 if ((*block_out_values)[place_id] == nullptr) {
2446 ASSERT(block->PredecessorCount() > 0);
2447 Definition* in_value = can_merge_eagerly
2448 ? MergeIncomingValues(block, place_id)
2449 : nullptr;
2450 if ((in_value == nullptr) &&
2451 (in_[preorder_number]->Contains(place_id))) {
2452 PhiInstr* phi = new (Z)
2453 PhiInstr(block->AsJoinEntry(), block->PredecessorCount());
2454 SetPlaceId(phi, place_id);
2455 pending_phis.Add(phi);
2456 in_value = phi;
2457 }
2458 (*block_out_values)[place_id] = in_value;
2459 }
2460 }
2461
2462 // If the block has outgoing phi moves perform them. Use temporary list
2463 // of values to ensure that cyclic moves are performed correctly.
2464 PhiPlaceMoves::MovesList phi_moves =
2465 aliased_set_->phi_moves()->GetOutgoingMoves(block);
2466 if ((phi_moves != nullptr) && (block_out_values != nullptr)) {
2467 if (temp_forwarded_values == nullptr) {
2468 temp_forwarded_values = CreateBlockOutValues();
2469 }
2470
2471 for (intptr_t i = 0; i < phi_moves->length(); i++) {
2472 const intptr_t from = (*phi_moves)[i].from();
2473 const intptr_t to = (*phi_moves)[i].to();
2474 if (from == to) continue;
2475
2476 (*temp_forwarded_values)[to] = (*block_out_values)[from];
2477 }
2478
2479 for (intptr_t i = 0; i < phi_moves->length(); i++) {
2480 const intptr_t from = (*phi_moves)[i].from();
2481 const intptr_t to = (*phi_moves)[i].to();
2482 if (from == to) continue;
2483
2484 (*block_out_values)[to] = (*temp_forwarded_values)[to];
2485 }
2486 }
2487
2488 if (FLAG_trace_load_optimization && graph_->should_print()) {
2489 THR_Print("B%" Pd "\n", block->block_id());
2490 THR_Print(" IN: ");
2491 aliased_set_->PrintSet(in_[preorder_number]);
2492 THR_Print("\n");
2493
2494 THR_Print(" KILL: ");
2495 aliased_set_->PrintSet(kill_[preorder_number]);
2496 THR_Print("\n");
2497
2498 THR_Print(" OUT: ");
2499 aliased_set_->PrintSet(out_[preorder_number]);
2500 THR_Print("\n");
2501 }
2502 }
2503
2504 // All blocks were visited. Fill pending phis with inputs
2505 // that flow on back edges.
2506 for (intptr_t i = 0; i < pending_phis.length(); i++) {
2507 FillPhiInputs(pending_phis[i]);
2508 }
2509 }
2510
2511 bool CanMergeEagerly(BlockEntryInstr* block) {
2512 for (intptr_t i = 0; i < block->PredecessorCount(); i++) {
2513 BlockEntryInstr* pred = block->PredecessorAt(i);
2514 if (pred->postorder_number() < block->postorder_number()) {
2515 return false;
2516 }
2517 }
2518 return true;
2519 }
2520
2521 void MarkLoopInvariantLoads() {
2522 const ZoneGrowableArray<BlockEntryInstr*>& loop_headers =
2523 graph_->GetLoopHierarchy().headers();
2524
2525 ZoneGrowableArray<BitVector*>* invariant_loads =
2526 new (Z) ZoneGrowableArray<BitVector*>(loop_headers.length());
2527
2528 for (intptr_t i = 0; i < loop_headers.length(); i++) {
2529 BlockEntryInstr* header = loop_headers[i];
2530 BlockEntryInstr* pre_header = header->ImmediateDominator();
2531 if (pre_header == nullptr) {
2532 invariant_loads->Add(nullptr);
2533 continue;
2534 }
2535
2536 BitVector* loop_gen = new (Z) BitVector(Z, aliased_set_->max_place_id());
2537 for (BitVector::Iterator loop_it(header->loop_info()->blocks());
2538 !loop_it.Done(); loop_it.Advance()) {
2539 const intptr_t preorder_number = loop_it.Current();
2540 loop_gen->AddAll(gen_[preorder_number]);
2541 }
2542
2543 for (BitVector::Iterator loop_it(header->loop_info()->blocks());
2544 !loop_it.Done(); loop_it.Advance()) {
2545 const intptr_t preorder_number = loop_it.Current();
2546 loop_gen->RemoveAll(kill_[preorder_number]);
2547 }
2548
2549 if (FLAG_trace_optimization && graph_->should_print()) {
2550 for (BitVector::Iterator it(loop_gen); !it.Done(); it.Advance()) {
2551 THR_Print("place %s is loop invariant for B%" Pd "\n",
2552 aliased_set_->places()[it.Current()]->ToCString(),
2553 header->block_id());
2554 }
2555 }
2556
2557 invariant_loads->Add(loop_gen);
2558 }
2559
2560 graph_->set_loop_invariant_loads(invariant_loads);
2561 }
2562
2563 // Compute incoming value for the given expression id.
2564 // Will create a phi if different values are incoming from multiple
2565 // predecessors.
2566 Definition* MergeIncomingValues(BlockEntryInstr* block, intptr_t place_id) {
2567 // First check if the same value is coming in from all predecessors.
2568 static Definition* const kDifferentValuesMarker =
2569 reinterpret_cast<Definition*>(-1);
2570 Definition* incoming = nullptr;
2571 for (intptr_t i = 0; i < block->PredecessorCount(); i++) {
2572 BlockEntryInstr* pred = block->PredecessorAt(i);
2573 ZoneGrowableArray<Definition*>* pred_out_values =
2574 out_values_[pred->preorder_number()];
2575 if ((pred_out_values == nullptr) ||
2576 ((*pred_out_values)[place_id] == nullptr)) {
2577 return nullptr;
2578 } else if (incoming == nullptr) {
2579 incoming = (*pred_out_values)[place_id];
2580 } else if (incoming != (*pred_out_values)[place_id]) {
2581 incoming = kDifferentValuesMarker;
2582 }
2583 }
2584
2585 if (incoming != kDifferentValuesMarker) {
2586 ASSERT(incoming != nullptr);
2587 return incoming;
2588 }
2589
2590 // Incoming values are different. Phi is required to merge.
2591 PhiInstr* phi =
2592 new (Z) PhiInstr(block->AsJoinEntry(), block->PredecessorCount());
2593 SetPlaceId(phi, place_id);
2594 FillPhiInputs(phi);
2595 return phi;
2596 }
2597
2598 void FillPhiInputs(PhiInstr* phi) {
2599 BlockEntryInstr* block = phi->GetBlock();
2600 const intptr_t place_id = GetPlaceId(phi);
2601
2602 for (intptr_t i = 0; i < block->PredecessorCount(); i++) {
2603 BlockEntryInstr* pred = block->PredecessorAt(i);
2604 ZoneGrowableArray<Definition*>* pred_out_values =
2605 out_values_[pred->preorder_number()];
2606 ASSERT((*pred_out_values)[place_id] != nullptr);
2607
2608 // Sets of outgoing values are not linked into use lists so
2609 // they might contain values that were replaced and removed
2610 // from the graph by this iteration.
2611 // To prevent using them we additionally mark definitions themselves
2612 // as replaced and store a pointer to the replacement.
2613 Definition* replacement = (*pred_out_values)[place_id]->Replacement();
2614 Value* input = new (Z) Value(replacement);
2615 phi->SetInputAt(i, input);
2616 replacement->AddInputUse(input);
2617 // If any input is untagged, then the Phi should be marked as untagged.
2618 if (replacement->representation() == kUntagged) {
2619 phi->set_representation(kUntagged);
2620 }
2621 }
2622
2623 graph_->AllocateSSAIndex(phi);
2624 phis_.Add(phi); // Postpone phi insertion until after load forwarding.
2625
2626 if (FLAG_support_il_printer && FLAG_trace_load_optimization &&
2627 graph_->should_print()) {
2628 THR_Print("created pending phi %s for %s at B%" Pd "\n", phi->ToCString(),
2629 aliased_set_->places()[place_id]->ToCString(),
2630 block->block_id());
2631 }
2632 }
2633
2634 // Iterate over basic blocks and replace exposed loads with incoming
2635 // values.
2636 void ForwardLoads() {
2637 for (BlockIterator block_it = graph_->reverse_postorder_iterator();
2638 !block_it.Done(); block_it.Advance()) {
2639 BlockEntryInstr* block = block_it.Current();
2640
2641 ZoneGrowableArray<Definition*>* loads =
2642 exposed_values_[block->preorder_number()];
2643 if (loads == nullptr) continue; // No exposed loads.
2644
2645 BitVector* in = in_[block->preorder_number()];
2646
2647 for (intptr_t i = 0; i < loads->length(); i++) {
2648 Definition* load = (*loads)[i];
2649 if (!in->Contains(GetPlaceId(load))) continue; // No incoming value.
2650
2651 Definition* replacement = MergeIncomingValues(block, GetPlaceId(load));
2652 ASSERT(replacement != nullptr);
2653
2654 // Sets of outgoing values are not linked into use lists so
2655 // they might contain values that were replace and removed
2656 // from the graph by this iteration.
2657 // To prevent using them we additionally mark definitions themselves
2658 // as replaced and store a pointer to the replacement.
2659 replacement = replacement->Replacement();
2660
2661 if ((load != replacement) && CanForwardLoadTo(load, replacement)) {
2662 graph_->EnsureSSATempIndex(load, replacement);
2663
2664 if (FLAG_trace_optimization && graph_->should_print()) {
2665 THR_Print("Replacing load v%" Pd " with v%" Pd "\n",
2666 load->ssa_temp_index(), replacement->ssa_temp_index());
2667 }
2668
2669 replacement = ReplaceLoad(load, replacement);
2670 load->RemoveFromGraph();
2671 load->SetReplacement(replacement);
2672 forwarded_ = true;
2673 }
2674 }
2675 }
2676 }
2677
2678 // Check if the given phi take the same value on all code paths.
2679 // Eliminate it as redundant if this is the case.
2680 // When analyzing phi operands assumes that only generated during
2681 // this load phase can be redundant. They can be distinguished because
2682 // they are not marked alive.
2683 // TODO(vegorov): move this into a separate phase over all phis.
2684 bool EliminateRedundantPhi(PhiInstr* phi) {
2685 Definition* value = nullptr; // Possible value of this phi.
2686
2687 worklist_.Clear();
2688 if (in_worklist_ == nullptr) {
2689 in_worklist_ = new (Z) BitVector(Z, graph_->current_ssa_temp_index());
2690 } else {
2691 in_worklist_->Clear();
2692 }
2693
2694 worklist_.Add(phi);
2695 in_worklist_->Add(phi->ssa_temp_index());
2696
2697 for (intptr_t i = 0; i < worklist_.length(); i++) {
2698 PhiInstr* phi = worklist_[i];
2699
2700 for (intptr_t i = 0; i < phi->InputCount(); i++) {
2701 Definition* input = phi->InputAt(i)->definition();
2702 if (input == phi) continue;
2703
2704 PhiInstr* phi_input = input->AsPhi();
2705 if ((phi_input != nullptr) && !phi_input->is_alive()) {
2706 if (!in_worklist_->Contains(phi_input->ssa_temp_index())) {
2707 worklist_.Add(phi_input);
2708 in_worklist_->Add(phi_input->ssa_temp_index());
2709 }
2710 continue;
2711 }
2712
2713 if (value == nullptr) {
2714 value = input;
2715 } else if (value != input) {
2716 return false; // This phi is not redundant.
2717 }
2718 }
2719 }
2720
2721 // All phis in the worklist are redundant and have the same computed
2722 // value on all code paths.
2723 ASSERT(value != nullptr);
2724 for (intptr_t i = 0; i < worklist_.length(); i++) {
2725 worklist_[i]->ReplaceUsesWith(value);
2726 }
2727
2728 return true;
2729 }
2730
2731 // Returns true if definitions are congruent assuming their inputs
2732 // are congruent.
2733 bool CanBeCongruent(Definition* a, Definition* b) {
2734 return (a->tag() == b->tag()) &&
2735 ((a->IsPhi() && (a->GetBlock() == b->GetBlock())) ||
2736 (a->AllowsCSE() && a->AttributesEqual(*b)));
2737 }
2738
2739 // Given two definitions check if they are congruent under assumption that
2740 // their inputs will be proven congruent. If they are - add them to the
2741 // worklist to check their inputs' congruency.
2742 // Returns true if pair was added to the worklist or is already in the
2743 // worklist and false if a and b are not congruent.
2744 bool AddPairToCongruencyWorklist(Definition* a, Definition* b) {
2745 if (!CanBeCongruent(a, b)) {
2746 return false;
2747 }
2748
2749 // If a is already in the worklist check if it is being compared to b.
2750 // Give up if it is not.
2751 if (in_worklist_->Contains(a->ssa_temp_index())) {
2752 for (intptr_t i = 0; i < congruency_worklist_.length(); i += 2) {
2753 if (a == congruency_worklist_[i]) {
2754 return (b == congruency_worklist_[i + 1]);
2755 }
2756 }
2757 UNREACHABLE();
2758 } else if (in_worklist_->Contains(b->ssa_temp_index())) {
2759 return AddPairToCongruencyWorklist(b, a);
2760 }
2761
2762 congruency_worklist_.Add(a);
2763 congruency_worklist_.Add(b);
2764 in_worklist_->Add(a->ssa_temp_index());
2765 return true;
2766 }
2767
2768 bool AreInputsCongruent(Definition* a, Definition* b) {
2769 ASSERT(a->tag() == b->tag());
2770 ASSERT(a->InputCount() == b->InputCount());
2771 for (intptr_t j = 0; j < a->InputCount(); j++) {
2772 Definition* inputA = a->InputAt(j)->definition();
2773 Definition* inputB = b->InputAt(j)->definition();
2774
2775 if (inputA != inputB) {
2776 if (!AddPairToCongruencyWorklist(inputA, inputB)) {
2777 return false;
2778 }
2779 }
2780 }
2781 return true;
2782 }
2783
2784 // Returns true if instruction dom dominates instruction other.
2785 static bool Dominates(Instruction* dom, Instruction* other) {
2786 BlockEntryInstr* dom_block = dom->GetBlock();
2787 BlockEntryInstr* other_block = other->GetBlock();
2788
2789 if (dom_block == other_block) {
2790 for (Instruction* current = dom->next(); current != nullptr;
2791 current = current->next()) {
2792 if (current == other) {
2793 return true;
2794 }
2795 }
2796 return false;
2797 }
2798
2799 return dom_block->Dominates(other_block);
2800 }
2801
2802 // Replace the given phi with another if they are congruent.
2803 // Returns true if succeeds.
2804 bool ReplacePhiWith(PhiInstr* phi, PhiInstr* replacement) {
2805 ASSERT(phi->InputCount() == replacement->InputCount());
2806 ASSERT(phi->block() == replacement->block());
2807
2808 congruency_worklist_.Clear();
2809 if (in_worklist_ == nullptr) {
2810 in_worklist_ = new (Z) BitVector(Z, graph_->current_ssa_temp_index());
2811 } else {
2812 in_worklist_->Clear();
2813 }
2814
2815 // During the comparison worklist contains pairs of definitions to be
2816 // compared.
2817 if (!AddPairToCongruencyWorklist(phi, replacement)) {
2818 return false;
2819 }
2820
2821 // Process the worklist. It might grow during each comparison step.
2822 for (intptr_t i = 0; i < congruency_worklist_.length(); i += 2) {
2823 if (!AreInputsCongruent(congruency_worklist_[i],
2824 congruency_worklist_[i + 1])) {
2825 return false;
2826 }
2827 }
2828
2829 // At this point worklist contains pairs of congruent definitions.
2830 // Replace the one member of the pair with another maintaining proper
2831 // domination relation between definitions and uses.
2832 for (intptr_t i = 0; i < congruency_worklist_.length(); i += 2) {
2833 Definition* a = congruency_worklist_[i];
2834 Definition* b = congruency_worklist_[i + 1];
2835
2836 // If these definitions are not phis then we need to pick up one
2837 // that dominates another as the replacement: if a dominates b swap them.
2838 // Note: both a and b are used as a phi input at the same block B which
2839 // means a dominates B and b dominates B, which guarantees that either
2840 // a dominates b or b dominates a.
2841 if (!a->IsPhi()) {
2842 if (Dominates(a, b)) {
2843 Definition* t = a;
2844 a = b;
2845 b = t;
2846 }
2847 ASSERT(Dominates(b, a));
2848 }
2849
2850 if (FLAG_support_il_printer && FLAG_trace_load_optimization &&
2851 graph_->should_print()) {
2852 THR_Print("Replacing %s with congruent %s\n", a->ToCString(),
2853 b->ToCString());
2854 }
2855
2856 a->ReplaceUsesWith(b);
2857 if (a->IsPhi()) {
2858 // We might be replacing a phi introduced by the load forwarding
2859 // that is not inserted in the graph yet.
2860 ASSERT(b->IsPhi());
2861 PhiInstr* phi_a = a->AsPhi();
2862 if (phi_a->is_alive()) {
2863 phi_a->mark_dead();
2864 phi_a->block()->RemovePhi(phi_a);
2865 phi_a->UnuseAllInputs();
2866 }
2867 } else {
2868 a->RemoveFromGraph();
2869 }
2870 }
2871
2872 return true;
2873 }
2874
2875 // Insert the given phi into the graph. Attempt to find an equal one in the
2876 // target block first.
2877 // Returns true if the phi was inserted and false if it was replaced.
2878 bool EmitPhi(PhiInstr* phi) {
2879 for (PhiIterator it(phi->block()); !it.Done(); it.Advance()) {
2880 if (ReplacePhiWith(phi, it.Current())) {
2881 return false;
2882 }
2883 }
2884
2885 phi->mark_alive();
2886 phi->block()->InsertPhi(phi);
2887 return true;
2888 }
2889
2890 // Phis have not yet been inserted into the graph but they have uses of
2891 // their inputs. Insert the non-redundant ones and clear the input uses
2892 // of the redundant ones.
2893 void EmitPhis() {
2894 // First eliminate all redundant phis.
2895 for (intptr_t i = 0; i < phis_.length(); i++) {
2896 PhiInstr* phi = phis_[i];
2897 if (!phi->HasUses() || EliminateRedundantPhi(phi)) {
2898 phi->UnuseAllInputs();
2899 phis_[i] = nullptr;
2900 }
2901 }
2902
2903 // Now emit phis or replace them with equal phis already present in the
2904 // graph.
2905 for (intptr_t i = 0; i < phis_.length(); i++) {
2906 PhiInstr* phi = phis_[i];
2907 if ((phi != nullptr) && (!phi->HasUses() || !EmitPhi(phi))) {
2908 phi->UnuseAllInputs();
2909 }
2910 }
2911 }
2912
2913 ZoneGrowableArray<Definition*>* CreateBlockOutValues() {
2914 ZoneGrowableArray<Definition*>* out =
2915 new (Z) ZoneGrowableArray<Definition*>(aliased_set_->max_place_id());
2916 for (intptr_t i = 0; i < aliased_set_->max_place_id(); i++) {
2917 out->Add(nullptr);
2918 }
2919 return out;
2920 }
2921
2922 FlowGraph* graph_;
2923 PointerSet<Place>* map_;
2924
2925 // Mapping between field offsets in words and expression ids of loads from
2926 // that offset.
2927 AliasedSet* aliased_set_;
2928
2929 // Per block sets of expression ids for loads that are: incoming (available
2930 // on the entry), outgoing (available on the exit), generated and killed.
2931 GrowableArray<BitVector*> in_;
2932 GrowableArray<BitVector*> out_;
2933 GrowableArray<BitVector*> gen_;
2934 GrowableArray<BitVector*> kill_;
2935
2936 // Per block list of upwards exposed loads.
2937 GrowableArray<ZoneGrowableArray<Definition*>*> exposed_values_;
2938
2939 // Per block mappings between expression ids and outgoing definitions that
2940 // represent those ids.
2941 GrowableArray<ZoneGrowableArray<Definition*>*> out_values_;
2942
2943 // List of phis generated during ComputeOutValues and ForwardLoads.
2944 // Some of these phis might be redundant and thus a separate pass is
2945 // needed to emit only non-redundant ones.
2946 GrowableArray<PhiInstr*> phis_;
2947
2948 // Auxiliary worklist used by redundant phi elimination.
2949 GrowableArray<PhiInstr*> worklist_;
2950 GrowableArray<Definition*> congruency_worklist_;
2951 BitVector* in_worklist_;
2952
2953 // True if any load was eliminated.
2954 bool forwarded_;
2955
2956 DISALLOW_COPY_AND_ASSIGN(LoadOptimizer);
2957};
2958
2960 bool run_load_optimization /* = true */) {
2961 bool changed = false;
2962 if (FLAG_load_cse && run_load_optimization) {
2963 changed = LoadOptimizer::OptimizeGraph(graph) || changed;
2964 }
2965
2967 changed = OptimizeRecursive(graph, graph->graph_entry(), &map) || changed;
2968
2969 return changed;
2970}
2971
2972bool DominatorBasedCSE::OptimizeRecursive(FlowGraph* graph,
2973 BlockEntryInstr* block,
2975 bool changed = false;
2976 for (ForwardInstructionIterator it(block); !it.Done(); it.Advance()) {
2977 Instruction* current = it.Current();
2978 if (current->AllowsCSE()) {
2979 Instruction* replacement = map->Lookup(current);
2980
2981 if (replacement != nullptr) {
2982 // Replace current with lookup result.
2983 ASSERT(replacement->AllowsCSE());
2984 graph->ReplaceCurrentInstruction(&it, current, replacement);
2985 changed = true;
2986 continue;
2987 }
2988
2989 map->Insert(current);
2990 }
2991 }
2992
2993 // Process children in the dominator tree recursively.
2994 intptr_t num_children = block->dominated_blocks().length();
2995 if (num_children != 0) {
2996 graph->thread()->CheckForSafepoint();
2997 }
2998 for (intptr_t i = 0; i < num_children; ++i) {
2999 BlockEntryInstr* child = block->dominated_blocks()[i];
3000 if (i < num_children - 1) {
3001 // Copy map.
3002 CSEInstructionSet child_map(*map);
3003 changed = OptimizeRecursive(graph, child, &child_map) || changed;
3004 } else {
3005 // Reuse map for the last child.
3006 changed = OptimizeRecursive(graph, child, map) || changed;
3007 }
3008 }
3009 return changed;
3010}
3011
3013 public:
3015 AliasedSet* aliased_set,
3017 : LivenessAnalysis(aliased_set->max_place_id(), graph->postorder()),
3018 graph_(graph),
3019 map_(map),
3020 aliased_set_(aliased_set),
3021 exposed_stores_(graph_->postorder().length()) {
3022 const intptr_t num_blocks = graph_->postorder().length();
3023 for (intptr_t i = 0; i < num_blocks; i++) {
3024 exposed_stores_.Add(nullptr);
3025 }
3026 }
3027
3028 static void OptimizeGraph(FlowGraph* graph) {
3029 ASSERT(FLAG_load_cse);
3030
3031 // For now, bail out for large functions to avoid OOM situations.
3032 // TODO(fschneider): Fix the memory consumption issue.
3033 if (graph->is_huge_method()) {
3034 return;
3035 }
3036
3038 AliasedSet* aliased_set = NumberPlaces(graph, &map, kOptimizeStores);
3039 if ((aliased_set != nullptr) && !aliased_set->IsEmpty()) {
3040 StoreOptimizer store_optimizer(graph, aliased_set, &map);
3041 store_optimizer.Optimize();
3042 }
3043 }
3044
3045 private:
3046 void Optimize() {
3047 Analyze();
3048 if (FLAG_trace_load_optimization && graph_->should_print()) {
3049 Dump();
3050 }
3051 EliminateDeadStores();
3052 }
3053
3054 bool CanEliminateStore(Instruction* instr) {
3055 if (CompilerState::Current().is_aot()) {
3056 // Tracking initializing stores is important for proper shared boxes
3057 // initialization, which doesn't happen in AOT and for
3058 // LowerContextAllocation optimization which creates unitialized objects
3059 // which is also JIT-only currently.
3060 return true;
3061 }
3062 switch (instr->tag()) {
3063 case Instruction::kStoreField: {
3064 StoreFieldInstr* store_instance = instr->AsStoreField();
3065 // Can't eliminate stores that initialize fields.
3066 return !store_instance->is_initialization();
3067 }
3068 case Instruction::kStoreIndexed:
3069 case Instruction::kStoreStaticField:
3070 return true;
3071 default:
3072 UNREACHABLE();
3073 return false;
3074 }
3075 }
3076
3077 virtual void ComputeInitialSets() {
3078 Zone* zone = graph_->zone();
3079 BitVector* all_places =
3080 new (zone) BitVector(zone, aliased_set_->max_place_id());
3081 all_places->SetAll();
3082
3083 BitVector* all_aliased_places = nullptr;
3085 all_aliased_places =
3086 new (zone) BitVector(zone, aliased_set_->max_place_id());
3087 }
3088 const auto& places = aliased_set_->places();
3089 for (intptr_t i = 0; i < places.length(); i++) {
3090 Place* place = places[i];
3091 if (place->DependsOnInstance()) {
3092 Definition* instance = place->instance();
3093 // Find escaping places by inspecting definition allocation
3094 // [AliasIdentity] field, which is populated above by
3095 // [AliasedSet::ComputeAliasing].
3096 if ((all_aliased_places != nullptr) &&
3098 instance->Identity().IsAliased())) {
3099 all_aliased_places->Add(i);
3100 }
3101 if (instance != nullptr) {
3102 // Avoid incorrect propagation of "dead" state beyond definition
3103 // of the instance. Otherwise it may eventually reach stores into
3104 // this place via loop backedge.
3105 live_in_[instance->GetBlock()->postorder_number()]->Add(i);
3106 }
3107 } else {
3108 if (all_aliased_places != nullptr) {
3109 all_aliased_places->Add(i);
3110 }
3111 }
3112 if (place->kind() == Place::kIndexed) {
3113 Definition* index = place->index();
3114 if (index != nullptr) {
3115 // Avoid incorrect propagation of "dead" state beyond definition
3116 // of the index. Otherwise it may eventually reach stores into
3117 // this place via loop backedge.
3118 live_in_[index->GetBlock()->postorder_number()]->Add(i);
3119 }
3120 }
3121 }
3122
3123 for (BlockIterator block_it = graph_->postorder_iterator();
3124 !block_it.Done(); block_it.Advance()) {
3125 BlockEntryInstr* block = block_it.Current();
3126 const intptr_t postorder_number = block->postorder_number();
3127
3128 BitVector* kill = kill_[postorder_number];
3129 BitVector* live_in = live_in_[postorder_number];
3130 BitVector* live_out = live_out_[postorder_number];
3131
3132 ZoneGrowableArray<Instruction*>* exposed_stores = nullptr;
3133
3134 // Iterate backwards starting at the last instruction.
3135 for (BackwardInstructionIterator instr_it(block); !instr_it.Done();
3136 instr_it.Advance()) {
3137 Instruction* instr = instr_it.Current();
3138
3139 bool is_load = false;
3140 bool is_store = false;
3141 Place place(instr, &is_load, &is_store);
3142 if (place.IsImmutableField()) {
3143 // Loads/stores of final fields do not participate.
3144 continue;
3145 }
3146
3147 // Handle stores.
3148 if (is_store) {
3149 if (kill->Contains(GetPlaceId(instr))) {
3150 if (!live_in->Contains(GetPlaceId(instr)) &&
3151 CanEliminateStore(instr)) {
3152 if (FLAG_trace_optimization && graph_->should_print()) {
3153 THR_Print("Removing dead store to place %" Pd " in block B%" Pd
3154 "\n",
3155 GetPlaceId(instr), block->block_id());
3156 }
3157 instr_it.RemoveCurrentFromGraph();
3158 }
3159 } else if (!live_in->Contains(GetPlaceId(instr))) {
3160 // Mark this store as down-ward exposed: They are the only
3161 // candidates for the global store elimination.
3162 if (exposed_stores == nullptr) {
3163 const intptr_t kMaxExposedStoresInitialSize = 5;
3164 exposed_stores = new (zone) ZoneGrowableArray<Instruction*>(
3165 Utils::Minimum(kMaxExposedStoresInitialSize,
3166 aliased_set_->max_place_id()));
3167 }
3168 exposed_stores->Add(instr);
3169 }
3170 // Interfering stores kill only loads from the same place.
3171 kill->Add(GetPlaceId(instr));
3172 live_in->Remove(GetPlaceId(instr));
3173 continue;
3174 }
3175
3176 if (instr->IsThrow() || instr->IsReThrow() || instr->IsReturnBase()) {
3177 // Initialize live-out for exit blocks since it won't be computed
3178 // otherwise during the fixed point iteration.
3179 live_out->CopyFrom(all_places);
3180 }
3181
3182 // Handle side effects, deoptimization and function return.
3184 if (instr->HasUnknownSideEffects() || instr->IsReturnBase()) {
3185 // An instruction that returns or has unknown side effects
3186 // is treated as if it loads from all places.
3187 live_in->CopyFrom(all_places);
3188 continue;
3189 } else if (instr->MayThrow()) {
3190 if (block->try_index() == kInvalidTryIndex) {
3191 // Outside of a try-catch block, an instruction that may throw
3192 // is only treated as if it loads from escaping places.
3193 live_in->AddAll(all_aliased_places);
3194 } else {
3195 // Inside of a try-catch block, an instruction that may throw
3196 // is treated as if it loads from all places.
3197 live_in->CopyFrom(all_places);
3198 }
3199 continue;
3200 }
3201 } else { // jit
3202 // Similar optimization to the above could be implemented in JIT
3203 // as long as deoptimization side-effects are taken into account.
3204 // Conceptually variables in deoptimization environment for
3205 // "MayThrow" instructions have to be also added to the
3206 // [live_in] set as they can be considered as escaping (to
3207 // unoptimized code). However those deoptimization environment
3208 // variables include also non-escaping(not aliased) ones, so
3209 // how to deal with that needs to be figured out.
3210 if (instr->HasUnknownSideEffects() || instr->CanDeoptimize() ||
3211 instr->MayThrow() || instr->IsReturnBase()) {
3212 // Instructions that return from the function, instructions with
3213 // side effects and instructions that can deoptimize are considered
3214 // as loads from all places.
3215 live_in->CopyFrom(all_places);
3216 continue;
3217 }
3218 }
3219
3220 // Handle loads.
3221 Definition* defn = instr->AsDefinition();
3222 if ((defn != nullptr) && IsLoadEliminationCandidate(defn)) {
3223 const intptr_t alias = aliased_set_->LookupAliasId(place.ToAlias());
3224 live_in->AddAll(aliased_set_->GetKilledSet(alias));
3225 continue;
3226 }
3227 }
3228 exposed_stores_[postorder_number] = exposed_stores;
3229 }
3230 if (FLAG_trace_load_optimization && graph_->should_print()) {
3231 Dump();
3232 THR_Print("---\n");
3233 }
3234 }
3235
3236 void EliminateDeadStores() {
3237 // Iteration order does not matter here.
3238 for (BlockIterator block_it = graph_->postorder_iterator();
3239 !block_it.Done(); block_it.Advance()) {
3240 BlockEntryInstr* block = block_it.Current();
3241 const intptr_t postorder_number = block->postorder_number();
3242
3243 BitVector* live_out = live_out_[postorder_number];
3244
3245 ZoneGrowableArray<Instruction*>* exposed_stores =
3246 exposed_stores_[postorder_number];
3247 if (exposed_stores == nullptr) continue; // No exposed stores.
3248
3249 // Iterate over candidate stores.
3250 for (intptr_t i = 0; i < exposed_stores->length(); ++i) {
3251 Instruction* instr = (*exposed_stores)[i];
3252 bool is_load = false;
3253 bool is_store = false;
3254 Place place(instr, &is_load, &is_store);
3255 ASSERT(!is_load && is_store);
3256 if (place.IsImmutableField()) {
3257 // Final field do not participate in dead store elimination.
3258 continue;
3259 }
3260 // Eliminate a downward exposed store if the corresponding place is not
3261 // in live-out.
3262 if (!live_out->Contains(GetPlaceId(instr)) &&
3263 CanEliminateStore(instr)) {
3264 if (FLAG_trace_optimization && graph_->should_print()) {
3265 THR_Print("Removing dead store to place %" Pd " block B%" Pd "\n",
3266 GetPlaceId(instr), block->block_id());
3267 }
3268 instr->RemoveFromGraph(/* ignored */ false);
3269 }
3270 }
3271 }
3272 }
3273
3274 FlowGraph* graph_;
3275 PointerSet<Place>* map_;
3276
3277 // Mapping between field offsets in words and expression ids of loads from
3278 // that offset.
3279 AliasedSet* aliased_set_;
3280
3281 // Per block list of downward exposed stores.
3282 GrowableArray<ZoneGrowableArray<Instruction*>*> exposed_stores_;
3283
3284 DISALLOW_COPY_AND_ASSIGN(StoreOptimizer);
3285};
3286
3288 if (FLAG_dead_store_elimination && FLAG_load_cse) {
3290 }
3291}
3292
3293//
3294// Allocation Sinking
3295//
3296
3298 ArrayAllocationInstr* array_alloc) {
3299 const intptr_t kMaxAllocationSinkingNumElements = 32;
3300 if (!array_alloc->HasConstantNumElements()) {
3301 return false;
3302 }
3303 const intptr_t length = array_alloc->GetConstantNumElements();
3304 return (length >= 0) && (length <= kMaxAllocationSinkingNumElements);
3305}
3306
3307// Returns true if the given instruction is an allocation that
3308// can be sunk by the Allocation Sinking pass.
3310 return instr->IsAllocation() &&
3311 (!instr->IsArrayAllocation() ||
3312 IsValidLengthForAllocationSinking(instr->AsArrayAllocation()));
3313}
3314
3315// Check if the use is safe for allocation sinking. Allocation sinking
3316// candidates can only be used as inputs to store and allocation instructions:
3317//
3318// - any store into the allocation candidate itself is unconditionally safe
3319// as it just changes the rematerialization state of this candidate;
3320// - store into another object is only safe if the other object is
3321// an allocation candidate.
3322// - use as input to another allocation is only safe if the other allocation
3323// is a candidate.
3324//
3325// We use a simple fix-point algorithm to discover the set of valid candidates
3326// (see CollectCandidates method), that's why this IsSafeUse can operate in two
3327// modes:
3328//
3329// - optimistic, when every allocation is assumed to be an allocation
3330// sinking candidate;
3331// - strict, when only marked allocations are assumed to be allocation
3332// sinking candidates.
3333//
3334// Fix-point algorithm in CollectCandidates first collects a set of allocations
3335// optimistically and then checks each collected candidate strictly and unmarks
3336// invalid candidates transitively until only strictly valid ones remain.
3337bool AllocationSinking::IsSafeUse(Value* use, SafeUseCheck check_type) {
3338 ASSERT(IsSupportedAllocation(use->definition()));
3339
3340 if (use->instruction()->IsMaterializeObject()) {
3341 return true;
3342 }
3343
3344 if (auto* const alloc = use->instruction()->AsAllocation()) {
3345 return IsSupportedAllocation(alloc) &&
3346 ((check_type == kOptimisticCheck) ||
3347 alloc->Identity().IsAllocationSinkingCandidate());
3348 }
3349
3350 if (auto* store = use->instruction()->AsStoreField()) {
3351 if (use == store->value()) {
3352 Definition* instance = store->instance()->definition();
3354 ((check_type == kOptimisticCheck) ||
3355 instance->Identity().IsAllocationSinkingCandidate());
3356 }
3357 return true;
3358 }
3359
3360 if (auto* store = use->instruction()->AsStoreIndexed()) {
3361 if (use == store->index()) {
3362 return false;
3363 }
3364 if (use == store->array()) {
3365 if (!store->index()->BindsToSmiConstant()) {
3366 return false;
3367 }
3368 const intptr_t index = store->index()->BoundSmiConstant();
3369 if (index < 0 || index >= use->definition()
3370 ->AsArrayAllocation()
3371 ->GetConstantNumElements()) {
3372 return false;
3373 }
3374 if (auto* alloc_typed_data = use->definition()->AsAllocateTypedData()) {
3375 if (store->class_id() != alloc_typed_data->class_id() ||
3376 !store->aligned() ||
3378 alloc_typed_data->class_id())) {
3379 return false;
3380 }
3381 }
3382 }
3383 if (use == store->value()) {
3384 Definition* instance = store->array()->definition();
3386 ((check_type == kOptimisticCheck) ||
3387 instance->Identity().IsAllocationSinkingCandidate());
3388 }
3389 return true;
3390 }
3391
3392 return false;
3393}
3394
3395// Right now we are attempting to sink allocation only into
3396// deoptimization exit. So candidate should only be used in StoreField
3397// instructions that write into fields of the allocated object.
3398bool AllocationSinking::IsAllocationSinkingCandidate(Definition* alloc,
3399 SafeUseCheck check_type) {
3400 for (Value* use = alloc->input_use_list(); use != nullptr;
3401 use = use->next_use()) {
3402 if (!IsSafeUse(use, check_type)) {
3403 if (FLAG_support_il_printer && FLAG_trace_optimization &&
3404 flow_graph_->should_print()) {
3405 THR_Print("use of %s at %s is unsafe for allocation sinking\n",
3406 alloc->ToCString(), use->instruction()->ToCString());
3407 }
3408 return false;
3409 }
3410 }
3411
3412 return true;
3413}
3414
3415// If the given use is a store into an object then return an object we are
3416// storing into.
3418 if (auto* const alloc = use->instruction()->AsAllocation()) {
3419 return alloc;
3420 }
3421 if (auto* const store = use->instruction()->AsStoreField()) {
3422 return store->instance()->definition();
3423 }
3424 if (auto* const store = use->instruction()->AsStoreIndexed()) {
3425 return store->array()->definition();
3426 }
3427 return nullptr;
3428}
3429
3430// If the given instruction is a load from an object, then return an object
3431// we are loading from.
3433 if (auto load = instr->AsLoadField()) {
3434 return load->instance()->definition();
3435 }
3436 if (auto load = instr->AsLoadIndexed()) {
3437 return load->array()->definition();
3438 }
3439 return nullptr;
3440}
3441
3442// Remove the given allocation from the graph. It is not observable.
3443// If deoptimization occurs the object will be materialized.
3444void AllocationSinking::EliminateAllocation(Definition* alloc) {
3445 ASSERT(IsAllocationSinkingCandidate(alloc, kStrictCheck));
3446
3447 if (FLAG_trace_optimization && flow_graph_->should_print()) {
3448 THR_Print("removing allocation from the graph: v%" Pd "\n",
3449 alloc->ssa_temp_index());
3450 }
3451
3452 // As an allocation sinking candidate, remove stores to this candidate.
3453 // Do this in a two-step process, as this allocation may be used multiple
3454 // times in a single instruction (e.g., as the instance and the value in
3455 // a StoreField). This means multiple entries may be removed from the
3456 // use list when removing instructions, not just the current one, so
3457 // Value::Iterator cannot be safely used.
3458 GrowableArray<Instruction*> stores_to_remove;
3459 for (Value* use = alloc->input_use_list(); use != nullptr;
3460 use = use->next_use()) {
3461 Instruction* const instr = use->instruction();
3462 Definition* const instance = StoreDestination(use);
3463 // All uses of a candidate should be stores or other allocations.
3464 ASSERT(instance != nullptr);
3465 if (instance == alloc) {
3466 // An allocation instruction cannot be a direct input to itself.
3467 ASSERT(!instr->IsAllocation());
3468 stores_to_remove.Add(instr);
3469 } else {
3470 // The candidate is being stored into another candidate, either through
3471 // a store instruction or as the input to a to-be-eliminated allocation,
3472 // so this instruction will be removed with the other candidate.
3473 ASSERT(candidates_.Contains(instance));
3474 }
3475 }
3476
3477 for (auto* const store : stores_to_remove) {
3478 // Avoid calling RemoveFromGraph() more than once on the same instruction.
3479 if (store->previous() != nullptr) {
3480 store->RemoveFromGraph();
3481 }
3482 }
3483
3484// There should be no environment uses. The pass replaced them with
3485// MaterializeObject instructions.
3486#ifdef DEBUG
3487 for (Value* use = alloc->env_use_list(); use != nullptr;
3488 use = use->next_use()) {
3489 ASSERT(use->instruction()->IsMaterializeObject());
3490 }
3491#endif
3492
3493 alloc->RemoveFromGraph();
3494}
3495
3496// Find allocation instructions that can be potentially eliminated and
3497// rematerialized at deoptimization exits if needed. See IsSafeUse
3498// for the description of algorithm used below.
3499void AllocationSinking::CollectCandidates() {
3500 // Optimistically collect all potential candidates.
3501 for (BlockIterator block_it = flow_graph_->reverse_postorder_iterator();
3502 !block_it.Done(); block_it.Advance()) {
3503 BlockEntryInstr* block = block_it.Current();
3504 for (ForwardInstructionIterator it(block); !it.Done(); it.Advance()) {
3505 Instruction* current = it.Current();
3506 if (!IsSupportedAllocation(current)) {
3507 continue;
3508 }
3509
3510 Definition* alloc = current->Cast<Definition>();
3511 if (IsAllocationSinkingCandidate(alloc, kOptimisticCheck)) {
3512 alloc->SetIdentity(AliasIdentity::AllocationSinkingCandidate());
3513 candidates_.Add(alloc);
3514 }
3515 }
3516 }
3517
3518 // Transitively unmark all candidates that are not strictly valid.
3519 bool changed;
3520 do {
3521 changed = false;
3522 for (intptr_t i = 0; i < candidates_.length(); i++) {
3523 Definition* alloc = candidates_[i];
3524 if (alloc->Identity().IsAllocationSinkingCandidate()) {
3525 if (!IsAllocationSinkingCandidate(alloc, kStrictCheck)) {
3526 alloc->SetIdentity(AliasIdentity::Unknown());
3527 changed = true;
3528 }
3529 }
3530 }
3531 } while (changed);
3532
3533 // Shrink the list of candidates removing all unmarked ones.
3534 intptr_t j = 0;
3535 for (intptr_t i = 0; i < candidates_.length(); i++) {
3536 Definition* alloc = candidates_[i];
3537 if (alloc->Identity().IsAllocationSinkingCandidate()) {
3538 if (FLAG_trace_optimization && flow_graph_->should_print()) {
3539 THR_Print("discovered allocation sinking candidate: v%" Pd "\n",
3540 alloc->ssa_temp_index());
3541 }
3542
3543 if (j != i) {
3544 candidates_[j] = alloc;
3545 }
3546 j++;
3547 }
3548 }
3549 candidates_.TruncateTo(j);
3550}
3551
3552// If materialization references an allocation sinking candidate then replace
3553// this reference with a materialization which should have been computed for
3554// this side-exit. CollectAllExits should have collected this exit.
3555void AllocationSinking::NormalizeMaterializations() {
3556 for (intptr_t i = 0; i < candidates_.length(); i++) {
3557 Definition* alloc = candidates_[i];
3558
3559 Value* next_use;
3560 for (Value* use = alloc->input_use_list(); use != nullptr; use = next_use) {
3561 next_use = use->next_use();
3562 if (use->instruction()->IsMaterializeObject()) {
3563 use->BindTo(MaterializationFor(alloc, use->instruction()));
3564 }
3565 }
3566 }
3567}
3568
3569// We transitively insert materializations at each deoptimization exit that
3570// might see the given allocation (see ExitsCollector). Some of these
3571// materializations are not actually used and some fail to compute because
3572// they are inserted in the block that is not dominated by the allocation.
3573// Remove unused materializations from the graph.
3574void AllocationSinking::RemoveUnusedMaterializations() {
3575 intptr_t j = 0;
3576 for (intptr_t i = 0; i < materializations_.length(); i++) {
3577 MaterializeObjectInstr* mat = materializations_[i];
3578 if ((mat->input_use_list() == nullptr) &&
3579 (mat->env_use_list() == nullptr)) {
3580 // Check if this materialization failed to compute and remove any
3581 // unforwarded loads. There were no loads from any allocation sinking
3582 // candidate in the beginning so it is safe to assume that any encountered
3583 // load was inserted by CreateMaterializationAt.
3584 for (intptr_t i = 0; i < mat->InputCount(); i++) {
3585 Definition* load = mat->InputAt(i)->definition();
3586 if (LoadSource(load) == mat->allocation()) {
3587 load->ReplaceUsesWith(flow_graph_->constant_null());
3588 load->RemoveFromGraph();
3589 }
3590 }
3591 mat->RemoveFromGraph();
3592 } else {
3593 if (j != i) {
3594 materializations_[j] = mat;
3595 }
3596 j++;
3597 }
3598 }
3599 materializations_.TruncateTo(j);
3600}
3601
3602// Some candidates might stop being eligible for allocation sinking after
3603// the load forwarding because they flow into phis that load forwarding
3604// inserts. Discover such allocations and remove them from the list
3605// of allocation sinking candidates undoing all changes that we did
3606// in preparation for sinking these allocations.
3607void AllocationSinking::DiscoverFailedCandidates() {
3608 // Transitively unmark all candidates that are not strictly valid.
3609 bool changed;
3610 do {
3611 changed = false;
3612 for (intptr_t i = 0; i < candidates_.length(); i++) {
3613 Definition* alloc = candidates_[i];
3614 if (alloc->Identity().IsAllocationSinkingCandidate()) {
3615 if (!IsAllocationSinkingCandidate(alloc, kStrictCheck)) {
3616 alloc->SetIdentity(AliasIdentity::Unknown());
3617 changed = true;
3618 }
3619 }
3620 }
3621 } while (changed);
3622
3623 // Remove all failed candidates from the candidates list.
3624 intptr_t j = 0;
3625 for (intptr_t i = 0; i < candidates_.length(); i++) {
3626 Definition* alloc = candidates_[i];
3627 if (!alloc->Identity().IsAllocationSinkingCandidate()) {
3628 if (FLAG_trace_optimization && flow_graph_->should_print()) {
3629 THR_Print("allocation v%" Pd " can't be eliminated\n",
3630 alloc->ssa_temp_index());
3631 }
3632
3633#ifdef DEBUG
3634 for (Value* use = alloc->env_use_list(); use != nullptr;
3635 use = use->next_use()) {
3636 ASSERT(use->instruction()->IsMaterializeObject());
3637 }
3638#endif
3639
3640 // All materializations will be removed from the graph. Remove inserted
3641 // loads first and detach materializations from allocation's environment
3642 // use list: we will reconstruct it when we start removing
3643 // materializations.
3644 alloc->set_env_use_list(nullptr);
3645 for (Value* use = alloc->input_use_list(); use != nullptr;
3646 use = use->next_use()) {
3647 if (use->instruction()->IsLoadField() ||
3648 use->instruction()->IsLoadIndexed()) {
3649 Definition* load = use->instruction()->AsDefinition();
3650 load->ReplaceUsesWith(flow_graph_->constant_null());
3651 load->RemoveFromGraph();
3652 } else {
3653 ASSERT(use->instruction()->IsMaterializeObject() ||
3654 use->instruction()->IsPhi() ||
3655 use->instruction()->IsStoreField() ||
3656 use->instruction()->IsStoreIndexed());
3657 }
3658 }
3659 } else {
3660 if (j != i) {
3661 candidates_[j] = alloc;
3662 }
3663 j++;
3664 }
3665 }
3666
3667 if (j != candidates_.length()) { // Something was removed from candidates.
3668 intptr_t k = 0;
3669 for (intptr_t i = 0; i < materializations_.length(); i++) {
3670 MaterializeObjectInstr* mat = materializations_[i];
3671 if (!mat->allocation()->Identity().IsAllocationSinkingCandidate()) {
3672 // Restore environment uses of the allocation that were replaced
3673 // by this materialization and drop materialization.
3674 mat->ReplaceUsesWith(mat->allocation());
3675 mat->RemoveFromGraph();
3676 } else {
3677 if (k != i) {
3678 materializations_[k] = mat;
3679 }
3680 k++;
3681 }
3682 }
3683 materializations_.TruncateTo(k);
3684 }
3685
3686 candidates_.TruncateTo(j);
3687}
3688
3690 // Allocation sinking depends on load forwarding, so give up early if load
3691 // forwarding is disabled.
3692 if (!FLAG_load_cse || flow_graph_->is_huge_method()) {
3693 return;
3694 }
3695
3696 CollectCandidates();
3697
3698 // Insert MaterializeObject instructions that will describe the state of the
3699 // object at all deoptimization points. Each inserted materialization looks
3700 // like this (where v_0 is allocation that we are going to eliminate):
3701 // v_1 <- LoadField(v_0, field_1)
3702 // ...
3703 // v_N <- LoadField(v_0, field_N)
3704 // v_{N+1} <- MaterializeObject(field_1 = v_1, ..., field_N = v_N)
3705 //
3706 // For typed data objects materialization looks like this:
3707 // v_1 <- LoadIndexed(v_0, index_1)
3708 // ...
3709 // v_N <- LoadIndexed(v_0, index_N)
3710 // v_{N+1} <- MaterializeObject([index_1] = v_1, ..., [index_N] = v_N)
3711 //
3712 // For arrays materialization looks like this:
3713 // v_1 <- LoadIndexed(v_0, index_1)
3714 // ...
3715 // v_N <- LoadIndexed(v_0, index_N)
3716 // v_{N+1} <- LoadField(v_0, Array.type_arguments)
3717 // v_{N+2} <- MaterializeObject([index_1] = v_1, ..., [index_N] = v_N,
3718 // type_arguments = v_{N+1})
3719 //
3720 for (intptr_t i = 0; i < candidates_.length(); i++) {
3721 InsertMaterializations(candidates_[i]);
3722 }
3723
3724 // Run load forwarding to eliminate LoadField/LoadIndexed instructions
3725 // inserted above.
3726 //
3727 // All loads will be successfully eliminated because:
3728 // a) they use fields/constant indices and thus provide precise aliasing
3729 // information
3730 // b) candidate does not escape and thus its fields/elements are not
3731 // affected by external effects from calls.
3732 LoadOptimizer::OptimizeGraph(flow_graph_);
3733
3734 NormalizeMaterializations();
3735
3736 RemoveUnusedMaterializations();
3737
3738 // If any candidates are no longer eligible for allocation sinking abort
3739 // the optimization for them and undo any changes we did in preparation.
3740 DiscoverFailedCandidates();
3741
3742 // At this point we have computed the state of object at each deoptimization
3743 // point and we can eliminate it. Loads inserted above were forwarded so there
3744 // are no uses of the allocation outside other candidates to eliminate, just
3745 // as in the beginning of the pass.
3746 for (intptr_t i = 0; i < candidates_.length(); i++) {
3747 EliminateAllocation(candidates_[i]);
3748 }
3749
3750 // Process materializations and unbox their arguments: materializations
3751 // are part of the environment and can materialize boxes for double/mint/simd
3752 // values when needed.
3753 // TODO(vegorov): handle all box types here.
3754 for (intptr_t i = 0; i < materializations_.length(); i++) {
3755 MaterializeObjectInstr* mat = materializations_[i];
3756 for (intptr_t j = 0; j < mat->InputCount(); j++) {
3757 Definition* defn = mat->InputAt(j)->definition();
3758 if (defn->IsBox()) {
3759 mat->InputAt(j)->BindTo(defn->InputAt(0)->definition());
3760 }
3761 }
3762 }
3763}
3764
3765// Remove materializations from the graph. Register allocator will treat them
3766// as part of the environment not as a real instruction.
3768 for (MaterializeObjectInstr* mat : materializations_) {
3769 mat->previous()->LinkTo(mat->next());
3770 mat->set_next(nullptr);
3771 mat->set_previous(nullptr);
3772 }
3773}
3774
3775// Add a field/offset to the list of fields if it is not yet present there.
3776static void AddSlot(ZoneGrowableArray<const Slot*>* slots, const Slot& slot) {
3777 if (!slots->Contains(&slot)) {
3778 slots->Add(&slot);
3779 }
3780}
3781
3782// Find deoptimization exit for the given materialization assuming that all
3783// materializations are emitted right before the instruction which is a
3784// deoptimization exit.
3786 while (mat->next()->IsMaterializeObject()) {
3787 mat = mat->next()->AsMaterializeObject();
3788 }
3789 return mat->next();
3790}
3791
3792// Given the deoptimization exit find first materialization that was inserted
3793// before it.
3795 while (exit->previous()->IsMaterializeObject()) {
3796 exit = exit->previous();
3797 }
3798 return exit;
3799}
3800
3801// Given the allocation and deoptimization exit try to find MaterializeObject
3802// instruction corresponding to this allocation at this exit.
3804 Definition* alloc,
3805 Instruction* exit) {
3806 if (exit->IsMaterializeObject()) {
3807 exit = ExitForMaterialization(exit->AsMaterializeObject());
3808 }
3809
3810 for (MaterializeObjectInstr* mat = exit->previous()->AsMaterializeObject();
3811 mat != nullptr; mat = mat->previous()->AsMaterializeObject()) {
3812 if (mat->allocation() == alloc) {
3813 return mat;
3814 }
3815 }
3816
3817 return nullptr;
3818}
3819
3821 const Slot& slot) {
3822 if (slot.representation() != kUntagged) {
3824 }
3825 if (!slot.may_contain_inner_pointer()) {
3827 }
3828 if (slot.IsIdentical(Slot::PointerBase_data())) {
3829 // The data field of external arrays is not unsafe, as it never contains
3830 // a GC-movable address.
3831 if (alloc->IsAllocateObject() &&
3832 IsExternalPayloadClassId(alloc->AsAllocateObject()->cls().id())) {
3834 }
3835 }
3837}
3838
3839// Insert MaterializeObject instruction for the given allocation before
3840// the given instruction that can deoptimize.
3841void AllocationSinking::CreateMaterializationAt(
3842 Instruction* exit,
3843 Definition* alloc,
3844 const ZoneGrowableArray<const Slot*>& slots) {
3845 InputsArray values(slots.length());
3846
3847 // All loads should be inserted before the first materialization so that
3848 // IR follows the following pattern: loads, materializations, deoptimizing
3849 // instruction.
3850 Instruction* load_point = FirstMaterializationAt(exit);
3851
3852 // Insert load instruction for every field and element.
3853 for (auto slot : slots) {
3854 Definition* load = nullptr;
3855 if (slot->IsArrayElement()) {
3856 intptr_t array_cid, index;
3857 if (alloc->IsCreateArray()) {
3858 array_cid = kArrayCid;
3859 index =
3860 compiler::target::Array::index_at_offset(slot->offset_in_bytes());
3861 } else if (auto alloc_typed_data = alloc->AsAllocateTypedData()) {
3862 array_cid = alloc_typed_data->class_id();
3863 index = slot->offset_in_bytes() /
3865 } else {
3866 UNREACHABLE();
3867 }
3868 load = new (Z) LoadIndexedInstr(
3869 new (Z) Value(alloc),
3870 new (Z) Value(
3871 flow_graph_->GetConstant(Smi::ZoneHandle(Z, Smi::New(index)))),
3872 /*index_unboxed=*/false,
3873 /*index_scale=*/compiler::target::Instance::ElementSizeFor(array_cid),
3874 array_cid, kAlignedAccess, DeoptId::kNone, alloc->source());
3875 } else {
3878 load = new (Z)
3879 LoadFieldInstr(new (Z) Value(alloc), *slot, access, alloc->source());
3880 }
3881 flow_graph_->InsertBefore(load_point, load, nullptr, FlowGraph::kValue);
3882 values.Add(new (Z) Value(load));
3883 }
3884
3885 const Class* cls = nullptr;
3886 intptr_t length_or_shape = -1;
3887 if (auto instr = alloc->AsAllocateObject()) {
3888 cls = &(instr->cls());
3889 } else if (alloc->IsAllocateClosure()) {
3890 cls = &Class::ZoneHandle(
3891 flow_graph_->isolate_group()->object_store()->closure_class());
3892 } else if (auto instr = alloc->AsAllocateContext()) {
3894 length_or_shape = instr->num_context_variables();
3895 } else if (auto instr = alloc->AsAllocateUninitializedContext()) {
3897 length_or_shape = instr->num_context_variables();
3898 } else if (auto instr = alloc->AsCreateArray()) {
3899 cls = &Class::ZoneHandle(
3900 flow_graph_->isolate_group()->object_store()->array_class());
3901 length_or_shape = instr->GetConstantNumElements();
3902 } else if (auto instr = alloc->AsAllocateTypedData()) {
3903 cls = &Class::ZoneHandle(
3904 flow_graph_->isolate_group()->class_table()->At(instr->class_id()));
3905 length_or_shape = instr->GetConstantNumElements();
3906 } else if (auto instr = alloc->AsAllocateRecord()) {
3907 cls = &Class::ZoneHandle(
3908 flow_graph_->isolate_group()->class_table()->At(kRecordCid));
3909 length_or_shape = instr->shape().AsInt();
3910 } else if (auto instr = alloc->AsAllocateSmallRecord()) {
3911 cls = &Class::ZoneHandle(
3912 flow_graph_->isolate_group()->class_table()->At(kRecordCid));
3913 length_or_shape = instr->shape().AsInt();
3914 } else {
3915 UNREACHABLE();
3916 }
3917 MaterializeObjectInstr* mat = new (Z) MaterializeObjectInstr(
3918 alloc->AsAllocation(), *cls, length_or_shape, slots, std::move(values));
3919
3920 flow_graph_->InsertBefore(exit, mat, nullptr, FlowGraph::kValue);
3921
3922 // Replace all mentions of this allocation with a newly inserted
3923 // MaterializeObject instruction.
3924 // We must preserve the identity: all mentions are replaced by the same
3925 // materialization.
3926 exit->ReplaceInEnvironment(alloc, mat);
3927
3928 // Mark MaterializeObject as an environment use of this allocation.
3929 // This will allow us to discover it when we are looking for deoptimization
3930 // exits for another allocation that potentially flows into this one.
3931 Value* val = new (Z) Value(alloc);
3932 val->set_instruction(mat);
3933 alloc->AddEnvUse(val);
3934
3935 // Record inserted materialization.
3936 materializations_.Add(mat);
3937}
3938
3939// Add given instruction to the list of the instructions if it is not yet
3940// present there.
3941template <typename T>
3943 ASSERT(!value->IsGraphEntry() && !value->IsFunctionEntry());
3944 for (intptr_t i = 0; i < list->length(); i++) {
3945 if ((*list)[i] == value) {
3946 return;
3947 }
3948 }
3949 list->Add(value);
3950}
3951
3952// Transitively collect all deoptimization exits that might need this allocation
3953// rematerialized. It is not enough to collect only environment uses of this
3954// allocation because it can flow into other objects that will be
3955// dematerialized and that are referenced by deopt environments that
3956// don't contain this allocation explicitly.
3957void AllocationSinking::ExitsCollector::Collect(Definition* alloc) {
3958 for (Value* use = alloc->env_use_list(); use != nullptr;
3959 use = use->next_use()) {
3960 if (use->instruction()->IsMaterializeObject()) {
3962 use->instruction()->AsMaterializeObject()));
3963 } else {
3964 AddInstruction(&exits_, use->instruction());
3965 }
3966 }
3967
3968 // Check if this allocation is stored into any other allocation sinking
3969 // candidate and put it on worklist so that we conservatively collect all
3970 // exits for that candidate as well because they potentially might see
3971 // this object.
3972 for (Value* use = alloc->input_use_list(); use != nullptr;
3973 use = use->next_use()) {
3974 Definition* obj = StoreDestination(use);
3975 if ((obj != nullptr) && (obj != alloc)) {
3976 AddInstruction(&worklist_, obj);
3977 }
3978 }
3979}
3980
3981void AllocationSinking::ExitsCollector::CollectTransitively(Definition* alloc) {
3982 exits_.TruncateTo(0);
3983 worklist_.TruncateTo(0);
3984
3985 worklist_.Add(alloc);
3986
3987 // Note: worklist potentially will grow while we are iterating over it.
3988 // We are not removing allocations from the worklist not to waste space on
3989 // the side maintaining BitVector of already processed allocations: worklist
3990 // is expected to be very small thus linear search in it is just as efficient
3991 // as a bitvector.
3992 for (intptr_t i = 0; i < worklist_.length(); i++) {
3993 Collect(worklist_[i]);
3994 }
3995}
3996
3997static bool IsDataFieldOfTypedDataView(Definition* alloc, const Slot& slot) {
3998 if (!slot.IsIdentical(Slot::PointerBase_data())) return false;
3999 // Internal typed data objects use AllocateTypedData.
4000 if (!alloc->IsAllocateObject()) return false;
4001 auto const cid = alloc->AsAllocateObject()->cls().id();
4003}
4004
4005void AllocationSinking::InsertMaterializations(Definition* alloc) {
4006 // Collect all fields and array elements that are written for this instance.
4007 auto slots = new (Z) ZoneGrowableArray<const Slot*>(5);
4008
4009 for (Value* use = alloc->input_use_list(); use != nullptr;
4010 use = use->next_use()) {
4011 if (StoreDestination(use) == alloc) {
4012 // Allocation instructions cannot be used in as inputs to themselves.
4013 ASSERT(!use->instruction()->AsAllocation());
4014 if (auto store = use->instruction()->AsStoreField()) {
4015 // Only add the data field slot for external arrays. For views, it is
4016 // recomputed using the other fields in DeferredObject::Fill().
4017 if (!IsDataFieldOfTypedDataView(alloc, store->slot())) {
4018 AddSlot(slots, store->slot());
4019 }
4020 } else if (auto store = use->instruction()->AsStoreIndexed()) {
4021 const intptr_t index = store->index()->BoundSmiConstant();
4022 intptr_t offset = -1;
4023 if (alloc->IsCreateArray()) {
4025 } else if (alloc->IsAllocateTypedData()) {
4026 offset = index * store->index_scale();
4027 } else {
4028 UNREACHABLE();
4029 }
4030 AddSlot(slots,
4031 Slot::GetArrayElementSlot(flow_graph_->thread(), offset));
4032 }
4033 }
4034 }
4035
4036 if (auto* const allocation = alloc->AsAllocation()) {
4037 for (intptr_t pos = 0; pos < allocation->InputCount(); pos++) {
4038 if (auto* const slot = allocation->SlotForInput(pos)) {
4039 // Don't add slots for immutable length slots if not already added
4040 // above, as they are already represented as the number of elements in
4041 // the MaterializeObjectInstr.
4042 if (!slot->IsImmutableLengthSlot()) {
4043 AddSlot(slots, *slot);
4044 }
4045 }
4046 }
4047 }
4048
4049 // Collect all instructions that mention this object in the environment.
4050 exits_collector_.CollectTransitively(alloc);
4051
4052 // Insert materializations at environment uses.
4053 for (intptr_t i = 0; i < exits_collector_.exits().length(); i++) {
4054 CreateMaterializationAt(exits_collector_.exits()[i], alloc, *slots);
4055 }
4056}
4057
4058// TryCatchAnalyzer tries to reduce the state that needs to be synchronized
4059// on entry to the catch by discovering Parameter-s which are never used
4060// or which are always constant.
4061//
4062// This analysis is similar to dead/redundant phi elimination because
4063// Parameter instructions serve as "implicit" phis.
4064//
4065// Caveat: when analyzing which Parameter-s are redundant we limit ourselves to
4066// constant values because CatchBlockEntry-s are hanging out directly from
4067// GraphEntry and thus they are only dominated by constants from GraphEntry -
4068// thus we can't replace Parameter with arbitrary Definition which is not a
4069// Constant even if we know that this Parameter is redundant and would always
4070// evaluate to that Definition.
4072 public:
4073 explicit TryCatchAnalyzer(FlowGraph* flow_graph, bool is_aot)
4074 : flow_graph_(flow_graph),
4075 is_aot_(is_aot),
4076 // Initial capacity is selected based on trivial examples.
4077 worklist_(flow_graph, /*initial_capacity=*/10) {}
4078
4079 // Run analysis and eliminate dead/redundant Parameter-s.
4080 void Optimize();
4081
4082 private:
4083 // In precompiled mode we can eliminate all parameters that have no real uses
4084 // and subsequently clear out corresponding slots in the environments assigned
4085 // to instructions that can throw an exception which would be caught by
4086 // the corresponding CatchEntryBlock.
4087 //
4088 // Computing "dead" parameters is essentially a fixed point algorithm because
4089 // Parameter value can flow into another Parameter via an environment attached
4090 // to an instruction that can throw.
4091 //
4092 // Note: this optimization pass assumes that environment values are only
4093 // used during catching, that is why it should only be used in AOT mode.
4094 void OptimizeDeadParameters() {
4095 ASSERT(is_aot_);
4096
4097 NumberCatchEntryParameters();
4098 ComputeIncomingValues();
4099 CollectAliveParametersOrPhis();
4100 PropagateLivenessToInputs();
4101 EliminateDeadParameters();
4102 }
4103
4104 static intptr_t GetParameterId(const Instruction* instr) {
4105 return instr->GetPassSpecificId(CompilerPass::kTryCatchOptimization);
4106 }
4107
4108 static void SetParameterId(Instruction* instr, intptr_t id) {
4109 instr->SetPassSpecificId(CompilerPass::kTryCatchOptimization, id);
4110 }
4111
4112 static bool HasParameterId(Instruction* instr) {
4113 return instr->HasPassSpecificId(CompilerPass::kTryCatchOptimization);
4114 }
4115
4116 // Assign sequential ids to each ParameterInstr in each CatchEntryBlock.
4117 // Collect reverse mapping from try indexes to corresponding catches.
4118 void NumberCatchEntryParameters() {
4119 for (auto catch_entry : flow_graph_->graph_entry()->catch_entries()) {
4120 const GrowableArray<Definition*>& idefs =
4121 *catch_entry->initial_definitions();
4122 for (auto idef : idefs) {
4123 if (idef->IsParameter()) {
4124 SetParameterId(idef, parameter_info_.length());
4125 parameter_info_.Add(new ParameterInfo(idef->AsParameter()));
4126 }
4127 }
4128
4129 catch_by_index_.EnsureLength(catch_entry->catch_try_index() + 1, nullptr);
4130 catch_by_index_[catch_entry->catch_try_index()] = catch_entry;
4131 }
4132 }
4133
4134 // Compute potential incoming values for each Parameter in each catch block
4135 // by looking into environments assigned to MayThrow instructions within
4136 // blocks covered by the corresponding catch.
4137 void ComputeIncomingValues() {
4138 for (auto block : flow_graph_->reverse_postorder()) {
4139 if (block->try_index() == kInvalidTryIndex) {
4140 continue;
4141 }
4142
4143 ASSERT(block->try_index() < catch_by_index_.length());
4144 auto catch_entry = catch_by_index_[block->try_index()];
4145 const auto& idefs = *catch_entry->initial_definitions();
4146
4147 for (ForwardInstructionIterator instr_it(block); !instr_it.Done();
4148 instr_it.Advance()) {
4149 Instruction* current = instr_it.Current();
4150 if (!current->MayThrow()) continue;
4151
4152 Environment* env = current->env()->Outermost();
4153 ASSERT(env != nullptr);
4154
4155 for (intptr_t env_idx = 0; env_idx < idefs.length(); ++env_idx) {
4156 if (ParameterInstr* param = idefs[env_idx]->AsParameter()) {
4157 Definition* defn = env->ValueAt(env_idx)->definition();
4158
4159 // Add defn as an incoming value to the parameter if it is not
4160 // already present in the list.
4161 bool found = false;
4162 for (auto other_defn :
4163 parameter_info_[GetParameterId(param)]->incoming) {
4164 if (other_defn == defn) {
4165 found = true;
4166 break;
4167 }
4168 }
4169 if (!found) {
4170 parameter_info_[GetParameterId(param)]->incoming.Add(defn);
4171 }
4172 }
4173 }
4174 }
4175 }
4176 }
4177
4178 // Find all parameters (and phis) that are definitely alive - because they
4179 // have non-phi uses and place them into worklist.
4180 //
4181 // Note: phis that only have phi (and environment) uses would be marked as
4182 // dead.
4183 void CollectAliveParametersOrPhis() {
4184 for (auto block : flow_graph_->reverse_postorder()) {
4185 if (JoinEntryInstr* join = block->AsJoinEntry()) {
4186 if (join->phis() == nullptr) continue;
4187
4188 for (auto phi : *join->phis()) {
4189 phi->mark_dead();
4190 if (HasActualUse(phi)) {
4191 MarkLive(phi);
4192 }
4193 }
4194 }
4195 }
4196
4197 for (auto info : parameter_info_) {
4198 if (HasActualUse(info->instr)) {
4199 MarkLive(info->instr);
4200 }
4201 }
4202 }
4203
4204 // Propagate liveness from live parameters and phis to other parameters and
4205 // phis transitively.
4206 void PropagateLivenessToInputs() {
4207 while (!worklist_.IsEmpty()) {
4208 Definition* defn = worklist_.RemoveLast();
4209 if (ParameterInstr* param = defn->AsParameter()) {
4210 auto s = parameter_info_[GetParameterId(param)];
4211 for (auto input : s->incoming) {
4212 MarkLive(input);
4213 }
4214 } else if (PhiInstr* phi = defn->AsPhi()) {
4215 for (intptr_t i = 0; i < phi->InputCount(); i++) {
4216 MarkLive(phi->InputAt(i)->definition());
4217 }
4218 }
4219 }
4220 }
4221
4222 // Mark definition as live if it is a dead Phi or a dead Parameter and place
4223 // them into worklist.
4224 void MarkLive(Definition* defn) {
4225 if (PhiInstr* phi = defn->AsPhi()) {
4226 if (!phi->is_alive()) {
4227 phi->mark_alive();
4228 worklist_.Add(phi);
4229 }
4230 } else if (ParameterInstr* param = defn->AsParameter()) {
4231 if (HasParameterId(param)) {
4232 auto input_s = parameter_info_[GetParameterId(param)];
4233 if (!input_s->alive) {
4234 input_s->alive = true;
4235 worklist_.Add(param);
4236 }
4237 }
4238 } else if (UnboxInstr* unbox = defn->AsUnbox()) {
4239 MarkLive(unbox->value()->definition());
4240 }
4241 }
4242
4243 // Replace all dead parameters with null value and clear corresponding
4244 // slots in environments.
4245 void EliminateDeadParameters() {
4246 for (auto info : parameter_info_) {
4247 if (!info->alive) {
4248 info->instr->ReplaceUsesWith(flow_graph_->constant_null());
4249 }
4250 }
4251
4252 for (auto block : flow_graph_->reverse_postorder()) {
4253 if (block->try_index() == -1) continue;
4254
4255 auto catch_entry = catch_by_index_[block->try_index()];
4256 const auto& idefs = *catch_entry->initial_definitions();
4257
4258 for (ForwardInstructionIterator instr_it(block); !instr_it.Done();
4259 instr_it.Advance()) {
4260 Instruction* current = instr_it.Current();
4261 if (!current->MayThrow()) continue;
4262
4263 Environment* env = current->env()->Outermost();
4264 RELEASE_ASSERT(env != nullptr);
4265
4266 for (intptr_t env_idx = 0; env_idx < idefs.length(); ++env_idx) {
4267 if (ParameterInstr* param = idefs[env_idx]->AsParameter()) {
4268 if (!parameter_info_[GetParameterId(param)]->alive) {
4269 env->ValueAt(env_idx)->BindToEnvironment(
4270 flow_graph_->constant_null());
4271 }
4272 }
4273 }
4274 }
4275 }
4276
4278 }
4279
4280 // Returns true if definition has a use in an instruction which is not a phi.
4281 // Skip over Unbox instructions which may be inserted for unused phis.
4282 static bool HasActualUse(Definition* defn) {
4283 for (Value* use = defn->input_use_list(); use != nullptr;
4284 use = use->next_use()) {
4285 Instruction* use_instruction = use->instruction();
4286 if (UnboxInstr* unbox = use_instruction->AsUnbox()) {
4287 if (HasActualUse(unbox)) {
4288 return true;
4289 }
4290 } else if (!use_instruction->IsPhi()) {
4291 return true;
4292 }
4293 }
4294 return false;
4295 }
4296
4297 struct ParameterInfo : public ZoneAllocated {
4298 explicit ParameterInfo(ParameterInstr* instr) : instr(instr) {}
4299
4300 ParameterInstr* instr;
4301 bool alive = false;
4302 GrowableArray<Definition*> incoming;
4303 };
4304
4305 FlowGraph* const flow_graph_;
4306 const bool is_aot_;
4307
4308 // Additional information for each Parameter from each CatchBlockEntry.
4309 // Parameter-s are numbered and their number is stored in
4310 // Instruction::place_id() field which is otherwise not used for anything
4311 // at this stage.
4312 GrowableArray<ParameterInfo*> parameter_info_;
4313
4314 // Mapping from catch_try_index to corresponding CatchBlockEntry-s.
4315 GrowableArray<CatchBlockEntryInstr*> catch_by_index_;
4316
4317 // Worklist for live Phi and Parameter instructions which need to be
4318 // processed by PropagateLivenessToInputs.
4319 DefinitionWorklist worklist_;
4320};
4321
4322void OptimizeCatchEntryStates(FlowGraph* flow_graph, bool is_aot) {
4323 if (flow_graph->graph_entry()->catch_entries().is_empty()) {
4324 return;
4325 }
4326
4327 TryCatchAnalyzer analyzer(flow_graph, is_aot);
4328 analyzer.Optimize();
4329}
4330
4332 // Analyze catch entries and remove "dead" Parameter instructions.
4333 if (is_aot_) {
4334 OptimizeDeadParameters();
4335 }
4336
4337 // For every catch-block: Iterate over all call instructions inside the
4338 // corresponding try-block and figure out for each environment value if it
4339 // is the same constant at all calls. If yes, replace the initial definition
4340 // at the catch-entry with this constant.
4341 const GrowableArray<CatchBlockEntryInstr*>& catch_entries =
4342 flow_graph_->graph_entry()->catch_entries();
4343
4344 for (auto catch_entry : catch_entries) {
4345 // Initialize cdefs with the original initial definitions (ParameterInstr).
4346 // The following representation is used:
4347 // ParameterInstr => unknown
4348 // ConstantInstr => known constant
4349 // nullptr => non-constant
4350 GrowableArray<Definition*>* idefs = catch_entry->initial_definitions();
4351 GrowableArray<Definition*> cdefs(idefs->length());
4352 cdefs.AddArray(*idefs);
4353
4354 // exception_var and stacktrace_var are never constant. In asynchronous or
4355 // generator functions they may be context-allocated in which case they are
4356 // not tracked in the environment anyway.
4357
4358 cdefs[flow_graph_->EnvIndex(catch_entry->raw_exception_var())] = nullptr;
4359 cdefs[flow_graph_->EnvIndex(catch_entry->raw_stacktrace_var())] = nullptr;
4360
4361 for (BlockIterator block_it = flow_graph_->reverse_postorder_iterator();
4362 !block_it.Done(); block_it.Advance()) {
4363 BlockEntryInstr* block = block_it.Current();
4364 if (block->try_index() == catch_entry->catch_try_index()) {
4365 for (ForwardInstructionIterator instr_it(block); !instr_it.Done();
4366 instr_it.Advance()) {
4367 Instruction* current = instr_it.Current();
4368 if (current->MayThrow()) {
4369 Environment* env = current->env()->Outermost();
4370 ASSERT(env != nullptr);
4371 for (intptr_t env_idx = 0; env_idx < cdefs.length(); ++env_idx) {
4372 if (cdefs[env_idx] != nullptr && !cdefs[env_idx]->IsConstant() &&
4373 env->ValueAt(env_idx)->BindsToConstant()) {
4374 // If the recorded definition is not a constant, record this
4375 // definition as the current constant definition.
4376 cdefs[env_idx] = env->ValueAt(env_idx)->definition();
4377 }
4378 if (cdefs[env_idx] != env->ValueAt(env_idx)->definition()) {
4379 // Non-constant definitions are reset to nullptr.
4380 cdefs[env_idx] = nullptr;
4381 }
4382 }
4383 }
4384 }
4385 }
4386 }
4387 for (intptr_t j = 0; j < idefs->length(); ++j) {
4388 if (cdefs[j] != nullptr && cdefs[j]->IsConstant()) {
4389 Definition* old = (*idefs)[j];
4390 ConstantInstr* orig = cdefs[j]->AsConstant();
4392 new (flow_graph_->zone()) ConstantInstr(orig->value());
4393 flow_graph_->AllocateSSAIndex(copy);
4394 old->ReplaceUsesWith(copy);
4395 copy->set_previous(old->previous()); // partial link
4396 (*idefs)[j] = copy;
4397 }
4398 }
4399 }
4400}
4401
4402// Returns true iff this definition is used in a non-phi instruction.
4403static bool HasRealUse(Definition* def) {
4404 // Environment uses are real (non-phi) uses.
4405 if (def->env_use_list() != nullptr) return true;
4406
4407 for (Value::Iterator it(def->input_use_list()); !it.Done(); it.Advance()) {
4408 if (!it.Current()->instruction()->IsPhi()) return true;
4409 }
4410 return false;
4411}
4412
4414 GrowableArray<PhiInstr*> live_phis;
4415 for (BlockIterator b = flow_graph->postorder_iterator(); !b.Done();
4416 b.Advance()) {
4417 JoinEntryInstr* join = b.Current()->AsJoinEntry();
4418 if (join != nullptr) {
4419 for (PhiIterator it(join); !it.Done(); it.Advance()) {
4420 PhiInstr* phi = it.Current();
4421 // Phis that have uses and phis inside try blocks are
4422 // marked as live.
4423 if (HasRealUse(phi)) {
4424 live_phis.Add(phi);
4425 phi->mark_alive();
4426 } else {
4427 phi->mark_dead();
4428 }
4429 }
4430 }
4431 }
4432
4433 while (!live_phis.is_empty()) {
4434 PhiInstr* phi = live_phis.RemoveLast();
4435 for (intptr_t i = 0; i < phi->InputCount(); i++) {
4436 Value* val = phi->InputAt(i);
4437 PhiInstr* used_phi = val->definition()->AsPhi();
4438 if ((used_phi != nullptr) && !used_phi->is_alive()) {
4439 used_phi->mark_alive();
4440 live_phis.Add(used_phi);
4441 }
4442 }
4443 }
4444
4446}
4447
4449 FlowGraph* flow_graph) {
4450 for (auto block : flow_graph->postorder()) {
4451 if (JoinEntryInstr* join = block->AsJoinEntry()) {
4452 if (join->phis_ == nullptr) continue;
4453
4454 // Eliminate dead phis and compact the phis_ array of the block.
4455 intptr_t to_index = 0;
4456 for (intptr_t i = 0; i < join->phis_->length(); ++i) {
4457 PhiInstr* phi = (*join->phis_)[i];
4458 if (phi != nullptr) {
4459 if (!phi->is_alive()) {
4460 phi->ReplaceUsesWith(flow_graph->constant_null());
4461 phi->UnuseAllInputs();
4462 (*join->phis_)[i] = nullptr;
4463 if (FLAG_trace_optimization && flow_graph->should_print()) {
4464 THR_Print("Removing dead phi v%" Pd "\n", phi->ssa_temp_index());
4465 }
4466 } else {
4467 (*join->phis_)[to_index++] = phi;
4468 }
4469 }
4470 }
4471 if (to_index == 0) {
4472 join->phis_ = nullptr;
4473 } else {
4474 join->phis_->TruncateTo(to_index);
4475 }
4476 }
4477 }
4478}
4479
4482 BitVector live(flow_graph->zone(), flow_graph->current_ssa_temp_index());
4483
4484 // Mark all instructions with side-effects as live.
4485 for (BlockIterator block_it = flow_graph->reverse_postorder_iterator();
4486 !block_it.Done(); block_it.Advance()) {
4487 BlockEntryInstr* block = block_it.Current();
4488 for (ForwardInstructionIterator it(block); !it.Done(); it.Advance()) {
4489 Instruction* current = it.Current();
4490 ASSERT(!current->IsMoveArgument());
4491 // TODO(alexmarkov): take control dependencies into account and
4492 // eliminate dead branches/conditions.
4493 if (!current->CanEliminate(block)) {
4494 worklist.Add(current);
4495 if (Definition* def = current->AsDefinition()) {
4496 if (def->HasSSATemp()) {
4497 live.Add(def->ssa_temp_index());
4498 }
4499 }
4500 }
4501 }
4502 }
4503
4504 // Iteratively follow inputs of instructions in the work list.
4505 while (!worklist.is_empty()) {
4506 Instruction* current = worklist.RemoveLast();
4507 for (intptr_t i = 0, n = current->InputCount(); i < n; ++i) {
4508 Definition* input = current->InputAt(i)->definition();
4509 ASSERT(input->HasSSATemp());
4510 if (!live.Contains(input->ssa_temp_index())) {
4511 worklist.Add(input);
4512 live.Add(input->ssa_temp_index());
4513 }
4514 }
4515 for (intptr_t i = 0, n = current->ArgumentCount(); i < n; ++i) {
4516 Definition* input = current->ArgumentAt(i);
4517 ASSERT(input->HasSSATemp());
4518 if (!live.Contains(input->ssa_temp_index())) {
4519 worklist.Add(input);
4520 live.Add(input->ssa_temp_index());
4521 }
4522 }
4523 if (current->env() != nullptr) {
4524 for (Environment::DeepIterator it(current->env()); !it.Done();
4525 it.Advance()) {
4526 Definition* input = it.CurrentValue()->definition();
4527 ASSERT(!input->IsMoveArgument());
4528 if (input->HasSSATemp() && !live.Contains(input->ssa_temp_index())) {
4529 worklist.Add(input);
4530 live.Add(input->ssa_temp_index());
4531 }
4532 }
4533 }
4534 }
4535
4536 // Remove all instructions which are not marked as live.
4537 for (BlockIterator block_it = flow_graph->reverse_postorder_iterator();
4538 !block_it.Done(); block_it.Advance()) {
4539 BlockEntryInstr* block = block_it.Current();
4540 if (JoinEntryInstr* join = block->AsJoinEntry()) {
4541 for (PhiIterator it(join); !it.Done(); it.Advance()) {
4542 PhiInstr* current = it.Current();
4543 if (!live.Contains(current->ssa_temp_index())) {
4545 }
4546 }
4547 }
4548 for (ForwardInstructionIterator it(block); !it.Done(); it.Advance()) {
4549 Instruction* current = it.Current();
4550 if (!current->CanEliminate(block)) {
4551 continue;
4552 }
4553 ASSERT(!current->IsMoveArgument());
4554 ASSERT((current->ArgumentCount() == 0) || !current->HasMoveArguments());
4555 if (Definition* def = current->AsDefinition()) {
4556 if (def->HasSSATemp() && live.Contains(def->ssa_temp_index())) {
4557 continue;
4558 }
4559 }
4561 }
4562 }
4563}
4564
4565// Returns true if function is marked with vm:unsafe:no-interrupts pragma.
4569 /*only_core=*/false, function,
4570 Symbols::vm_unsafe_no_interrupts(),
4571 /*multiple=*/false, &options);
4572}
4573
4575 const bool should_remove_all = IsMarkedWithNoInterrupts(graph->function());
4576 if (should_remove_all) {
4577 for (auto entry : graph->reverse_postorder()) {
4578 for (ForwardInstructionIterator it(entry); !it.Done(); it.Advance()) {
4579 if (it.Current()->IsCheckStackOverflow()) {
4581 }
4582 }
4583 }
4584 return;
4585 }
4586
4587 CheckStackOverflowInstr* first_stack_overflow_instr = nullptr;
4588 for (auto entry : graph->reverse_postorder()) {
4589 for (ForwardInstructionIterator it(entry); !it.Done(); it.Advance()) {
4590 Instruction* current = it.Current();
4591
4592 if (CheckStackOverflowInstr* instr = current->AsCheckStackOverflow()) {
4593 if (first_stack_overflow_instr == nullptr) {
4594 first_stack_overflow_instr = instr;
4595 ASSERT(!first_stack_overflow_instr->in_loop());
4596 }
4597 continue;
4598 }
4599
4600 if (current->IsBranch()) {
4601 current = current->AsBranch()->comparison();
4602 }
4603
4604 if (current->HasUnknownSideEffects()) {
4605 return;
4606 }
4607 }
4608 }
4609
4610 if (first_stack_overflow_instr != nullptr) {
4611 first_stack_overflow_instr->RemoveFromGraph();
4612 }
4613}
4614
4615} // namespace dart
const char * options
static void info(const char *fmt,...) SK_PRINTF_LIKE(1
Definition: DM.cpp:213
TArray< uint32_t > Key
SkPoint pos
#define check(reporter, ref, unref, make, kill)
Definition: RefCntTest.cpp:85
static void encode(uint8_t output[16], const uint32_t input[4])
Definition: SkMD5.cpp:240
static void copy(void *dst, const uint8_t *src, int width, int bpp, int deltaSrc, int offset, const SkPMColor ctable[])
Definition: SkSwizzler.cpp:31
static sk_sp< GrTextureProxy > wrapped(skiatest::Reporter *reporter, GrRecordingContext *rContext, GrProxyProvider *proxyProvider, SkBackingFit fit)
SI void store(P *ptr, const T &val)
SI T load(const P *ptr)
Definition: Transform_inl.h:98
static size_t element_size(Layout layout, SkSLType type)
#define UNREACHABLE()
Definition: assert.h:248
#define RELEASE_ASSERT(cond)
Definition: assert.h:327
static AliasIdentity NotAliased()
Definition: il.h:2423
static AliasIdentity Aliased()
Definition: il.h:2420
bool IsNotAliased() const
Definition: il.h:2452
bool IsUnknown() const
Definition: il.h:2450
static AliasIdentity Unknown()
Definition: il.h:2417
static AliasIdentity AllocationSinkingCandidate()
Definition: il.h:2427
void PrintSet(BitVector *set)
bool CanBeAliased(Definition *alloc)
intptr_t max_place_id() const
AliasedSet(Zone *zone, PointerSet< Place > *places_map, ZoneGrowableArray< Place * > *places, PhiPlaceMoves *phi_moves, bool print_traces)
BitVector * aliased_by_effects() const
intptr_t LookupAliasId(const Place &alias)
BitVector * GetKilledSet(intptr_t alias)
const PhiPlaceMoves * phi_moves() const
Place * LookupCanonical(Place *place) const
const ZoneGrowableArray< Place * > & places() const
virtual const Slot * SlotForInput(intptr_t pos)
Definition: il.h:7347
MaterializeObjectInstr * MaterializationFor(Definition *alloc, Instruction *exit)
intptr_t GetConstantNumElements() const
Definition: il.h:7796
bool HasConstantNumElements() const
Definition: il.h:7793
KeyValueTrait::Value LookupValue(typename KeyValueTrait::Key key) const
Definition: hash_map.h:159
void Insert(typename KeyValueTrait::Pair kv)
Definition: hash_map.h:230
bool HasKey(typename KeyValueTrait::Key key) const
Definition: hash_map.h:52
Iterator GetIterator() const
Definition: hash_map.h:87
bool Contains(const T &other, bool isEqual(const T &, const T &)=nullptr) const
void AddArray(const BaseGrowableArray< T, B, Allocator > &src)
void Add(const T &value)
intptr_t length() const
void Add(intptr_t i)
Definition: bit_vector.h:63
bool Contains(intptr_t i) const
Definition: bit_vector.h:91
bool AddAll(const BitVector *from)
Definition: bit_vector.cc:52
void Remove(intptr_t i)
Definition: bit_vector.h:68
void CopyFrom(const BitVector *other)
Definition: bit_vector.h:54
intptr_t try_index() const
Definition: il.h:1730
intptr_t postorder_number() const
Definition: il.h:1658
intptr_t preorder_number() const
Definition: il.h:1655
intptr_t block_id() const
Definition: il.h:1661
const GrowableArray< BlockEntryInstr * > & dominated_blocks()
Definition: il.h:1673
bool Dominates(BlockEntryInstr *other) const
Definition: il.cc:1815
virtual BlockEntryInstr * PredecessorAt(intptr_t index) const =0
Instruction * last_instruction() const
Definition: il.h:1686
bool Done() const
Definition: flow_graph.h:46
CSEInstructionSet(const CSEInstructionSet &other)
void Insert(Instruction *instr)
Instruction * Lookup(Instruction *other) const
static void EliminateStackOverflow(FlowGraph *graph)
bool in_loop() const
Definition: il.h:9904
ClassPtr At(intptr_t cid) const
Definition: class_table.h:362
static CompileType FromCid(intptr_t cid)
bool is_aot() const
static CompilerState & Current()
const Object & value() const
Definition: il.h:4230
static void EliminateDeadPhis(FlowGraph *graph)
static void RemoveDeadAndRedundantPhisFromTheGraph(FlowGraph *graph)
static void EliminateDeadCode(FlowGraph *graph)
static void Optimize(FlowGraph *graph)
void Add(Definition *defn)
Definition: flow_graph.h:829
Definition * RemoveLast()
Definition: flow_graph.h:843
Value * env_use_list() const
Definition: il.h:2578
virtual AliasIdentity Identity() const
Definition: il.h:2660
Value * input_use_list() const
Definition: il.h:2575
CompileType * Type()
Definition: il.h:2521
void ReplaceUsesWith(Definition *other)
Definition: il.cc:1493
bool HasSSATemp() const
Definition: il.h:2508
virtual Definition * AsDefinition()
Definition: il.h:2683
Definition * OriginalDefinition()
Definition: il.cc:532
intptr_t ssa_temp_index() const
Definition: il.h:2503
friend class Value
Definition: il.h:2690
static void Optimize(FlowGraph *graph)
static constexpr intptr_t kNone
Definition: deopt_id.h:27
static bool Optimize(FlowGraph *graph, bool run_load_optimization=true)
void MarkAsLazyDeoptToBeforeDeoptId()
Definition: il.h:11678
void MarkAsHoisted()
Definition: il.h:11688
Environment * Outermost()
Definition: il.h:11701
FieldPtr Original() const
Definition: object.cc:11739
bool is_static() const
Definition: object.h:4440
const GrowableArray< BlockEntryInstr * > & reverse_postorder() const
Definition: flow_graph.h:207
GraphEntryInstr * graph_entry() const
Definition: flow_graph.h:268
ConstantInstr * GetConstant(const Object &object, Representation representation=kTagged)
Definition: flow_graph.cc:187
IsolateGroup * isolate_group() const
Definition: flow_graph.h:262
bool should_print() const
Definition: flow_graph.h:503
void EnsureSSATempIndex(Definition *defn, Definition *replacement)
Definition: flow_graph.cc:90
Zone * zone() const
Definition: flow_graph.h:261
intptr_t current_ssa_temp_index() const
Definition: flow_graph.h:243
void ReplaceCurrentInstruction(ForwardInstructionIterator *iterator, Instruction *current, Instruction *replacement)
Definition: flow_graph.cc:141
Thread * thread() const
Definition: flow_graph.h:260
const GrowableArray< BlockEntryInstr * > & preorder() const
Definition: flow_graph.h:203
void set_loop_invariant_loads(ZoneGrowableArray< BitVector * > *loop_invariant_loads)
Definition: flow_graph.h:455
BlockIterator postorder_iterator() const
Definition: flow_graph.h:222
const GrowableArray< BlockEntryInstr * > & postorder() const
Definition: flow_graph.h:204
ConstantInstr * constant_null() const
Definition: flow_graph.h:270
const LoopHierarchy & GetLoopHierarchy()
Definition: flow_graph.h:430
const Function & function() const
Definition: flow_graph.h:130
bool is_huge_method() const
Definition: flow_graph.h:423
bool is_licm_allowed() const
Definition: flow_graph.h:404
ZoneGrowableArray< BitVector * > * loop_invariant_loads() const
Definition: flow_graph.h:452
void AllocateSSAIndex(Definition *def)
Definition: flow_graph.h:274
BlockIterator reverse_postorder_iterator() const
Definition: flow_graph.h:219
void InsertBefore(Instruction *next, Instruction *instr, Environment *env, UseKind use_kind)
Definition: flow_graph.h:312
void InsertAfter(Instruction *prev, Instruction *instr, Environment *env, UseKind use_kind)
Definition: flow_graph.cc:273
intptr_t EnvIndex(const LocalVariable *variable) const
Definition: flow_graph.h:189
Instruction * Current() const
Definition: il.h:1853
const GrowableArray< CatchBlockEntryInstr * > & catch_entries() const
Definition: il.h:2012
void InsertBefore(Instruction *next)
Definition: il.h:1280
Instruction * next() const
Definition: il.h:1093
virtual intptr_t InputCount() const =0
intptr_t GetDeoptId() const
Definition: il.h:1409
virtual bool MayThrow() const =0
virtual bool HasUnknownSideEffects() const =0
virtual Value * InputAt(intptr_t i) const =0
virtual BlockEntryInstr * GetBlock()
Definition: il.cc:1352
virtual void CopyDeoptIdFrom(const Instruction &instr)
Definition: il.h:1411
Environment * env() const
Definition: il.h:1215
virtual bool CanEliminate(const BlockEntryInstr *block) const
Definition: il.cc:1575
void RemoveEnvironment()
Definition: il.cc:1282
virtual Representation RequiredInputRepresentation(intptr_t idx) const
Definition: il.h:1241
bool HasPassSpecificId(CompilerPass::Id pass) const
Definition: il.h:1232
void UnuseAllInputs()
Definition: il.cc:1534
virtual bool MayHaveVisibleEffect() const
Definition: il.h:1352
virtual intptr_t ArgumentCount() const
Definition: il.h:1041
Definition * ArgumentAt(intptr_t index) const
Definition: il.h:3441
virtual Representation representation() const
Definition: il.h:1260
bool CanDeoptimize() const
Definition: il.h:1079
intptr_t GetPassSpecificId(CompilerPass::Id pass) const
Definition: il.h:1224
virtual bool AllowsCSE() const
Definition: il.h:1291
virtual Tag tag() const =0
Instruction * RemoveFromGraph(bool return_previous=true)
Definition: il.cc:1301
bool HasMoveArguments() const
Definition: il.h:1059
void SetPassSpecificId(CompilerPass::Id pass, intptr_t id)
Definition: il.h:1229
const T * Cast() const
Definition: il.h:1186
virtual const char * DebugName() const =0
Instruction * previous() const
Definition: il.h:1087
ObjectStore * object_store() const
Definition: isolate.h:510
ClassTable * class_table() const
Definition: isolate.h:496
void OptimisticallySpecializeSmiPhis()
LICM(FlowGraph *flow_graph)
static bool FindPragma(Thread *T, bool only_core, const Object &object, const String &pragma_name, bool multiple=false, Object *options=nullptr)
Definition: object.cc:4151
GrowableArray< BitVector * > kill_
Definition: flow_graph.h:815
GrowableArray< BitVector * > live_out_
Definition: flow_graph.h:811
Zone * zone() const
Definition: flow_graph.h:799
GrowableArray< BitVector * > live_in_
Definition: flow_graph.h:819
const Slot & slot() const
Definition: il.h:8144
Value * instance() const
Definition: il.h:8143
virtual Representation representation() const
Definition: il.cc:921
intptr_t class_id() const
Definition: il.h:6803
Value * array() const
Definition: il.h:6800
intptr_t index_scale() const
Definition: il.h:6802
Value * index() const
Definition: il.h:6801
static bool OptimizeGraph(FlowGraph *graph)
LoadOptimizer(FlowGraph *graph, AliasedSet *aliased_set)
const ZoneGrowableArray< BlockEntryInstr * > & headers() const
Definition: loops.h:329
void ComputeInduction() const
Definition: loops.cc:1207
bool IsAlwaysTaken(BlockEntryInstr *block) const
Definition: loops.cc:1010
BitVector * blocks() const
Definition: loops.h:253
static ClassPtr context_class()
Definition: object.h:549
virtual const char * ToCString() const
Definition: object.h:366
static Object & Handle()
Definition: object.h:407
static Object & ZoneHandle()
Definition: object.h:419
bool is_alive() const
Definition: il.h:2827
void mark_dead()
Definition: il.h:2829
void mark_alive()
Definition: il.h:2828
virtual BlockEntryInstr * GetBlock()
Definition: il.h:2816
Move(intptr_t from, intptr_t to)
void CreateOutgoingMove(Zone *zone, BlockEntryInstr *block, intptr_t from, intptr_t to)
const ZoneGrowableArray< Move > * MovesList
MovesList GetOutgoingMoves(BlockEntryInstr *block) const
static Place * CreateAnyInstanceAnyIndexAlias(Zone *zone, intptr_t id)
static Place * Wrap(Zone *zone, const Place &place, intptr_t id)
static bool IsTypedDataViewAllocation(Definition *defn)
intptr_t index_constant() const
bool Equals(const Place &other) const
static const char * DefinitionName(Definition *def)
Place ToAlias() const
Place CopyWithoutIndex() const
Place ToSmallerElement(ElementSize to, intptr_t index) const
ElementSize element_size() const
const Field & static_field() const
bool IsImmutableField() const
Place(const Place &other)
Representation representation() const
Place(AllocationInstr *alloc, intptr_t input_pos)
Definition * instance() const
Place(Instruction *instr, bool *is_load, bool *is_store)
const char * ToCString() const
void set_instance(Definition *def)
const Field * static_field_
static bool IsAllocation(Definition *defn)
bool DependsOnInstance() const
const Slot * instance_field_
Place CopyWithoutInstance() const
intptr_t id() const
Place ToLargerElement(ElementSize to) const
Definition * index() const
const Slot & instance_field() const
static const Slot & GetArrayElementSlot(Thread *thread, intptr_t offset_in_bytes)
Definition: slot.cc:316
bool IsIdentical(const Slot &other) const
Definition: slot.h:549
bool is_immutable() const
Definition: slot.h:521
Representation representation() const
Definition: slot.h:519
bool may_contain_inner_pointer() const
Definition: slot.h:533
bool Contains(T value) const
Definition: locations.h:648
void Add(T value)
Definition: locations.h:650
static SmiPtr New(intptr_t value)
Definition: object.h:10006
Value * value() const
Definition: il.h:6424
Value * array() const
Definition: il.h:7081
intptr_t class_id() const
Definition: il.h:7086
intptr_t index_scale() const
Definition: il.h:7085
Value * index() const
Definition: il.h:7082
StoreOptimizer(FlowGraph *graph, AliasedSet *aliased_set, PointerSet< Place > *map)
static void OptimizeGraph(FlowGraph *graph)
Value * value() const
Definition: il.h:6730
Zone * zone() const
Definition: thread_state.h:37
static Thread * Current()
Definition: thread.h:362
void CheckForSafepoint()
Definition: thread.h:1104
TryCatchAnalyzer(FlowGraph *flow_graph, bool is_aot)
static constexpr uintptr_t RoundUpToPowerOfTwo(uintptr_t x)
Definition: utils.h:135
static constexpr int ShiftForPowerOfTwo(T x)
Definition: utils.h:81
static T Minimum(T x, T y)
Definition: utils.h:36
static constexpr bool IsAligned(T x, uintptr_t alignment, uintptr_t offset=0)
Definition: utils.h:92
Definition: il.h:75
Instruction * instruction() const
Definition: il.h:121
Definition * definition() const
Definition: il.h:103
void BindTo(Definition *definition)
Definition: il.h:2718
intptr_t InputCount() const
Definition: il.h:2794
Value * InputAt(intptr_t i) const
Definition: il.h:2795
ZonePlace(const Place &place)
char * PrintToString(const char *format,...) PRINTF_ATTRIBUTE(2
Definition: zone.cc:313
static intptr_t index_at_offset(intptr_t offset_in_bytes)
static word element_offset(intptr_t index)
static word ElementSizeFor(intptr_t cid)
Definition: runtime_api.cc:581
#define THR_Print(format,...)
Definition: log.h:20
#define ASSERT(E)
VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE auto & d
Definition: main.cc:19
VkInstance instance
Definition: main.cc:48
static bool b
struct MyStruct s
struct MyStruct a[10]
#define FATAL(error)
FlutterSemanticsFlag flags
uint8_t value
GAsyncResult * result
constexpr bool FLAG_support_il_printer
Definition: flag_list.h:48
Dart_NativeFunction function
Definition: fuchsia.cc:51
size_t length
ImplicitString Name
Definition: DMSrcSink.h:38
bool Contains(const Container &container, const Value &value)
Definition: copy.py:1
exit(kErrorExitCode)
Definition: dart_vm.cc:33
bool IsTypedDataViewClassId(intptr_t index)
Definition: class_id.h:439
bool IsTypedDataClassId(intptr_t index)
Definition: class_id.h:433
const char *const name
void OptimizeCatchEntryStates(FlowGraph *flow_graph, bool is_aot)
static Definition * GetStoredValue(Instruction *instr)
static bool IsMarkedWithNoInterrupts(const Function &function)
bool IsTypedDataBaseClassId(intptr_t index)
Definition: class_id.h:429
InnerPointerAccess
Definition: il.h:6295
void AddInstruction(GrowableArray< T * > *list, T *value)
static bool IsSupportedAllocation(Instruction *instr)
uint32_t CombineHashes(uint32_t hash, uint32_t other_hash)
Definition: hash.h:12
static bool IsLoopInvariantLoad(ZoneGrowableArray< BitVector * > *sets, intptr_t loop_header_index, Instruction *instr)
int32_t classid_t
Definition: globals.h:524
static Instruction * ExitForMaterialization(MaterializeObjectInstr *mat)
bool IsUnmodifiableTypedDataViewClassId(intptr_t index)
Definition: class_id.h:453
@ kDynamicCid
Definition: class_id.h:253
Representation
Definition: locations.h:66
GrowableArray< Value * > InputsArray
Definition: il.h:901
uintptr_t uword
Definition: globals.h:501
static bool IsValidLengthForAllocationSinking(ArrayAllocationInstr *array_alloc)
static AliasedSet * NumberPlaces(FlowGraph *graph, PointerSet< Place > *map, CSEMode mode)
static DART_FORCE_INLINE intptr_t GetPlaceId(const Instruction *instr)
static Definition * StoreDestination(Value *use)
static InnerPointerAccess AccessForSlotInAllocatedObject(Definition *alloc, const Slot &slot)
bool IsExternalPayloadClassId(classid_t cid)
Definition: class_id.h:472
static bool IsDataFieldOfTypedDataView(Definition *alloc, const Slot &slot)
DEFINE_FLAG(bool, print_cluster_information, false, "Print information about clusters written to snapshot")
constexpr intptr_t kBitsPerInt32
Definition: globals.h:466
const intptr_t cid
static PhiPlaceMoves * ComputePhiMoves(PointerSet< Place > *map, ZoneGrowableArray< Place * > *places, bool print_traces)
uint32_t FinalizeHash(uint32_t hash, intptr_t hashbits=kBitsPerInt32)
Definition: hash.h:20
static DART_FORCE_INLINE void SetPlaceId(Instruction *instr, intptr_t id)
static uint32_t Hash(uint32_t key)
Definition: hashmap_test.cc:65
static bool IsLoadEliminationCandidate(Instruction *instr)
static intptr_t RepresentationBits(Representation r)
Definition: il.cc:2134
constexpr int32_t kMaxInt32
Definition: globals.h:483
static bool IsConstant(Definition *def, int64_t *val)
Definition: loops.cc:123
static bool HasRealUse(Definition *def)
static bool IsPhiDependentPlace(Place *place)
static constexpr intptr_t kInvalidTryIndex
@ kAlignedAccess
Definition: il.h:6766
static void AddSlot(ZoneGrowableArray< const Slot * > *slots, const Slot &slot)
static Instruction * FirstMaterializationAt(Instruction *exit)
static DART_FORCE_INLINE bool HasPlaceId(const Instruction *instr)
static Definition * LoadSource(Definition *instr)
Definition: dom.py:1
Definition: __init__.py:1
it will be possible to load the file into Perfetto s trace viewer disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive mode
Definition: switches.h:228
it will be possible to load the file into Perfetto s trace viewer disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive keep the shell running after the Dart script has completed enable serial On low power devices with low core running concurrent GC tasks on threads can cause them to contend with the UI thread which could potentially lead to jank This option turns off all concurrent GC activities domain network JSON encoded network policy per domain This overrides the DisallowInsecureConnections switch Embedder can specify whether to allow or disallow insecure connections at a domain level old gen heap size
Definition: switches.h:259
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir Path to the cache directory This is different from the persistent_cache_path in embedder which is used for Skia shader cache icu native lib Path to the library file that exports the ICU data vm service The hostname IP address on which the Dart VM Service should be served If not set
Definition: switches.h:76
Definition: gen.py:1
SI auto map(std::index_sequence< I... >, Fn &&fn, const Args &... args) -> skvx::Vec< sizeof...(I), decltype(fn(args[0]...))>
Definition: SkVx.h:680
#define Pd
Definition: globals.h:408
static DecodeResult decode(std::string path)
Definition: png_codec.cpp:124
#define T
Definition: precompiler.cc:65
#define Z
static const char header[]
Definition: skpbench.cpp:88
static SkString join(const CommandLineFlags::StringArray &)
Definition: skpbench.cpp:741
const Scalar scale
SeparatedVector2 offset
static constexpr size_t ValueSize(Representation rep)
Definition: locations.h:112
static constexpr bool IsUnboxedInteger(Representation rep)
Definition: locations.h:92
static constexpr bool IsUnboxed(Representation rep)
Definition: locations.h:101
static const char * ToCString(Representation rep)
Definition: locations.cc:129
static Representation RepresentationOfArrayElement(classid_t cid)
Definition: locations.cc:79