Skip to content

Commit bca5511

Browse files
Merge pull request swiftlang#29643 from ravikandhadai/fix_dead_array_elim
[SIL Optimization] Make ArraySemantics.cpp aware of "array.uninitialized_intrinsic"
2 parents 1709b61 + a6bed21 commit bca5511

File tree

7 files changed

+198
-116
lines changed

7 files changed

+198
-116
lines changed

include/swift/SILOptimizer/Analysis/ArraySemantic.h

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ enum class ArrayCallKind {
4141
// a function, and it has a self parameter, make sure that it is defined
4242
// before this comment.
4343
kArrayInit,
44-
kArrayUninitialized
44+
kArrayUninitialized,
45+
kArrayUninitializedIntrinsic
4546
};
4647

4748
/// Wrapper around array semantic calls.
@@ -183,6 +184,21 @@ class ArraySemanticsCall {
183184
/// Can this function be inlined by the early inliner.
184185
bool canInlineEarly() const;
185186

187+
/// If this is a call to ArrayUninitialized (or
188+
/// ArrayUninitializedInstrinsic), identify the instructions that store
189+
/// elements into the array indices. For every index, add the store
190+
/// instruction that stores to that index to \p ElementStoreMap.
191+
///
192+
/// \returns true iff this is an "array.uninitialized" semantic call, and the
193+
/// stores into the array indices are identified and the \p ElementStoreMap is
194+
/// populated.
195+
///
196+
/// Note that this function does not support array initializations that use
197+
/// copy_addr, which implies that arrays with address-only types would not
198+
/// be recognized by this function as yet.
199+
bool mapInitializationStores(
200+
llvm::DenseMap<uint64_t, StoreInst *> &ElementStoreMap);
201+
186202
protected:
187203
/// Validate the signature of this call.
188204
bool isValidSignature();

lib/SILOptimizer/Analysis/ArraySemantic.cpp

Lines changed: 121 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ ArrayCallKind swift::ArraySemanticsCall::getKind() const {
170170
ArrayCallKind::kArrayPropsIsNativeTypeChecked)
171171
.StartsWith("array.init", ArrayCallKind::kArrayInit)
172172
.Case("array.uninitialized", ArrayCallKind::kArrayUninitialized)
173+
.Case("array.uninitialized_intrinsic", ArrayCallKind::kArrayUninitializedIntrinsic)
173174
.Case("array.check_subscript", ArrayCallKind::kCheckSubscript)
174175
.Case("array.check_index", ArrayCallKind::kCheckIndex)
175176
.Case("array.get_count", ArrayCallKind::kGetCount)
@@ -591,11 +592,15 @@ bool swift::ArraySemanticsCall::canInlineEarly() const {
591592
case ArrayCallKind::kAppendContentsOf:
592593
case ArrayCallKind::kReserveCapacityForAppend:
593594
case ArrayCallKind::kAppendElement:
595+
case ArrayCallKind::kArrayUninitializedIntrinsic:
594596
// append(Element) calls other semantics functions. Therefore it's
595597
// important that it's inlined by the early inliner (which is before all
596598
// the array optimizations). Also, this semantics is only used to lookup
597599
// Array.append(Element), so inlining it does not prevent any other
598600
// optimization.
601+
//
602+
// Early inlining array.uninitialized_intrinsic semantic call helps in
603+
// stack promotion.
599604
return true;
600605
}
601606
}
@@ -622,61 +627,57 @@ SILValue swift::ArraySemanticsCall::getInitializationCount() const {
622627
return SILValue();
623628
}
624629

625-
SILValue swift::ArraySemanticsCall::getArrayValue() const {
626-
if (getKind() == ArrayCallKind::kArrayUninitialized) {
627-
TupleExtractInst *ArrayDef = nullptr;
628-
for (auto *Op : SemanticsCall->getUses()) {
629-
auto *TupleElt = dyn_cast<TupleExtractInst>(Op->getUser());
630-
if (!TupleElt)
631-
return SILValue();
632-
switch (TupleElt->getFieldNo()) {
633-
default:
634-
return SILValue();
635-
case 0: {
636-
// Should only have one tuple extract after CSE.
637-
if (ArrayDef)
638-
return SILValue();
639-
ArrayDef = TupleElt;
640-
break;
641-
}
642-
case 1: /*Ignore the storage address */ break;
643-
}
630+
/// Given an array semantic call \c arrayCall, if it is an "array.uninitialized"
631+
/// initializer, which returns a two-element tuple, return the element of the
632+
/// tuple at \c tupleElementIndex. Return a null SILValue if the
633+
/// array call is not an "array.uninitialized" initializer or if the extraction
634+
/// of the result tuple fails.
635+
static SILValue getArrayUninitializedInitResult(ArraySemanticsCall arrayCall,
636+
unsigned tupleElementIndex) {
637+
assert(tupleElementIndex <= 1 && "tupleElementIndex must be 0 or 1");
638+
ArrayCallKind arrayCallKind = arrayCall.getKind();
639+
if (arrayCallKind != ArrayCallKind::kArrayUninitialized &&
640+
arrayCallKind != ArrayCallKind::kArrayUninitializedIntrinsic)
641+
return SILValue();
642+
643+
// In OSSA, the call result will be extracted through a destructure_tuple
644+
// instruction.
645+
ApplyInst *callInst = arrayCall;
646+
if (callInst->getFunction()->hasOwnership()) {
647+
Operand *singleUse = callInst->getSingleUse();
648+
if (!singleUse)
649+
return SILValue();
650+
if (DestructureTupleInst *destructTuple =
651+
dyn_cast<DestructureTupleInst>(singleUse->getUser())) {
652+
return destructTuple->getResult(tupleElementIndex);
644653
}
645-
return SILValue(ArrayDef);
654+
return SILValue();
655+
}
656+
657+
// In non-OSSA, look for a tuple_extract instruction of the call result with
658+
// the requested tupleElementIndex.
659+
TupleExtractInst *tupleExtractInst = nullptr;
660+
for (auto *op : callInst->getUses()) {
661+
auto *tupleElt = dyn_cast<TupleExtractInst>(op->getUser());
662+
if (!tupleElt)
663+
return SILValue();
664+
if (tupleElt->getFieldNo() != tupleElementIndex)
665+
continue;
666+
tupleExtractInst = tupleElt;
667+
break;
646668
}
669+
return SILValue(tupleExtractInst);
670+
}
647671

648-
if (getKind() == ArrayCallKind::kArrayInit)
672+
SILValue swift::ArraySemanticsCall::getArrayValue() const {
673+
ArrayCallKind arrayCallKind = getKind();
674+
if (arrayCallKind == ArrayCallKind::kArrayInit)
649675
return SILValue(SemanticsCall);
650-
651-
return SILValue();
676+
return getArrayUninitializedInitResult(*this, 0);
652677
}
653678

654679
SILValue swift::ArraySemanticsCall::getArrayElementStoragePointer() const {
655-
if (getKind() == ArrayCallKind::kArrayUninitialized) {
656-
TupleExtractInst *ArrayElementStorage = nullptr;
657-
for (auto *Op : SemanticsCall->getUses()) {
658-
auto *TupleElt = dyn_cast<TupleExtractInst>(Op->getUser());
659-
if (!TupleElt)
660-
return SILValue();
661-
switch (TupleElt->getFieldNo()) {
662-
default:
663-
return SILValue();
664-
case 0: {
665-
// Ignore the array value.
666-
break;
667-
}
668-
case 1:
669-
// Should only have one tuple extract after CSE.
670-
if (ArrayElementStorage)
671-
return SILValue();
672-
ArrayElementStorage = TupleElt;
673-
break;
674-
}
675-
}
676-
return SILValue(ArrayElementStorage);
677-
}
678-
679-
return SILValue();
680+
return getArrayUninitializedInitResult(*this, 1);
680681
}
681682

682683
bool swift::ArraySemanticsCall::replaceByValue(SILValue V) {
@@ -786,3 +787,75 @@ bool swift::ArraySemanticsCall::replaceByAppendingValues(
786787

787788
return true;
788789
}
790+
791+
bool swift::ArraySemanticsCall::mapInitializationStores(
792+
llvm::DenseMap<uint64_t, StoreInst *> &ElementValueMap) {
793+
if (getKind() != ArrayCallKind::kArrayUninitialized &&
794+
getKind() != ArrayCallKind::kArrayUninitializedIntrinsic)
795+
return false;
796+
SILValue ElementBuffer = getArrayElementStoragePointer();
797+
if (!ElementBuffer)
798+
return false;
799+
800+
// Match initialization stores into ElementBuffer. E.g.
801+
// %83 = struct_extract %element_buffer : $UnsafeMutablePointer<Int>
802+
// %84 = pointer_to_address %83 : $Builtin.RawPointer to strict $*Int
803+
// store %85 to %84 : $*Int
804+
// %87 = integer_literal $Builtin.Word, 1
805+
// %88 = index_addr %84 : $*Int, %87 : $Builtin.Word
806+
// store %some_value to %88 : $*Int
807+
808+
// If this an ArrayUinitializedIntrinsic then the ElementBuffer is a
809+
// builtin.RawPointer. Otherwise, it is an UnsafeMutablePointer, which would
810+
// be struct-extracted to obtain a builtin.RawPointer.
811+
SILValue UnsafeMutablePointerExtract =
812+
(getKind() == ArrayCallKind::kArrayUninitialized)
813+
? dyn_cast_or_null<StructExtractInst>(
814+
getSingleNonDebugUser(ElementBuffer))
815+
: ElementBuffer;
816+
if (!UnsafeMutablePointerExtract)
817+
return false;
818+
819+
auto *PointerToAddress = dyn_cast_or_null<PointerToAddressInst>(
820+
getSingleNonDebugUser(UnsafeMutablePointerExtract));
821+
if (!PointerToAddress)
822+
return false;
823+
824+
// Match the stores. We can have either a store directly to the address or
825+
// to an index_addr projection.
826+
for (auto *Op : PointerToAddress->getUses()) {
827+
auto *Inst = Op->getUser();
828+
829+
// Store to the base.
830+
auto *SI = dyn_cast<StoreInst>(Inst);
831+
if (SI && SI->getDest() == PointerToAddress) {
832+
// We have already seen an entry for this index bail.
833+
if (ElementValueMap.count(0))
834+
return false;
835+
ElementValueMap[0] = SI;
836+
continue;
837+
} else if (SI)
838+
return false;
839+
840+
// Store to an index_addr projection.
841+
auto *IndexAddr = dyn_cast<IndexAddrInst>(Inst);
842+
if (!IndexAddr)
843+
return false;
844+
SI = dyn_cast_or_null<StoreInst>(getSingleNonDebugUser(IndexAddr));
845+
if (!SI || SI->getDest() != IndexAddr)
846+
return false;
847+
auto *Index = dyn_cast<IntegerLiteralInst>(IndexAddr->getIndex());
848+
if (!Index)
849+
return false;
850+
auto IndexVal = Index->getValue();
851+
// Let's not blow up our map.
852+
if (IndexVal.getActiveBits() > 16)
853+
return false;
854+
// Already saw an entry.
855+
if (ElementValueMap.count(IndexVal.getZExtValue()))
856+
return false;
857+
858+
ElementValueMap[IndexVal.getZExtValue()] = SI;
859+
}
860+
return !ElementValueMap.empty();
861+
}

lib/SILOptimizer/LoopTransforms/COWArrayOpt.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,7 @@ static bool isNonMutatingArraySemanticCall(SILInstruction *Inst) {
314314
case ArrayCallKind::kWithUnsafeMutableBufferPointer:
315315
case ArrayCallKind::kArrayInit:
316316
case ArrayCallKind::kArrayUninitialized:
317+
case ArrayCallKind::kArrayUninitializedIntrinsic:
317318
case ArrayCallKind::kAppendContentsOf:
318319
case ArrayCallKind::kAppendElement:
319320
return false;
@@ -662,7 +663,8 @@ bool COWArrayOpt::hasLoopOnlyDestructorSafeArrayOperations() {
662663
auto Kind = Sem.getKind();
663664
// Safe because they create new arrays.
664665
if (Kind == ArrayCallKind::kArrayInit ||
665-
Kind == ArrayCallKind::kArrayUninitialized)
666+
Kind == ArrayCallKind::kArrayUninitialized ||
667+
Kind == ArrayCallKind::kArrayUninitializedIntrinsic)
666668
continue;
667669
// All array types must be the same. This is a stronger guaranteed than
668670
// we actually need. The requirement is that we can't create another

lib/SILOptimizer/Transforms/ArrayElementValuePropagation.cpp

Lines changed: 11 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ class ArrayAllocation {
6767
/// A map of Array indices to element values
6868
llvm::DenseMap<uint64_t, SILValue> ElementValueMap;
6969

70-
bool mapInitializationStores(SILValue ElementBuffer);
70+
bool mapInitializationStores(ArraySemanticsCall arrayUninitCall);
7171
bool recursivelyCollectUses(ValueBase *Def);
7272
bool replacementsAreValid();
7373

@@ -93,64 +93,16 @@ class ArrayAllocation {
9393
};
9494

9595
/// Map the indices of array element initialization stores to their values.
96-
bool ArrayAllocation::mapInitializationStores(SILValue ElementBuffer) {
97-
assert(ElementBuffer &&
98-
"Must have identified an array element storage pointer");
99-
100-
// Match initialization stores.
101-
// %83 = struct_extract %element_buffer : $UnsafeMutablePointer<Int>
102-
// %84 = pointer_to_address %83 : $Builtin.RawPointer to strict $*Int
103-
// store %85 to %84 : $*Int
104-
// %87 = integer_literal $Builtin.Word, 1
105-
// %88 = index_addr %84 : $*Int, %87 : $Builtin.Word
106-
// store %some_value to %88 : $*Int
107-
108-
auto *UnsafeMutablePointerExtract =
109-
dyn_cast_or_null<StructExtractInst>(getSingleNonDebugUser(ElementBuffer));
110-
if (!UnsafeMutablePointerExtract)
96+
bool ArrayAllocation::mapInitializationStores(
97+
ArraySemanticsCall arrayUninitCall) {
98+
llvm::DenseMap<uint64_t, StoreInst *> elementStoreMap;
99+
if (!arrayUninitCall.mapInitializationStores(elementStoreMap))
111100
return false;
112-
auto *PointerToAddress = dyn_cast_or_null<PointerToAddressInst>(
113-
getSingleNonDebugUser(UnsafeMutablePointerExtract));
114-
if (!PointerToAddress)
115-
return false;
116-
117-
// Match the stores. We can have either a store directly to the address or
118-
// to an index_addr projection.
119-
for (auto *Op : PointerToAddress->getUses()) {
120-
auto *Inst = Op->getUser();
121-
122-
// Store to the base.
123-
auto *SI = dyn_cast<StoreInst>(Inst);
124-
if (SI && SI->getDest() == PointerToAddress) {
125-
// We have already seen an entry for this index bail.
126-
if (ElementValueMap.count(0))
127-
return false;
128-
ElementValueMap[0] = SI->getSrc();
129-
continue;
130-
} else if (SI)
131-
return false;
132-
133-
// Store an index_addr projection.
134-
auto *IndexAddr = dyn_cast<IndexAddrInst>(Inst);
135-
if (!IndexAddr)
136-
return false;
137-
SI = dyn_cast_or_null<StoreInst>(getSingleNonDebugUser(IndexAddr));
138-
if (!SI || SI->getDest() != IndexAddr)
139-
return false;
140-
auto *Index = dyn_cast<IntegerLiteralInst>(IndexAddr->getIndex());
141-
if (!Index)
142-
return false;
143-
auto IndexVal = Index->getValue();
144-
// Let's not blow up our map.
145-
if (IndexVal.getActiveBits() > 16)
146-
return false;
147-
// Already saw an entry.
148-
if (ElementValueMap.count(IndexVal.getZExtValue()))
149-
return false;
150-
151-
ElementValueMap[IndexVal.getZExtValue()] = SI->getSrc();
152-
}
153-
return !ElementValueMap.empty();
101+
// Extract the SIL values of the array elements from the stores.
102+
ElementValueMap.grow(elementStoreMap.size());
103+
for (auto keyValue : elementStoreMap)
104+
ElementValueMap[keyValue.getFirst()] = keyValue.getSecond()->getSrc();
105+
return true;
154106
}
155107

156108
bool ArrayAllocation::replacementsAreValid() {
@@ -217,12 +169,8 @@ bool ArrayAllocation::analyze(ApplyInst *Alloc) {
217169
if (!ArrayValue)
218170
return false;
219171

220-
SILValue ElementBuffer = Uninitialized.getArrayElementStoragePointer();
221-
if (!ElementBuffer)
222-
return false;
223-
224172
// Figure out all stores to the array.
225-
if (!mapInitializationStores(ElementBuffer))
173+
if (!mapInitializationStores(Uninitialized))
226174
return false;
227175

228176
// Check if the array value was stored or has escaped.

lib/SILOptimizer/Transforms/DeadObjectElimination.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -641,7 +641,8 @@ static bool removeAndReleaseArray(SingleValueInstruction *NewArrayValue,
641641
/// side effect?
642642
static bool isAllocatingApply(SILInstruction *Inst) {
643643
ArraySemanticsCall ArrayAlloc(Inst);
644-
return ArrayAlloc.getKind() == ArrayCallKind::kArrayUninitialized;
644+
return ArrayAlloc.getKind() == ArrayCallKind::kArrayUninitialized ||
645+
ArrayAlloc.getKind() == ArrayCallKind::kArrayUninitializedIntrinsic;
645646
}
646647

647648
namespace {
@@ -832,7 +833,9 @@ static bool getDeadInstsAfterInitializerRemoved(
832833
bool DeadObjectElimination::processAllocApply(ApplyInst *AI,
833834
DeadEndBlocks &DEBlocks) {
834835
// Currently only handle array.uninitialized
835-
if (ArraySemanticsCall(AI).getKind() != ArrayCallKind::kArrayUninitialized)
836+
if (ArraySemanticsCall(AI).getKind() != ArrayCallKind::kArrayUninitialized &&
837+
ArraySemanticsCall(AI).getKind() !=
838+
ArrayCallKind::kArrayUninitializedIntrinsic)
836839
return false;
837840

838841
llvm::SmallVector<SILInstruction *, 8> instsDeadAfterInitializerRemoved;

test/SILOptimizer/dead_array_elim.sil

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class TrivialDestructor {
2121
// Remove a dead array.
2222
// rdar://20980377 Add dead array elimination to DeadObjectElimination
2323
// Swift._allocateUninitializedArray <A> (Builtin.Word) -> (Swift.Array<A>, Builtin.RawPointer)
24-
sil [_semantics "array.uninitialized"] @allocArray : $@convention(thin) <τ_0_0> (Builtin.Word) -> @owned (Array<τ_0_0>, Builtin.RawPointer)
24+
sil [_semantics "array.uninitialized_intrinsic"] @allocArray : $@convention(thin) <τ_0_0> (Builtin.Word) -> @owned (Array<τ_0_0>, Builtin.RawPointer)
2525

2626
sil [_semantics "array.uninitialized"] @adoptStorageSpecialiedForInt : $@convention(method) (@guaranteed _ContiguousArrayStorage<Int>, Builtin.Word, @thin Array<Int>.Type) -> (@owned Array<Int>, UnsafeMutablePointer<Int>)
2727

0 commit comments

Comments
 (0)