Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
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> {};
651 class RepresentationBits
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
782 void PrintSet(BitVector* set) {
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;
990 i <= Place::kLargestElementSize; i++) {
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
1372 PointerSet<Place>* map,
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,
2974 CSEInstructionSet* map) {
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,
3016 PointerSet<Place>* map)
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 =
3084 new (zone) BitVector(zone, aliased_set_->max_place_id());
3086 const auto& places = aliased_set_->places();
3087 // Go through all places and identify those which are escaping.
3088 // We find such places by inspecting definition allocation
3089 // [AliasIdentity] field, which is populated above by
3090 // [AliasedSet::ComputeAliasing].
3091 for (intptr_t i = 0; i < places.length(); i++) {
3092 Place* place = places[i];
3093 if (place->DependsOnInstance()) {
3094 Definition* instance = place->instance();
3096 !instance->Identity().IsAliased()) {
3097 continue;
3098 }
3099 }
3100 all_aliased_places->Add(i);
3101 }
3102 }
3103
3104 for (BlockIterator block_it = graph_->postorder_iterator();
3105 !block_it.Done(); block_it.Advance()) {
3106 BlockEntryInstr* block = block_it.Current();
3107 const intptr_t postorder_number = block->postorder_number();
3108
3109 BitVector* kill = kill_[postorder_number];
3110 BitVector* live_in = live_in_[postorder_number];
3111 BitVector* live_out = live_out_[postorder_number];
3112
3113 ZoneGrowableArray<Instruction*>* exposed_stores = nullptr;
3114
3115 // Iterate backwards starting at the last instruction.
3116 for (BackwardInstructionIterator instr_it(block); !instr_it.Done();
3117 instr_it.Advance()) {
3118 Instruction* instr = instr_it.Current();
3119
3120 bool is_load = false;
3121 bool is_store = false;
3122 Place place(instr, &is_load, &is_store);
3123 if (place.IsImmutableField()) {
3124 // Loads/stores of final fields do not participate.
3125 continue;
3126 }
3127
3128 // Handle stores.
3129 if (is_store) {
3130 if (kill->Contains(GetPlaceId(instr))) {
3131 if (!live_in->Contains(GetPlaceId(instr)) &&
3132 CanEliminateStore(instr)) {
3133 if (FLAG_trace_optimization && graph_->should_print()) {
3134 THR_Print("Removing dead store to place %" Pd " in block B%" Pd
3135 "\n",
3136 GetPlaceId(instr), block->block_id());
3137 }
3138 instr_it.RemoveCurrentFromGraph();
3139 }
3140 } else if (!live_in->Contains(GetPlaceId(instr))) {
3141 // Mark this store as down-ward exposed: They are the only
3142 // candidates for the global store elimination.
3143 if (exposed_stores == nullptr) {
3144 const intptr_t kMaxExposedStoresInitialSize = 5;
3145 exposed_stores = new (zone) ZoneGrowableArray<Instruction*>(
3146 Utils::Minimum(kMaxExposedStoresInitialSize,
3147 aliased_set_->max_place_id()));
3148 }
3149 exposed_stores->Add(instr);
3150 }
3151 // Interfering stores kill only loads from the same place.
3152 kill->Add(GetPlaceId(instr));
3153 live_in->Remove(GetPlaceId(instr));
3154 continue;
3155 }
3156
3157 if (instr->IsThrow() || instr->IsReThrow() || instr->IsReturnBase()) {
3158 // Initialize live-out for exit blocks since it won't be computed
3159 // otherwise during the fixed point iteration.
3160 live_out->CopyFrom(all_places);
3161 }
3162
3163 // Handle side effects, deoptimization and function return.
3165 if (instr->HasUnknownSideEffects() || instr->IsReturnBase()) {
3166 // An instruction that returns or has unknown side effects
3167 // is treated as if it loads from all places.
3168 live_in->CopyFrom(all_places);
3169 continue;
3170 } else if (instr->MayThrow()) {
3171 if (block->try_index() == kInvalidTryIndex) {
3172 // Outside of a try-catch block, an instruction that may throw
3173 // is only treated as if it loads from escaping places.
3174 live_in->AddAll(all_aliased_places);
3175 } else {
3176 // Inside of a try-catch block, an instruction that may throw
3177 // is treated as if it loads from all places.
3178 live_in->CopyFrom(all_places);
3179 }
3180 continue;
3181 }
3182 } else { // jit
3183 // Similar optimization to the above could be implemented in JIT
3184 // as long as deoptimization side-effects are taken into account.
3185 // Conceptually variables in deoptimization environment for
3186 // "MayThrow" instructions have to be also added to the
3187 // [live_in] set as they can be considered as escaping (to
3188 // unoptimized code). However those deoptimization environment
3189 // variables include also non-escaping(not aliased) ones, so
3190 // how to deal with that needs to be figured out.
3191 if (instr->HasUnknownSideEffects() || instr->CanDeoptimize() ||
3192 instr->MayThrow() || instr->IsReturnBase()) {
3193 // Instructions that return from the function, instructions with
3194 // side effects and instructions that can deoptimize are considered
3195 // as loads from all places.
3196 live_in->CopyFrom(all_places);
3197 continue;
3198 }
3199 }
3200
3201 // Handle loads.
3202 Definition* defn = instr->AsDefinition();
3203 if ((defn != nullptr) && IsLoadEliminationCandidate(defn)) {
3204 const intptr_t alias = aliased_set_->LookupAliasId(place.ToAlias());
3205 live_in->AddAll(aliased_set_->GetKilledSet(alias));
3206 continue;
3207 }
3208 }
3209 exposed_stores_[postorder_number] = exposed_stores;
3210 }
3211 if (FLAG_trace_load_optimization && graph_->should_print()) {
3212 Dump();
3213 THR_Print("---\n");
3214 }
3215 }
3216
3217 void EliminateDeadStores() {
3218 // Iteration order does not matter here.
3219 for (BlockIterator block_it = graph_->postorder_iterator();
3220 !block_it.Done(); block_it.Advance()) {
3221 BlockEntryInstr* block = block_it.Current();
3222 const intptr_t postorder_number = block->postorder_number();
3223
3224 BitVector* live_out = live_out_[postorder_number];
3225
3226 ZoneGrowableArray<Instruction*>* exposed_stores =
3227 exposed_stores_[postorder_number];
3228 if (exposed_stores == nullptr) continue; // No exposed stores.
3229
3230 // Iterate over candidate stores.
3231 for (intptr_t i = 0; i < exposed_stores->length(); ++i) {
3232 Instruction* instr = (*exposed_stores)[i];
3233 bool is_load = false;
3234 bool is_store = false;
3235 Place place(instr, &is_load, &is_store);
3236 ASSERT(!is_load && is_store);
3237 if (place.IsImmutableField()) {
3238 // Final field do not participate in dead store elimination.
3239 continue;
3240 }
3241 // Eliminate a downward exposed store if the corresponding place is not
3242 // in live-out.
3243 if (!live_out->Contains(GetPlaceId(instr)) &&
3244 CanEliminateStore(instr)) {
3245 if (FLAG_trace_optimization && graph_->should_print()) {
3246 THR_Print("Removing dead store to place %" Pd " block B%" Pd "\n",
3247 GetPlaceId(instr), block->block_id());
3248 }
3249 instr->RemoveFromGraph(/* ignored */ false);
3250 }
3251 }
3252 }
3253 }
3254
3255 FlowGraph* graph_;
3256 PointerSet<Place>* map_;
3257
3258 // Mapping between field offsets in words and expression ids of loads from
3259 // that offset.
3260 AliasedSet* aliased_set_;
3261
3262 // Per block list of downward exposed stores.
3263 GrowableArray<ZoneGrowableArray<Instruction*>*> exposed_stores_;
3264
3265 DISALLOW_COPY_AND_ASSIGN(StoreOptimizer);
3266};
3267
3269 if (FLAG_dead_store_elimination && FLAG_load_cse) {
3271 }
3272}
3273
3274//
3275// Allocation Sinking
3276//
3277
3279 ArrayAllocationInstr* array_alloc) {
3280 const intptr_t kMaxAllocationSinkingNumElements = 32;
3281 if (!array_alloc->HasConstantNumElements()) {
3282 return false;
3283 }
3284 const intptr_t length = array_alloc->GetConstantNumElements();
3285 return (length >= 0) && (length <= kMaxAllocationSinkingNumElements);
3286}
3287
3288// Returns true if the given instruction is an allocation that
3289// can be sunk by the Allocation Sinking pass.
3291 return instr->IsAllocation() &&
3292 (!instr->IsArrayAllocation() ||
3293 IsValidLengthForAllocationSinking(instr->AsArrayAllocation()));
3294}
3295
3296// Check if the use is safe for allocation sinking. Allocation sinking
3297// candidates can only be used as inputs to store and allocation instructions:
3298//
3299// - any store into the allocation candidate itself is unconditionally safe
3300// as it just changes the rematerialization state of this candidate;
3301// - store into another object is only safe if the other object is
3302// an allocation candidate.
3303// - use as input to another allocation is only safe if the other allocation
3304// is a candidate.
3305//
3306// We use a simple fix-point algorithm to discover the set of valid candidates
3307// (see CollectCandidates method), that's why this IsSafeUse can operate in two
3308// modes:
3309//
3310// - optimistic, when every allocation is assumed to be an allocation
3311// sinking candidate;
3312// - strict, when only marked allocations are assumed to be allocation
3313// sinking candidates.
3314//
3315// Fix-point algorithm in CollectCandidates first collects a set of allocations
3316// optimistically and then checks each collected candidate strictly and unmarks
3317// invalid candidates transitively until only strictly valid ones remain.
3318bool AllocationSinking::IsSafeUse(Value* use, SafeUseCheck check_type) {
3319 ASSERT(IsSupportedAllocation(use->definition()));
3320
3321 if (use->instruction()->IsMaterializeObject()) {
3322 return true;
3323 }
3324
3325 if (auto* const alloc = use->instruction()->AsAllocation()) {
3326 return IsSupportedAllocation(alloc) &&
3327 ((check_type == kOptimisticCheck) ||
3328 alloc->Identity().IsAllocationSinkingCandidate());
3329 }
3330
3331 if (auto* store = use->instruction()->AsStoreField()) {
3332 if (use == store->value()) {
3333 Definition* instance = store->instance()->definition();
3335 ((check_type == kOptimisticCheck) ||
3336 instance->Identity().IsAllocationSinkingCandidate());
3337 }
3338 return true;
3339 }
3340
3341 if (auto* store = use->instruction()->AsStoreIndexed()) {
3342 if (use == store->index()) {
3343 return false;
3344 }
3345 if (use == store->array()) {
3346 if (!store->index()->BindsToSmiConstant()) {
3347 return false;
3348 }
3349 const intptr_t index = store->index()->BoundSmiConstant();
3350 if (index < 0 || index >= use->definition()
3351 ->AsArrayAllocation()
3352 ->GetConstantNumElements()) {
3353 return false;
3354 }
3355 if (auto* alloc_typed_data = use->definition()->AsAllocateTypedData()) {
3356 if (store->class_id() != alloc_typed_data->class_id() ||
3357 !store->aligned() ||
3358 store->index_scale() != compiler::target::Instance::ElementSizeFor(
3359 alloc_typed_data->class_id())) {
3360 return false;
3361 }
3362 }
3363 }
3364 if (use == store->value()) {
3365 Definition* instance = store->array()->definition();
3367 ((check_type == kOptimisticCheck) ||
3368 instance->Identity().IsAllocationSinkingCandidate());
3369 }
3370 return true;
3371 }
3372
3373 return false;
3374}
3375
3376// Right now we are attempting to sink allocation only into
3377// deoptimization exit. So candidate should only be used in StoreField
3378// instructions that write into fields of the allocated object.
3379bool AllocationSinking::IsAllocationSinkingCandidate(Definition* alloc,
3380 SafeUseCheck check_type) {
3381 for (Value* use = alloc->input_use_list(); use != nullptr;
3382 use = use->next_use()) {
3383 if (!IsSafeUse(use, check_type)) {
3384 if (FLAG_support_il_printer && FLAG_trace_optimization &&
3385 flow_graph_->should_print()) {
3386 THR_Print("use of %s at %s is unsafe for allocation sinking\n",
3387 alloc->ToCString(), use->instruction()->ToCString());
3388 }
3389 return false;
3390 }
3391 }
3392
3393 return true;
3394}
3395
3396// If the given use is a store into an object then return an object we are
3397// storing into.
3399 if (auto* const alloc = use->instruction()->AsAllocation()) {
3400 return alloc;
3401 }
3402 if (auto* const store = use->instruction()->AsStoreField()) {
3403 return store->instance()->definition();
3404 }
3405 if (auto* const store = use->instruction()->AsStoreIndexed()) {
3406 return store->array()->definition();
3407 }
3408 return nullptr;
3409}
3410
3411// If the given instruction is a load from an object, then return an object
3412// we are loading from.
3414 if (auto load = instr->AsLoadField()) {
3415 return load->instance()->definition();
3416 }
3417 if (auto load = instr->AsLoadIndexed()) {
3418 return load->array()->definition();
3419 }
3420 return nullptr;
3421}
3422
3423// Remove the given allocation from the graph. It is not observable.
3424// If deoptimization occurs the object will be materialized.
3425void AllocationSinking::EliminateAllocation(Definition* alloc) {
3426 ASSERT(IsAllocationSinkingCandidate(alloc, kStrictCheck));
3427
3428 if (FLAG_trace_optimization && flow_graph_->should_print()) {
3429 THR_Print("removing allocation from the graph: v%" Pd "\n",
3430 alloc->ssa_temp_index());
3431 }
3432
3433 // As an allocation sinking candidate, remove stores to this candidate.
3434 // Do this in a two-step process, as this allocation may be used multiple
3435 // times in a single instruction (e.g., as the instance and the value in
3436 // a StoreField). This means multiple entries may be removed from the
3437 // use list when removing instructions, not just the current one, so
3438 // Value::Iterator cannot be safely used.
3439 GrowableArray<Instruction*> stores_to_remove;
3440 for (Value* use = alloc->input_use_list(); use != nullptr;
3441 use = use->next_use()) {
3442 Instruction* const instr = use->instruction();
3443 Definition* const instance = StoreDestination(use);
3444 // All uses of a candidate should be stores or other allocations.
3445 ASSERT(instance != nullptr);
3446 if (instance == alloc) {
3447 // An allocation instruction cannot be a direct input to itself.
3448 ASSERT(!instr->IsAllocation());
3449 stores_to_remove.Add(instr);
3450 } else {
3451 // The candidate is being stored into another candidate, either through
3452 // a store instruction or as the input to a to-be-eliminated allocation,
3453 // so this instruction will be removed with the other candidate.
3454 ASSERT(candidates_.Contains(instance));
3455 }
3456 }
3457
3458 for (auto* const store : stores_to_remove) {
3459 // Avoid calling RemoveFromGraph() more than once on the same instruction.
3460 if (store->previous() != nullptr) {
3461 store->RemoveFromGraph();
3462 }
3463 }
3464
3465// There should be no environment uses. The pass replaced them with
3466// MaterializeObject instructions.
3467#ifdef DEBUG
3468 for (Value* use = alloc->env_use_list(); use != nullptr;
3469 use = use->next_use()) {
3470 ASSERT(use->instruction()->IsMaterializeObject());
3471 }
3472#endif
3473
3474 alloc->RemoveFromGraph();
3475}
3476
3477// Find allocation instructions that can be potentially eliminated and
3478// rematerialized at deoptimization exits if needed. See IsSafeUse
3479// for the description of algorithm used below.
3480void AllocationSinking::CollectCandidates() {
3481 // Optimistically collect all potential candidates.
3482 for (BlockIterator block_it = flow_graph_->reverse_postorder_iterator();
3483 !block_it.Done(); block_it.Advance()) {
3484 BlockEntryInstr* block = block_it.Current();
3485 for (ForwardInstructionIterator it(block); !it.Done(); it.Advance()) {
3486 Instruction* current = it.Current();
3487 if (!IsSupportedAllocation(current)) {
3488 continue;
3489 }
3490
3491 Definition* alloc = current->Cast<Definition>();
3492 if (IsAllocationSinkingCandidate(alloc, kOptimisticCheck)) {
3493 alloc->SetIdentity(AliasIdentity::AllocationSinkingCandidate());
3494 candidates_.Add(alloc);
3495 }
3496 }
3497 }
3498
3499 // Transitively unmark all candidates that are not strictly valid.
3500 bool changed;
3501 do {
3502 changed = false;
3503 for (intptr_t i = 0; i < candidates_.length(); i++) {
3504 Definition* alloc = candidates_[i];
3505 if (alloc->Identity().IsAllocationSinkingCandidate()) {
3506 if (!IsAllocationSinkingCandidate(alloc, kStrictCheck)) {
3507 alloc->SetIdentity(AliasIdentity::Unknown());
3508 changed = true;
3509 }
3510 }
3511 }
3512 } while (changed);
3513
3514 // Shrink the list of candidates removing all unmarked ones.
3515 intptr_t j = 0;
3516 for (intptr_t i = 0; i < candidates_.length(); i++) {
3517 Definition* alloc = candidates_[i];
3518 if (alloc->Identity().IsAllocationSinkingCandidate()) {
3519 if (FLAG_trace_optimization && flow_graph_->should_print()) {
3520 THR_Print("discovered allocation sinking candidate: v%" Pd "\n",
3521 alloc->ssa_temp_index());
3522 }
3523
3524 if (j != i) {
3525 candidates_[j] = alloc;
3526 }
3527 j++;
3528 }
3529 }
3530 candidates_.TruncateTo(j);
3531}
3532
3533// If materialization references an allocation sinking candidate then replace
3534// this reference with a materialization which should have been computed for
3535// this side-exit. CollectAllExits should have collected this exit.
3536void AllocationSinking::NormalizeMaterializations() {
3537 for (intptr_t i = 0; i < candidates_.length(); i++) {
3538 Definition* alloc = candidates_[i];
3539
3540 Value* next_use;
3541 for (Value* use = alloc->input_use_list(); use != nullptr; use = next_use) {
3542 next_use = use->next_use();
3543 if (use->instruction()->IsMaterializeObject()) {
3544 use->BindTo(MaterializationFor(alloc, use->instruction()));
3545 }
3546 }
3547 }
3548}
3549
3550// We transitively insert materializations at each deoptimization exit that
3551// might see the given allocation (see ExitsCollector). Some of these
3552// materializations are not actually used and some fail to compute because
3553// they are inserted in the block that is not dominated by the allocation.
3554// Remove unused materializations from the graph.
3555void AllocationSinking::RemoveUnusedMaterializations() {
3556 intptr_t j = 0;
3557 for (intptr_t i = 0; i < materializations_.length(); i++) {
3558 MaterializeObjectInstr* mat = materializations_[i];
3559 if ((mat->input_use_list() == nullptr) &&
3560 (mat->env_use_list() == nullptr)) {
3561 // Check if this materialization failed to compute and remove any
3562 // unforwarded loads. There were no loads from any allocation sinking
3563 // candidate in the beginning so it is safe to assume that any encountered
3564 // load was inserted by CreateMaterializationAt.
3565 for (intptr_t i = 0; i < mat->InputCount(); i++) {
3566 Definition* load = mat->InputAt(i)->definition();
3567 if (LoadSource(load) == mat->allocation()) {
3568 load->ReplaceUsesWith(flow_graph_->constant_null());
3569 load->RemoveFromGraph();
3570 }
3571 }
3572 mat->RemoveFromGraph();
3573 } else {
3574 if (j != i) {
3575 materializations_[j] = mat;
3576 }
3577 j++;
3578 }
3579 }
3580 materializations_.TruncateTo(j);
3581}
3582
3583// Some candidates might stop being eligible for allocation sinking after
3584// the load forwarding because they flow into phis that load forwarding
3585// inserts. Discover such allocations and remove them from the list
3586// of allocation sinking candidates undoing all changes that we did
3587// in preparation for sinking these allocations.
3588void AllocationSinking::DiscoverFailedCandidates() {
3589 // Transitively unmark all candidates that are not strictly valid.
3590 bool changed;
3591 do {
3592 changed = false;
3593 for (intptr_t i = 0; i < candidates_.length(); i++) {
3594 Definition* alloc = candidates_[i];
3595 if (alloc->Identity().IsAllocationSinkingCandidate()) {
3596 if (!IsAllocationSinkingCandidate(alloc, kStrictCheck)) {
3597 alloc->SetIdentity(AliasIdentity::Unknown());
3598 changed = true;
3599 }
3600 }
3601 }
3602 } while (changed);
3603
3604 // Remove all failed candidates from the candidates list.
3605 intptr_t j = 0;
3606 for (intptr_t i = 0; i < candidates_.length(); i++) {
3607 Definition* alloc = candidates_[i];
3608 if (!alloc->Identity().IsAllocationSinkingCandidate()) {
3609 if (FLAG_trace_optimization && flow_graph_->should_print()) {
3610 THR_Print("allocation v%" Pd " can't be eliminated\n",
3611 alloc->ssa_temp_index());
3612 }
3613
3614#ifdef DEBUG
3615 for (Value* use = alloc->env_use_list(); use != nullptr;
3616 use = use->next_use()) {
3617 ASSERT(use->instruction()->IsMaterializeObject());
3618 }
3619#endif
3620
3621 // All materializations will be removed from the graph. Remove inserted
3622 // loads first and detach materializations from allocation's environment
3623 // use list: we will reconstruct it when we start removing
3624 // materializations.
3625 alloc->set_env_use_list(nullptr);
3626 for (Value* use = alloc->input_use_list(); use != nullptr;
3627 use = use->next_use()) {
3628 if (use->instruction()->IsLoadField() ||
3629 use->instruction()->IsLoadIndexed()) {
3630 Definition* load = use->instruction()->AsDefinition();
3631 load->ReplaceUsesWith(flow_graph_->constant_null());
3632 load->RemoveFromGraph();
3633 } else {
3634 ASSERT(use->instruction()->IsMaterializeObject() ||
3635 use->instruction()->IsPhi() ||
3636 use->instruction()->IsStoreField() ||
3637 use->instruction()->IsStoreIndexed());
3638 }
3639 }
3640 } else {
3641 if (j != i) {
3642 candidates_[j] = alloc;
3643 }
3644 j++;
3645 }
3646 }
3647
3648 if (j != candidates_.length()) { // Something was removed from candidates.
3649 intptr_t k = 0;
3650 for (intptr_t i = 0; i < materializations_.length(); i++) {
3651 MaterializeObjectInstr* mat = materializations_[i];
3652 if (!mat->allocation()->Identity().IsAllocationSinkingCandidate()) {
3653 // Restore environment uses of the allocation that were replaced
3654 // by this materialization and drop materialization.
3655 mat->ReplaceUsesWith(mat->allocation());
3656 mat->RemoveFromGraph();
3657 } else {
3658 if (k != i) {
3659 materializations_[k] = mat;
3660 }
3661 k++;
3662 }
3663 }
3664 materializations_.TruncateTo(k);
3665 }
3666
3667 candidates_.TruncateTo(j);
3668}
3669
3671 // Allocation sinking depends on load forwarding, so give up early if load
3672 // forwarding is disabled.
3673 if (!FLAG_load_cse || flow_graph_->is_huge_method()) {
3674 return;
3675 }
3676
3677 CollectCandidates();
3678
3679 // Insert MaterializeObject instructions that will describe the state of the
3680 // object at all deoptimization points. Each inserted materialization looks
3681 // like this (where v_0 is allocation that we are going to eliminate):
3682 // v_1 <- LoadField(v_0, field_1)
3683 // ...
3684 // v_N <- LoadField(v_0, field_N)
3685 // v_{N+1} <- MaterializeObject(field_1 = v_1, ..., field_N = v_N)
3686 //
3687 // For typed data objects materialization looks like this:
3688 // v_1 <- LoadIndexed(v_0, index_1)
3689 // ...
3690 // v_N <- LoadIndexed(v_0, index_N)
3691 // v_{N+1} <- MaterializeObject([index_1] = v_1, ..., [index_N] = v_N)
3692 //
3693 // For arrays materialization looks like this:
3694 // v_1 <- LoadIndexed(v_0, index_1)
3695 // ...
3696 // v_N <- LoadIndexed(v_0, index_N)
3697 // v_{N+1} <- LoadField(v_0, Array.type_arguments)
3698 // v_{N+2} <- MaterializeObject([index_1] = v_1, ..., [index_N] = v_N,
3699 // type_arguments = v_{N+1})
3700 //
3701 for (intptr_t i = 0; i < candidates_.length(); i++) {
3702 InsertMaterializations(candidates_[i]);
3703 }
3704
3705 // Run load forwarding to eliminate LoadField/LoadIndexed instructions
3706 // inserted above.
3707 //
3708 // All loads will be successfully eliminated because:
3709 // a) they use fields/constant indices and thus provide precise aliasing
3710 // information
3711 // b) candidate does not escape and thus its fields/elements are not
3712 // affected by external effects from calls.
3713 LoadOptimizer::OptimizeGraph(flow_graph_);
3714
3715 NormalizeMaterializations();
3716
3717 RemoveUnusedMaterializations();
3718
3719 // If any candidates are no longer eligible for allocation sinking abort
3720 // the optimization for them and undo any changes we did in preparation.
3721 DiscoverFailedCandidates();
3722
3723 // At this point we have computed the state of object at each deoptimization
3724 // point and we can eliminate it. Loads inserted above were forwarded so there
3725 // are no uses of the allocation outside other candidates to eliminate, just
3726 // as in the beginning of the pass.
3727 for (intptr_t i = 0; i < candidates_.length(); i++) {
3728 EliminateAllocation(candidates_[i]);
3729 }
3730
3731 // Process materializations and unbox their arguments: materializations
3732 // are part of the environment and can materialize boxes for double/mint/simd
3733 // values when needed.
3734 // TODO(vegorov): handle all box types here.
3735 for (intptr_t i = 0; i < materializations_.length(); i++) {
3736 MaterializeObjectInstr* mat = materializations_[i];
3737 for (intptr_t j = 0; j < mat->InputCount(); j++) {
3738 Definition* defn = mat->InputAt(j)->definition();
3739 if (defn->IsBox()) {
3740 mat->InputAt(j)->BindTo(defn->InputAt(0)->definition());
3741 }
3742 }
3743 }
3744}
3745
3746// Remove materializations from the graph. Register allocator will treat them
3747// as part of the environment not as a real instruction.
3749 for (MaterializeObjectInstr* mat : materializations_) {
3750 mat->previous()->LinkTo(mat->next());
3751 mat->set_next(nullptr);
3752 mat->set_previous(nullptr);
3753 }
3754}
3755
3756// Add a field/offset to the list of fields if it is not yet present there.
3757static void AddSlot(ZoneGrowableArray<const Slot*>* slots, const Slot& slot) {
3758 if (!slots->Contains(&slot)) {
3759 slots->Add(&slot);
3760 }
3761}
3762
3763// Find deoptimization exit for the given materialization assuming that all
3764// materializations are emitted right before the instruction which is a
3765// deoptimization exit.
3767 while (mat->next()->IsMaterializeObject()) {
3768 mat = mat->next()->AsMaterializeObject();
3769 }
3770 return mat->next();
3771}
3772
3773// Given the deoptimization exit find first materialization that was inserted
3774// before it.
3776 while (exit->previous()->IsMaterializeObject()) {
3777 exit = exit->previous();
3778 }
3779 return exit;
3780}
3781
3782// Given the allocation and deoptimization exit try to find MaterializeObject
3783// instruction corresponding to this allocation at this exit.
3785 Definition* alloc,
3786 Instruction* exit) {
3787 if (exit->IsMaterializeObject()) {
3788 exit = ExitForMaterialization(exit->AsMaterializeObject());
3789 }
3790
3791 for (MaterializeObjectInstr* mat = exit->previous()->AsMaterializeObject();
3792 mat != nullptr; mat = mat->previous()->AsMaterializeObject()) {
3793 if (mat->allocation() == alloc) {
3794 return mat;
3795 }
3796 }
3797
3798 return nullptr;
3799}
3800
3802 const Slot& slot) {
3803 if (slot.representation() != kUntagged) {
3805 }
3806 if (!slot.may_contain_inner_pointer()) {
3808 }
3809 if (slot.IsIdentical(Slot::PointerBase_data())) {
3810 // The data field of external arrays is not unsafe, as it never contains
3811 // a GC-movable address.
3812 if (alloc->IsAllocateObject() &&
3813 IsExternalPayloadClassId(alloc->AsAllocateObject()->cls().id())) {
3815 }
3816 }
3818}
3819
3820// Insert MaterializeObject instruction for the given allocation before
3821// the given instruction that can deoptimize.
3822void AllocationSinking::CreateMaterializationAt(
3823 Instruction* exit,
3824 Definition* alloc,
3825 const ZoneGrowableArray<const Slot*>& slots) {
3826 InputsArray values(slots.length());
3827
3828 // All loads should be inserted before the first materialization so that
3829 // IR follows the following pattern: loads, materializations, deoptimizing
3830 // instruction.
3831 Instruction* load_point = FirstMaterializationAt(exit);
3832
3833 // Insert load instruction for every field and element.
3834 for (auto slot : slots) {
3835 Definition* load = nullptr;
3836 if (slot->IsArrayElement()) {
3837 intptr_t array_cid, index;
3838 if (alloc->IsCreateArray()) {
3839 array_cid = kArrayCid;
3840 index =
3841 compiler::target::Array::index_at_offset(slot->offset_in_bytes());
3842 } else if (auto alloc_typed_data = alloc->AsAllocateTypedData()) {
3843 array_cid = alloc_typed_data->class_id();
3844 index = slot->offset_in_bytes() /
3845 compiler::target::Instance::ElementSizeFor(array_cid);
3846 } else {
3847 UNREACHABLE();
3848 }
3849 load = new (Z) LoadIndexedInstr(
3850 new (Z) Value(alloc),
3851 new (Z) Value(
3852 flow_graph_->GetConstant(Smi::ZoneHandle(Z, Smi::New(index)))),
3853 /*index_unboxed=*/false,
3854 /*index_scale=*/compiler::target::Instance::ElementSizeFor(array_cid),
3855 array_cid, kAlignedAccess, DeoptId::kNone, alloc->source());
3856 } else {
3859 load = new (Z)
3860 LoadFieldInstr(new (Z) Value(alloc), *slot, access, alloc->source());
3861 }
3862 flow_graph_->InsertBefore(load_point, load, nullptr, FlowGraph::kValue);
3863 values.Add(new (Z) Value(load));
3864 }
3865
3866 const Class* cls = nullptr;
3867 intptr_t length_or_shape = -1;
3868 if (auto instr = alloc->AsAllocateObject()) {
3869 cls = &(instr->cls());
3870 } else if (alloc->IsAllocateClosure()) {
3871 cls = &Class::ZoneHandle(
3872 flow_graph_->isolate_group()->object_store()->closure_class());
3873 } else if (auto instr = alloc->AsAllocateContext()) {
3875 length_or_shape = instr->num_context_variables();
3876 } else if (auto instr = alloc->AsAllocateUninitializedContext()) {
3878 length_or_shape = instr->num_context_variables();
3879 } else if (auto instr = alloc->AsCreateArray()) {
3880 cls = &Class::ZoneHandle(
3881 flow_graph_->isolate_group()->object_store()->array_class());
3882 length_or_shape = instr->GetConstantNumElements();
3883 } else if (auto instr = alloc->AsAllocateTypedData()) {
3884 cls = &Class::ZoneHandle(
3885 flow_graph_->isolate_group()->class_table()->At(instr->class_id()));
3886 length_or_shape = instr->GetConstantNumElements();
3887 } else if (auto instr = alloc->AsAllocateRecord()) {
3888 cls = &Class::ZoneHandle(
3889 flow_graph_->isolate_group()->class_table()->At(kRecordCid));
3890 length_or_shape = instr->shape().AsInt();
3891 } else if (auto instr = alloc->AsAllocateSmallRecord()) {
3892 cls = &Class::ZoneHandle(
3893 flow_graph_->isolate_group()->class_table()->At(kRecordCid));
3894 length_or_shape = instr->shape().AsInt();
3895 } else {
3896 UNREACHABLE();
3897 }
3898 MaterializeObjectInstr* mat = new (Z) MaterializeObjectInstr(
3899 alloc->AsAllocation(), *cls, length_or_shape, slots, std::move(values));
3900
3901 flow_graph_->InsertBefore(exit, mat, nullptr, FlowGraph::kValue);
3902
3903 // Replace all mentions of this allocation with a newly inserted
3904 // MaterializeObject instruction.
3905 // We must preserve the identity: all mentions are replaced by the same
3906 // materialization.
3907 exit->ReplaceInEnvironment(alloc, mat);
3908
3909 // Mark MaterializeObject as an environment use of this allocation.
3910 // This will allow us to discover it when we are looking for deoptimization
3911 // exits for another allocation that potentially flows into this one.
3912 Value* val = new (Z) Value(alloc);
3913 val->set_instruction(mat);
3914 alloc->AddEnvUse(val);
3915
3916 // Record inserted materialization.
3917 materializations_.Add(mat);
3918}
3919
3920// Add given instruction to the list of the instructions if it is not yet
3921// present there.
3922template <typename T>
3924 ASSERT(!value->IsGraphEntry() && !value->IsFunctionEntry());
3925 for (intptr_t i = 0; i < list->length(); i++) {
3926 if ((*list)[i] == value) {
3927 return;
3928 }
3929 }
3930 list->Add(value);
3931}
3932
3933// Transitively collect all deoptimization exits that might need this allocation
3934// rematerialized. It is not enough to collect only environment uses of this
3935// allocation because it can flow into other objects that will be
3936// dematerialized and that are referenced by deopt environments that
3937// don't contain this allocation explicitly.
3938void AllocationSinking::ExitsCollector::Collect(Definition* alloc) {
3939 for (Value* use = alloc->env_use_list(); use != nullptr;
3940 use = use->next_use()) {
3941 if (use->instruction()->IsMaterializeObject()) {
3943 use->instruction()->AsMaterializeObject()));
3944 } else {
3945 AddInstruction(&exits_, use->instruction());
3946 }
3947 }
3948
3949 // Check if this allocation is stored into any other allocation sinking
3950 // candidate and put it on worklist so that we conservatively collect all
3951 // exits for that candidate as well because they potentially might see
3952 // this object.
3953 for (Value* use = alloc->input_use_list(); use != nullptr;
3954 use = use->next_use()) {
3955 Definition* obj = StoreDestination(use);
3956 if ((obj != nullptr) && (obj != alloc)) {
3957 AddInstruction(&worklist_, obj);
3958 }
3959 }
3960}
3961
3962void AllocationSinking::ExitsCollector::CollectTransitively(Definition* alloc) {
3963 exits_.TruncateTo(0);
3964 worklist_.TruncateTo(0);
3965
3966 worklist_.Add(alloc);
3967
3968 // Note: worklist potentially will grow while we are iterating over it.
3969 // We are not removing allocations from the worklist not to waste space on
3970 // the side maintaining BitVector of already processed allocations: worklist
3971 // is expected to be very small thus linear search in it is just as efficient
3972 // as a bitvector.
3973 for (intptr_t i = 0; i < worklist_.length(); i++) {
3974 Collect(worklist_[i]);
3975 }
3976}
3977
3978static bool IsDataFieldOfTypedDataView(Definition* alloc, const Slot& slot) {
3979 if (!slot.IsIdentical(Slot::PointerBase_data())) return false;
3980 // Internal typed data objects use AllocateTypedData.
3981 if (!alloc->IsAllocateObject()) return false;
3982 auto const cid = alloc->AsAllocateObject()->cls().id();
3984}
3985
3986void AllocationSinking::InsertMaterializations(Definition* alloc) {
3987 // Collect all fields and array elements that are written for this instance.
3988 auto slots = new (Z) ZoneGrowableArray<const Slot*>(5);
3989
3990 for (Value* use = alloc->input_use_list(); use != nullptr;
3991 use = use->next_use()) {
3992 if (StoreDestination(use) == alloc) {
3993 // Allocation instructions cannot be used in as inputs to themselves.
3994 ASSERT(!use->instruction()->AsAllocation());
3995 if (auto store = use->instruction()->AsStoreField()) {
3996 // Only add the data field slot for external arrays. For views, it is
3997 // recomputed using the other fields in DeferredObject::Fill().
3998 if (!IsDataFieldOfTypedDataView(alloc, store->slot())) {
3999 AddSlot(slots, store->slot());
4000 }
4001 } else if (auto store = use->instruction()->AsStoreIndexed()) {
4002 const intptr_t index = store->index()->BoundSmiConstant();
4003 intptr_t offset = -1;
4004 if (alloc->IsCreateArray()) {
4005 offset = compiler::target::Array::element_offset(index);
4006 } else if (alloc->IsAllocateTypedData()) {
4007 offset = index * store->index_scale();
4008 } else {
4009 UNREACHABLE();
4010 }
4011 AddSlot(slots,
4012 Slot::GetArrayElementSlot(flow_graph_->thread(), offset));
4013 }
4014 }
4015 }
4016
4017 if (auto* const allocation = alloc->AsAllocation()) {
4018 for (intptr_t pos = 0; pos < allocation->InputCount(); pos++) {
4019 if (auto* const slot = allocation->SlotForInput(pos)) {
4020 // Don't add slots for immutable length slots if not already added
4021 // above, as they are already represented as the number of elements in
4022 // the MaterializeObjectInstr.
4023 if (!slot->IsImmutableLengthSlot()) {
4024 AddSlot(slots, *slot);
4025 }
4026 }
4027 }
4028 }
4029
4030 // Collect all instructions that mention this object in the environment.
4031 exits_collector_.CollectTransitively(alloc);
4032
4033 // Insert materializations at environment uses.
4034 for (intptr_t i = 0; i < exits_collector_.exits().length(); i++) {
4035 CreateMaterializationAt(exits_collector_.exits()[i], alloc, *slots);
4036 }
4037}
4038
4039// TryCatchAnalyzer tries to reduce the state that needs to be synchronized
4040// on entry to the catch by discovering Parameter-s which are never used
4041// or which are always constant.
4042//
4043// This analysis is similar to dead/redundant phi elimination because
4044// Parameter instructions serve as "implicit" phis.
4045//
4046// Caveat: when analyzing which Parameter-s are redundant we limit ourselves to
4047// constant values because CatchBlockEntry-s are hanging out directly from
4048// GraphEntry and thus they are only dominated by constants from GraphEntry -
4049// thus we can't replace Parameter with arbitrary Definition which is not a
4050// Constant even if we know that this Parameter is redundant and would always
4051// evaluate to that Definition.
4053 public:
4054 explicit TryCatchAnalyzer(FlowGraph* flow_graph, bool is_aot)
4055 : flow_graph_(flow_graph),
4056 is_aot_(is_aot),
4057 // Initial capacity is selected based on trivial examples.
4058 worklist_(flow_graph, /*initial_capacity=*/10) {}
4059
4060 // Run analysis and eliminate dead/redundant Parameter-s.
4061 void Optimize();
4062
4063 private:
4064 // In precompiled mode we can eliminate all parameters that have no real uses
4065 // and subsequently clear out corresponding slots in the environments assigned
4066 // to instructions that can throw an exception which would be caught by
4067 // the corresponding CatchEntryBlock.
4068 //
4069 // Computing "dead" parameters is essentially a fixed point algorithm because
4070 // Parameter value can flow into another Parameter via an environment attached
4071 // to an instruction that can throw.
4072 //
4073 // Note: this optimization pass assumes that environment values are only
4074 // used during catching, that is why it should only be used in AOT mode.
4075 void OptimizeDeadParameters() {
4076 ASSERT(is_aot_);
4077
4078 NumberCatchEntryParameters();
4079 ComputeIncomingValues();
4080 CollectAliveParametersOrPhis();
4081 PropagateLivenessToInputs();
4082 EliminateDeadParameters();
4083 }
4084
4085 static intptr_t GetParameterId(const Instruction* instr) {
4086 return instr->GetPassSpecificId(CompilerPass::kTryCatchOptimization);
4087 }
4088
4089 static void SetParameterId(Instruction* instr, intptr_t id) {
4090 instr->SetPassSpecificId(CompilerPass::kTryCatchOptimization, id);
4091 }
4092
4093 static bool HasParameterId(Instruction* instr) {
4094 return instr->HasPassSpecificId(CompilerPass::kTryCatchOptimization);
4095 }
4096
4097 // Assign sequential ids to each ParameterInstr in each CatchEntryBlock.
4098 // Collect reverse mapping from try indexes to corresponding catches.
4099 void NumberCatchEntryParameters() {
4100 for (auto catch_entry : flow_graph_->graph_entry()->catch_entries()) {
4101 const GrowableArray<Definition*>& idefs =
4102 *catch_entry->initial_definitions();
4103 for (auto idef : idefs) {
4104 if (idef->IsParameter()) {
4105 SetParameterId(idef, parameter_info_.length());
4106 parameter_info_.Add(new ParameterInfo(idef->AsParameter()));
4107 }
4108 }
4109
4110 catch_by_index_.EnsureLength(catch_entry->catch_try_index() + 1, nullptr);
4111 catch_by_index_[catch_entry->catch_try_index()] = catch_entry;
4112 }
4113 }
4114
4115 // Compute potential incoming values for each Parameter in each catch block
4116 // by looking into environments assigned to MayThrow instructions within
4117 // blocks covered by the corresponding catch.
4118 void ComputeIncomingValues() {
4119 for (auto block : flow_graph_->reverse_postorder()) {
4120 if (block->try_index() == kInvalidTryIndex) {
4121 continue;
4122 }
4123
4124 ASSERT(block->try_index() < catch_by_index_.length());
4125 auto catch_entry = catch_by_index_[block->try_index()];
4126 const auto& idefs = *catch_entry->initial_definitions();
4127
4128 for (ForwardInstructionIterator instr_it(block); !instr_it.Done();
4129 instr_it.Advance()) {
4130 Instruction* current = instr_it.Current();
4131 if (!current->MayThrow()) continue;
4132
4133 Environment* env = current->env()->Outermost();
4134 ASSERT(env != nullptr);
4135
4136 for (intptr_t env_idx = 0; env_idx < idefs.length(); ++env_idx) {
4137 if (ParameterInstr* param = idefs[env_idx]->AsParameter()) {
4138 Definition* defn = env->ValueAt(env_idx)->definition();
4139
4140 // Add defn as an incoming value to the parameter if it is not
4141 // already present in the list.
4142 bool found = false;
4143 for (auto other_defn :
4144 parameter_info_[GetParameterId(param)]->incoming) {
4145 if (other_defn == defn) {
4146 found = true;
4147 break;
4148 }
4149 }
4150 if (!found) {
4151 parameter_info_[GetParameterId(param)]->incoming.Add(defn);
4152 }
4153 }
4154 }
4155 }
4156 }
4157 }
4158
4159 // Find all parameters (and phis) that are definitely alive - because they
4160 // have non-phi uses and place them into worklist.
4161 //
4162 // Note: phis that only have phi (and environment) uses would be marked as
4163 // dead.
4164 void CollectAliveParametersOrPhis() {
4165 for (auto block : flow_graph_->reverse_postorder()) {
4166 if (JoinEntryInstr* join = block->AsJoinEntry()) {
4167 if (join->phis() == nullptr) continue;
4168
4169 for (auto phi : *join->phis()) {
4170 phi->mark_dead();
4171 if (HasActualUse(phi)) {
4172 MarkLive(phi);
4173 }
4174 }
4175 }
4176 }
4177
4178 for (auto info : parameter_info_) {
4179 if (HasActualUse(info->instr)) {
4180 MarkLive(info->instr);
4181 }
4182 }
4183 }
4184
4185 // Propagate liveness from live parameters and phis to other parameters and
4186 // phis transitively.
4187 void PropagateLivenessToInputs() {
4188 while (!worklist_.IsEmpty()) {
4189 Definition* defn = worklist_.RemoveLast();
4190 if (ParameterInstr* param = defn->AsParameter()) {
4191 auto s = parameter_info_[GetParameterId(param)];
4192 for (auto input : s->incoming) {
4193 MarkLive(input);
4194 }
4195 } else if (PhiInstr* phi = defn->AsPhi()) {
4196 for (intptr_t i = 0; i < phi->InputCount(); i++) {
4197 MarkLive(phi->InputAt(i)->definition());
4198 }
4199 }
4200 }
4201 }
4202
4203 // Mark definition as live if it is a dead Phi or a dead Parameter and place
4204 // them into worklist.
4205 void MarkLive(Definition* defn) {
4206 if (PhiInstr* phi = defn->AsPhi()) {
4207 if (!phi->is_alive()) {
4208 phi->mark_alive();
4209 worklist_.Add(phi);
4210 }
4211 } else if (ParameterInstr* param = defn->AsParameter()) {
4212 if (HasParameterId(param)) {
4213 auto input_s = parameter_info_[GetParameterId(param)];
4214 if (!input_s->alive) {
4215 input_s->alive = true;
4216 worklist_.Add(param);
4217 }
4218 }
4219 } else if (UnboxInstr* unbox = defn->AsUnbox()) {
4220 MarkLive(unbox->value()->definition());
4221 }
4222 }
4223
4224 // Replace all dead parameters with null value and clear corresponding
4225 // slots in environments.
4226 void EliminateDeadParameters() {
4227 for (auto info : parameter_info_) {
4228 if (!info->alive) {
4229 info->instr->ReplaceUsesWith(flow_graph_->constant_null());
4230 }
4231 }
4232
4233 for (auto block : flow_graph_->reverse_postorder()) {
4234 if (block->try_index() == -1) continue;
4235
4236 auto catch_entry = catch_by_index_[block->try_index()];
4237 const auto& idefs = *catch_entry->initial_definitions();
4238
4239 for (ForwardInstructionIterator instr_it(block); !instr_it.Done();
4240 instr_it.Advance()) {
4241 Instruction* current = instr_it.Current();
4242 if (!current->MayThrow()) continue;
4243
4244 Environment* env = current->env()->Outermost();
4245 RELEASE_ASSERT(env != nullptr);
4246
4247 for (intptr_t env_idx = 0; env_idx < idefs.length(); ++env_idx) {
4248 if (ParameterInstr* param = idefs[env_idx]->AsParameter()) {
4249 if (!parameter_info_[GetParameterId(param)]->alive) {
4250 env->ValueAt(env_idx)->BindToEnvironment(
4251 flow_graph_->constant_null());
4252 }
4253 }
4254 }
4255 }
4256 }
4257
4259 }
4260
4261 // Returns true if definition has a use in an instruction which is not a phi.
4262 // Skip over Unbox instructions which may be inserted for unused phis.
4263 static bool HasActualUse(Definition* defn) {
4264 for (Value* use = defn->input_use_list(); use != nullptr;
4265 use = use->next_use()) {
4266 Instruction* use_instruction = use->instruction();
4267 if (UnboxInstr* unbox = use_instruction->AsUnbox()) {
4268 if (HasActualUse(unbox)) {
4269 return true;
4270 }
4271 } else if (!use_instruction->IsPhi()) {
4272 return true;
4273 }
4274 }
4275 return false;
4276 }
4277
4278 struct ParameterInfo : public ZoneAllocated {
4279 explicit ParameterInfo(ParameterInstr* instr) : instr(instr) {}
4280
4281 ParameterInstr* instr;
4282 bool alive = false;
4283 GrowableArray<Definition*> incoming;
4284 };
4285
4286 FlowGraph* const flow_graph_;
4287 const bool is_aot_;
4288
4289 // Additional information for each Parameter from each CatchBlockEntry.
4290 // Parameter-s are numbered and their number is stored in
4291 // Instruction::place_id() field which is otherwise not used for anything
4292 // at this stage.
4293 GrowableArray<ParameterInfo*> parameter_info_;
4294
4295 // Mapping from catch_try_index to corresponding CatchBlockEntry-s.
4296 GrowableArray<CatchBlockEntryInstr*> catch_by_index_;
4297
4298 // Worklist for live Phi and Parameter instructions which need to be
4299 // processed by PropagateLivenessToInputs.
4300 DefinitionWorklist worklist_;
4301};
4302
4303void OptimizeCatchEntryStates(FlowGraph* flow_graph, bool is_aot) {
4304 if (flow_graph->graph_entry()->catch_entries().is_empty()) {
4305 return;
4306 }
4307
4308 TryCatchAnalyzer analyzer(flow_graph, is_aot);
4309 analyzer.Optimize();
4310}
4311
4313 // Analyze catch entries and remove "dead" Parameter instructions.
4314 if (is_aot_) {
4315 OptimizeDeadParameters();
4316 }
4317
4318 // For every catch-block: Iterate over all call instructions inside the
4319 // corresponding try-block and figure out for each environment value if it
4320 // is the same constant at all calls. If yes, replace the initial definition
4321 // at the catch-entry with this constant.
4322 const GrowableArray<CatchBlockEntryInstr*>& catch_entries =
4323 flow_graph_->graph_entry()->catch_entries();
4324
4325 for (auto catch_entry : catch_entries) {
4326 // Initialize cdefs with the original initial definitions (ParameterInstr).
4327 // The following representation is used:
4328 // ParameterInstr => unknown
4329 // ConstantInstr => known constant
4330 // nullptr => non-constant
4331 GrowableArray<Definition*>* idefs = catch_entry->initial_definitions();
4332 GrowableArray<Definition*> cdefs(idefs->length());
4333 cdefs.AddArray(*idefs);
4334
4335 // exception_var and stacktrace_var are never constant. In asynchronous or
4336 // generator functions they may be context-allocated in which case they are
4337 // not tracked in the environment anyway.
4338
4339 cdefs[flow_graph_->EnvIndex(catch_entry->raw_exception_var())] = nullptr;
4340 cdefs[flow_graph_->EnvIndex(catch_entry->raw_stacktrace_var())] = nullptr;
4341
4342 for (BlockIterator block_it = flow_graph_->reverse_postorder_iterator();
4343 !block_it.Done(); block_it.Advance()) {
4344 BlockEntryInstr* block = block_it.Current();
4345 if (block->try_index() == catch_entry->catch_try_index()) {
4346 for (ForwardInstructionIterator instr_it(block); !instr_it.Done();
4347 instr_it.Advance()) {
4348 Instruction* current = instr_it.Current();
4349 if (current->MayThrow()) {
4350 Environment* env = current->env()->Outermost();
4351 ASSERT(env != nullptr);
4352 for (intptr_t env_idx = 0; env_idx < cdefs.length(); ++env_idx) {
4353 if (cdefs[env_idx] != nullptr && !cdefs[env_idx]->IsConstant() &&
4354 env->ValueAt(env_idx)->BindsToConstant()) {
4355 // If the recorded definition is not a constant, record this
4356 // definition as the current constant definition.
4357 cdefs[env_idx] = env->ValueAt(env_idx)->definition();
4358 }
4359 if (cdefs[env_idx] != env->ValueAt(env_idx)->definition()) {
4360 // Non-constant definitions are reset to nullptr.
4361 cdefs[env_idx] = nullptr;
4362 }
4363 }
4364 }
4365 }
4366 }
4367 }
4368 for (intptr_t j = 0; j < idefs->length(); ++j) {
4369 if (cdefs[j] != nullptr && cdefs[j]->IsConstant()) {
4370 Definition* old = (*idefs)[j];
4371 ConstantInstr* orig = cdefs[j]->AsConstant();
4373 new (flow_graph_->zone()) ConstantInstr(orig->value());
4374 flow_graph_->AllocateSSAIndex(copy);
4375 old->ReplaceUsesWith(copy);
4376 copy->set_previous(old->previous()); // partial link
4377 (*idefs)[j] = copy;
4378 }
4379 }
4380 }
4381}
4382
4383// Returns true iff this definition is used in a non-phi instruction.
4384static bool HasRealUse(Definition* def) {
4385 // Environment uses are real (non-phi) uses.
4386 if (def->env_use_list() != nullptr) return true;
4387
4388 for (Value::Iterator it(def->input_use_list()); !it.Done(); it.Advance()) {
4389 if (!it.Current()->instruction()->IsPhi()) return true;
4390 }
4391 return false;
4392}
4393
4395 GrowableArray<PhiInstr*> live_phis;
4396 for (BlockIterator b = flow_graph->postorder_iterator(); !b.Done();
4397 b.Advance()) {
4398 JoinEntryInstr* join = b.Current()->AsJoinEntry();
4399 if (join != nullptr) {
4400 for (PhiIterator it(join); !it.Done(); it.Advance()) {
4401 PhiInstr* phi = it.Current();
4402 // Phis that have uses and phis inside try blocks are
4403 // marked as live.
4404 if (HasRealUse(phi)) {
4405 live_phis.Add(phi);
4406 phi->mark_alive();
4407 } else {
4408 phi->mark_dead();
4409 }
4410 }
4411 }
4412 }
4413
4414 while (!live_phis.is_empty()) {
4415 PhiInstr* phi = live_phis.RemoveLast();
4416 for (intptr_t i = 0; i < phi->InputCount(); i++) {
4417 Value* val = phi->InputAt(i);
4418 PhiInstr* used_phi = val->definition()->AsPhi();
4419 if ((used_phi != nullptr) && !used_phi->is_alive()) {
4420 used_phi->mark_alive();
4421 live_phis.Add(used_phi);
4422 }
4423 }
4424 }
4425
4427}
4428
4430 FlowGraph* flow_graph) {
4431 for (auto block : flow_graph->postorder()) {
4432 if (JoinEntryInstr* join = block->AsJoinEntry()) {
4433 if (join->phis_ == nullptr) continue;
4434
4435 // Eliminate dead phis and compact the phis_ array of the block.
4436 intptr_t to_index = 0;
4437 for (intptr_t i = 0; i < join->phis_->length(); ++i) {
4438 PhiInstr* phi = (*join->phis_)[i];
4439 if (phi != nullptr) {
4440 if (!phi->is_alive()) {
4441 phi->ReplaceUsesWith(flow_graph->constant_null());
4442 phi->UnuseAllInputs();
4443 (*join->phis_)[i] = nullptr;
4444 if (FLAG_trace_optimization && flow_graph->should_print()) {
4445 THR_Print("Removing dead phi v%" Pd "\n", phi->ssa_temp_index());
4446 }
4447 } else {
4448 (*join->phis_)[to_index++] = phi;
4449 }
4450 }
4451 }
4452 if (to_index == 0) {
4453 join->phis_ = nullptr;
4454 } else {
4455 join->phis_->TruncateTo(to_index);
4456 }
4457 }
4458 }
4459}
4460
4463 BitVector live(flow_graph->zone(), flow_graph->current_ssa_temp_index());
4464
4465 // Mark all instructions with side-effects as live.
4466 for (BlockIterator block_it = flow_graph->reverse_postorder_iterator();
4467 !block_it.Done(); block_it.Advance()) {
4468 BlockEntryInstr* block = block_it.Current();
4469 for (ForwardInstructionIterator it(block); !it.Done(); it.Advance()) {
4470 Instruction* current = it.Current();
4471 ASSERT(!current->IsMoveArgument());
4472 // TODO(alexmarkov): take control dependencies into account and
4473 // eliminate dead branches/conditions.
4474 if (!current->CanEliminate(block)) {
4475 worklist.Add(current);
4476 if (Definition* def = current->AsDefinition()) {
4477 if (def->HasSSATemp()) {
4478 live.Add(def->ssa_temp_index());
4479 }
4480 }
4481 }
4482 }
4483 }
4484
4485 // Iteratively follow inputs of instructions in the work list.
4486 while (!worklist.is_empty()) {
4487 Instruction* current = worklist.RemoveLast();
4488 for (intptr_t i = 0, n = current->InputCount(); i < n; ++i) {
4489 Definition* input = current->InputAt(i)->definition();
4490 ASSERT(input->HasSSATemp());
4491 if (!live.Contains(input->ssa_temp_index())) {
4492 worklist.Add(input);
4493 live.Add(input->ssa_temp_index());
4494 }
4495 }
4496 for (intptr_t i = 0, n = current->ArgumentCount(); i < n; ++i) {
4497 Definition* input = current->ArgumentAt(i);
4498 ASSERT(input->HasSSATemp());
4499 if (!live.Contains(input->ssa_temp_index())) {
4500 worklist.Add(input);
4501 live.Add(input->ssa_temp_index());
4502 }
4503 }
4504 if (current->env() != nullptr) {
4505 for (Environment::DeepIterator it(current->env()); !it.Done();
4506 it.Advance()) {
4507 Definition* input = it.CurrentValue()->definition();
4508 ASSERT(!input->IsMoveArgument());
4509 if (input->HasSSATemp() && !live.Contains(input->ssa_temp_index())) {
4510 worklist.Add(input);
4511 live.Add(input->ssa_temp_index());
4512 }
4513 }
4514 }
4515 }
4516
4517 // Remove all instructions which are not marked as live.
4518 for (BlockIterator block_it = flow_graph->reverse_postorder_iterator();
4519 !block_it.Done(); block_it.Advance()) {
4520 BlockEntryInstr* block = block_it.Current();
4521 if (JoinEntryInstr* join = block->AsJoinEntry()) {
4522 for (PhiIterator it(join); !it.Done(); it.Advance()) {
4523 PhiInstr* current = it.Current();
4524 if (!live.Contains(current->ssa_temp_index())) {
4526 }
4527 }
4528 }
4529 for (ForwardInstructionIterator it(block); !it.Done(); it.Advance()) {
4530 Instruction* current = it.Current();
4531 if (!current->CanEliminate(block)) {
4532 continue;
4533 }
4534 ASSERT(!current->IsMoveArgument());
4535 ASSERT((current->ArgumentCount() == 0) || !current->HasMoveArguments());
4536 if (Definition* def = current->AsDefinition()) {
4537 if (def->HasSSATemp() && live.Contains(def->ssa_temp_index())) {
4538 continue;
4539 }
4540 }
4542 }
4543 }
4544}
4545
4546// Returns true if function is marked with vm:unsafe:no-interrupts pragma.
4550 /*only_core=*/false, function,
4551 Symbols::vm_unsafe_no_interrupts(),
4552 /*multiple=*/false, &options);
4553}
4554
4556 const bool should_remove_all = IsMarkedWithNoInterrupts(graph->function());
4557 if (should_remove_all) {
4558 for (auto entry : graph->reverse_postorder()) {
4559 for (ForwardInstructionIterator it(entry); !it.Done(); it.Advance()) {
4560 if (it.Current()->IsCheckStackOverflow()) {
4562 }
4563 }
4564 }
4565 return;
4566 }
4567
4568 CheckStackOverflowInstr* first_stack_overflow_instr = nullptr;
4569 for (auto entry : graph->reverse_postorder()) {
4570 for (ForwardInstructionIterator it(entry); !it.Done(); it.Advance()) {
4571 Instruction* current = it.Current();
4572
4573 if (CheckStackOverflowInstr* instr = current->AsCheckStackOverflow()) {
4574 if (first_stack_overflow_instr == nullptr) {
4575 first_stack_overflow_instr = instr;
4576 ASSERT(!first_stack_overflow_instr->in_loop());
4577 }
4578 continue;
4579 }
4580
4581 if (current->IsBranch()) {
4582 current = current->AsBranch()->comparison();
4583 }
4584
4585 if (current->HasUnknownSideEffects()) {
4586 return;
4587 }
4588 }
4589 }
4590
4591 if (first_stack_overflow_instr != nullptr) {
4592 first_stack_overflow_instr->RemoveFromGraph();
4593 }
4594}
4595
4596} // 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)
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)
static size_t element_size(Layout layout, SkSLType type)
#define UNREACHABLE()
Definition assert.h:248
#define RELEASE_ASSERT(cond)
Definition assert.h:327
#define Z
static AliasIdentity NotAliased()
Definition il.h:2405
static AliasIdentity Aliased()
Definition il.h:2402
bool IsNotAliased() const
Definition il.h:2434
bool IsUnknown() const
Definition il.h:2432
static AliasIdentity Unknown()
Definition il.h:2399
static AliasIdentity AllocationSinkingCandidate()
Definition il.h:2409
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:7308
MaterializeObjectInstr * MaterializationFor(Definition *alloc, Instruction *exit)
intptr_t GetConstantNumElements() const
Definition il.h:7757
bool HasConstantNumElements() const
Definition il.h:7754
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
static constexpr Kind decode(uword value)
Definition bitfield.h:173
static constexpr uword update(Representation value, uword original)
Definition bitfield.h:190
static constexpr uword encode(Kind value)
Definition bitfield.h:167
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:1724
intptr_t postorder_number() const
Definition il.h:1652
intptr_t preorder_number() const
Definition il.h:1649
intptr_t block_id() const
Definition il.h:1655
BlockEntryInstr * ImmediateDominator() const
Definition il.cc:1817
const GrowableArray< BlockEntryInstr * > & dominated_blocks()
Definition il.h:1667
bool Dominates(BlockEntryInstr *other) const
Definition il.cc:1806
virtual BlockEntryInstr * PredecessorAt(intptr_t index) const =0
Instruction * last_instruction() const
Definition il.h:1680
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)
ClassPtr At(intptr_t cid) const
static CompileType FromCid(intptr_t cid)
static CompilerState & Current()
const Object & value() const
Definition il.h:4212
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:831
Definition * RemoveLast()
Definition flow_graph.h:845
Value * env_use_list() const
Definition il.h:2560
virtual AliasIdentity Identity() const
Definition il.h:2642
Value * input_use_list() const
Definition il.h:2557
CompileType * Type()
Definition il.h:2503
void ReplaceUsesWith(Definition *other)
Definition il.cc:1484
bool HasSSATemp() const
Definition il.h:2490
virtual Definition * AsDefinition()
Definition il.h:2665
Definition * OriginalDefinition()
Definition il.cc:530
intptr_t ssa_temp_index() const
Definition il.h:2485
friend class Value
Definition il.h:2672
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:11624
void MarkAsHoisted()
Definition il.h:11634
Environment * Outermost()
Definition il.h:11647
FieldPtr Original() const
Definition object.cc:11790
bool is_static() const
Definition object.h:4418
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)
IsolateGroup * isolate_group() const
Definition flow_graph.h:262
bool should_print() const
Definition flow_graph.h:505
void EnsureSSATempIndex(Definition *defn, Definition *replacement)
Definition flow_graph.cc:95
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)
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)
intptr_t EnvIndex(const LocalVariable *variable) const
Definition flow_graph.h:189
Instruction * Current() const
Definition il.h:1847
const GrowableArray< CatchBlockEntryInstr * > & catch_entries() const
Definition il.h:1997
void InsertBefore(Instruction *next)
Definition il.h:1274
Instruction * next() const
Definition il.h:1087
virtual intptr_t InputCount() const =0
intptr_t GetDeoptId() const
Definition il.h:1403
virtual bool MayThrow() const =0
virtual bool HasUnknownSideEffects() const =0
virtual Value * InputAt(intptr_t i) const =0
virtual BlockEntryInstr * GetBlock()
Definition il.cc:1350
virtual void CopyDeoptIdFrom(const Instruction &instr)
Definition il.h:1405
Environment * env() const
Definition il.h:1209
virtual bool CanEliminate(const BlockEntryInstr *block) const
Definition il.cc:1566
void RemoveEnvironment()
Definition il.cc:1280
virtual Representation RequiredInputRepresentation(intptr_t idx) const
Definition il.h:1235
bool HasPassSpecificId(CompilerPass::Id pass) const
Definition il.h:1226
void UnuseAllInputs()
Definition il.cc:1525
virtual bool MayHaveVisibleEffect() const
Definition il.h:1346
virtual intptr_t ArgumentCount() const
Definition il.h:1035
Definition * ArgumentAt(intptr_t index) const
Definition il.h:3423
virtual Representation representation() const
Definition il.h:1254
bool CanDeoptimize() const
Definition il.h:1073
intptr_t GetPassSpecificId(CompilerPass::Id pass) const
Definition il.h:1218
virtual bool AllowsCSE() const
Definition il.h:1285
virtual Tag tag() const =0
Instruction * RemoveFromGraph(bool return_previous=true)
Definition il.cc:1299
bool HasMoveArguments() const
Definition il.h:1053
void SetPassSpecificId(CompilerPass::Id pass, intptr_t id)
Definition il.h:1223
const T * Cast() const
Definition il.h:1180
virtual const char * DebugName() const =0
Instruction * previous() const
Definition il.h:1081
ObjectStore * object_store() const
Definition isolate.h:505
ClassTable * class_table() const
Definition isolate.h:491
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:4201
GrowableArray< BitVector * > kill_
Definition flow_graph.h:817
GrowableArray< BitVector * > live_out_
Definition flow_graph.h:813
Zone * zone() const
Definition flow_graph.h:801
GrowableArray< BitVector * > live_in_
Definition flow_graph.h:821
const Slot & slot() const
Definition il.h:8096
Value * instance() const
Definition il.h:8095
virtual Representation representation() const
Definition il.cc:919
intptr_t class_id() const
Definition il.h:6759
Value * array() const
Definition il.h:6756
intptr_t index_scale() const
Definition il.h:6758
Value * index() const
Definition il.h:6757
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:2809
void mark_dead()
Definition il.h:2811
void mark_alive()
Definition il.h:2810
virtual BlockEntryInstr * GetBlock()
Definition il.h:2798
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 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:9985
Value * value() const
Definition il.h:6375
Value * array() const
Definition il.h:7037
intptr_t class_id() const
Definition il.h:7042
intptr_t index_scale() const
Definition il.h:7041
Value * index() const
Definition il.h:7038
StoreOptimizer(FlowGraph *graph, AliasedSet *aliased_set, PointerSet< Place > *map)
static void OptimizeGraph(FlowGraph *graph)
Value * value() const
Definition il.h:6686
Zone * zone() const
static Thread * Current()
Definition thread.h:361
void CheckForSafepoint()
Definition thread.h:1091
TryCatchAnalyzer(FlowGraph *flow_graph, bool is_aot)
static constexpr uintptr_t RoundUpToPowerOfTwo(uintptr_t x)
Definition utils.h:120
static constexpr int ShiftForPowerOfTwo(T x)
Definition utils.h:66
static T Minimum(T x, T y)
Definition utils.h:21
static constexpr bool IsAligned(T x, uintptr_t alignment, uintptr_t offset=0)
Definition utils.h:77
Instruction * instruction() const
Definition il.h:121
Definition * definition() const
Definition il.h:103
void BindTo(Definition *definition)
Definition il.h:2700
intptr_t InputCount() const
Definition il.h:2776
Value * InputAt(intptr_t i) const
Definition il.h:2777
ZonePlace(const Place &place)
char * PrintToString(const char *format,...) PRINTF_ATTRIBUTE(2
Definition zone.cc:313
#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
#define DEFINE_FLAG(type, name, default_value, comment)
Definition flags.h:16
Dart_NativeFunction function
Definition fuchsia.cc:51
size_t length
bool Contains(const Container &container, const Value &value)
Definition copy.py:1
exit(kErrorExitCode)
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:6246
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:895
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)
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 bool IsLoadEliminationCandidate(Instruction *instr)
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:6722
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 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
SINT Vec< 2 *N, T > join(const Vec< N, T > &lo, const Vec< N, T > &hi)
Definition SkVx.h:242
#define Pd
Definition globals.h:408
#define DISALLOW_COPY_AND_ASSIGN(TypeName)
Definition globals.h:581
#define T
static const char header[]
Definition skpbench.cpp:88
const Scalar scale
Point 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
GrSamplerState::WrapMode Wrap