Skip to content

Commit a6bed21

Browse files
committed
[SIL Optimization] Make ArraySemantics.cpp aware of "array.uninitialized_intrinsic"
semantics attribute that is used by the top-level array initializer (in ArrayShared.swift), which is the entry point used by the compiler to initialize array from array literals. This initializer is early-inlined so that other optimizations can work on its body. Fix DeadObjectElimination and ArrayCOWOpts optimization passes to work with this semantics attribute in addition to "array.uninitialized", which they already use. Refactor mapInitializationStores function from ArrayElementValuePropagation.cpp to ArraySemantic.cpp so that the array-initialization pattern matching functionality implemented by the function can be reused by other optimizations.
1 parent 956b9f2 commit a6bed21

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)