Skip to content

Commit 42092c5

Browse files
authored
Merge pull request #24205 from eeckstein/outline-dictionary-literals
2 parents a0676e0 + a402544 commit 42092c5

File tree

2 files changed

+136
-37
lines changed

2 files changed

+136
-37
lines changed

lib/SILOptimizer/Transforms/ObjectOutliner.cpp

Lines changed: 98 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -27,23 +27,26 @@ class ObjectOutliner {
2727
NominalTypeDecl *ArrayDecl = nullptr;
2828
int GlobIdx = 0;
2929

30+
// Instructions to be deleted.
31+
llvm::SmallVector<SILInstruction *, 4> ToRemove;
32+
3033
bool isCOWType(SILType type) {
3134
return type.getNominalOrBoundGenericNominal() == ArrayDecl;
3235
}
3336

34-
bool isValidUseOfObject(SILInstruction *Val, bool isCOWObject,
37+
bool isValidUseOfObject(SILInstruction *Val,
38+
bool isCOWObject,
3539
ApplyInst **FindStringCall = nullptr);
3640

3741
bool getObjectInitVals(SILValue Val,
3842
llvm::DenseMap<VarDecl *, StoreInst *> &MemberStores,
3943
llvm::SmallVectorImpl<StoreInst *> &TailStores,
44+
unsigned NumTailTupleElements,
4045
ApplyInst **FindStringCall);
41-
bool handleTailAddr(int TailIdx, SILInstruction *I,
46+
bool handleTailAddr(int TailIdx, SILInstruction *I, unsigned NumTailTupleElements,
4247
llvm::SmallVectorImpl<StoreInst *> &TailStores);
4348

44-
bool
45-
optimizeObjectAllocation(AllocRefInst *ARI,
46-
llvm::SmallVector<SILInstruction *, 4> &ToRemove);
49+
bool optimizeObjectAllocation(AllocRefInst *ARI);
4750
void replaceFindStringCall(ApplyInst *FindStringCall);
4851

4952
public:
@@ -60,23 +63,27 @@ bool ObjectOutliner::run(SILFunction *F) {
6063
for (auto &BB : *F) {
6164
auto Iter = BB.begin();
6265

63-
// We can't remove instructions willy-nilly as we iterate because
64-
// that might cause a pointer to the next instruction to become
65-
// garbage, causing iterator invalidations (and crashes).
66-
// Instead, we collect in a list the instructions we want to remove
67-
// and erase the BB they belong to at the end of the loop, once we're
68-
// sure it's safe to do so.
69-
llvm::SmallVector<SILInstruction *, 4> ToRemove;
70-
7166
while (Iter != BB.end()) {
7267
SILInstruction *I = &*Iter;
7368
Iter++;
7469
if (auto *ARI = dyn_cast<AllocRefInst>(I)) {
75-
hasChanged |= optimizeObjectAllocation(ARI, ToRemove);
70+
unsigned GarbageSize = ToRemove.size();
71+
72+
// Try to replace the alloc_ref with a static object.
73+
if (optimizeObjectAllocation(ARI)) {
74+
hasChanged = true;
75+
} else {
76+
// No transformation was made. Restore the original state of the garbage list.
77+
assert(GarbageSize <= ToRemove.size());
78+
ToRemove.resize(GarbageSize);
79+
}
7680
}
7781
}
82+
// Delaying the deallocation of instructions avoids problems with iterator invalidation in the
83+
// instruction loop above.
7884
for (auto *I : ToRemove)
7985
I->eraseFromParent();
86+
ToRemove.clear();
8087
}
8188
return hasChanged;
8289
}
@@ -177,6 +184,13 @@ bool ObjectOutliner::isValidUseOfObject(SILInstruction *I, bool isCOWObject,
177184
BuiltinValueKind K = BI->getBuiltinInfo().ID;
178185
if (K == BuiltinValueKind::ICMP_EQ || K == BuiltinValueKind::ICMP_NE)
179186
return true;
187+
if (K == BuiltinValueKind::DestroyArray) {
188+
// We must not try to delete the tail allocated values. Although this would be a no-op
189+
// (because we only handle trivial types), it would be semantically wrong to apply this
190+
// builtin on the outlined object.
191+
ToRemove.push_back(BI);
192+
return true;
193+
}
180194
return false;
181195
}
182196

@@ -194,14 +208,28 @@ bool ObjectOutliner::isValidUseOfObject(SILInstruction *I, bool isCOWObject,
194208

195209
/// Handle the address of a tail element.
196210
bool ObjectOutliner::handleTailAddr(int TailIdx, SILInstruction *TailAddr,
197-
llvm::SmallVectorImpl<StoreInst *> &TailStores) {
198-
if (TailIdx >= 0 && TailIdx < (int)TailStores.size()) {
199-
if (auto *SI = dyn_cast<StoreInst>(TailAddr)) {
200-
if (!isValidInitVal(SI->getSrc()) || TailStores[TailIdx])
201-
return false;
202-
TailStores[TailIdx] = SI;
211+
unsigned NumTailTupleElements,
212+
llvm::SmallVectorImpl<StoreInst *> &TailStores) {
213+
if (NumTailTupleElements > 0) {
214+
if (auto *TEA = dyn_cast<TupleElementAddrInst>(TailAddr)) {
215+
unsigned TupleIdx = TEA->getFieldNo();
216+
assert(TupleIdx < NumTailTupleElements);
217+
for (Operand *Use : TEA->getUses()) {
218+
if (!handleTailAddr(TailIdx * NumTailTupleElements + TupleIdx, Use->getUser(), 0,
219+
TailStores))
220+
return false;
221+
}
203222
return true;
204223
}
224+
} else {
225+
if (TailIdx >= 0 && TailIdx < (int)TailStores.size()) {
226+
if (auto *SI = dyn_cast<StoreInst>(TailAddr)) {
227+
if (!isValidInitVal(SI->getSrc()) || TailStores[TailIdx])
228+
return false;
229+
TailStores[TailIdx] = SI;
230+
return true;
231+
}
232+
}
205233
}
206234
return isValidUseOfObject(TailAddr, /*isCOWObject*/false);
207235
}
@@ -210,12 +238,13 @@ bool ObjectOutliner::handleTailAddr(int TailIdx, SILInstruction *TailAddr,
210238
bool ObjectOutliner::getObjectInitVals(SILValue Val,
211239
llvm::DenseMap<VarDecl *, StoreInst *> &MemberStores,
212240
llvm::SmallVectorImpl<StoreInst *> &TailStores,
241+
unsigned NumTailTupleElements,
213242
ApplyInst **FindStringCall) {
214243
for (Operand *Use : Val->getUses()) {
215244
SILInstruction *User = Use->getUser();
216245
if (auto *UC = dyn_cast<UpcastInst>(User)) {
217246
// Upcast is transparent.
218-
if (!getObjectInitVals(UC, MemberStores, TailStores, FindStringCall))
247+
if (!getObjectInitVals(UC, MemberStores, TailStores, NumTailTupleElements, FindStringCall))
219248
return false;
220249
} else if (auto *REA = dyn_cast<RefElementAddrInst>(User)) {
221250
// The address of a stored property.
@@ -242,11 +271,11 @@ bool ObjectOutliner::getObjectInitVals(SILValue Val,
242271
TailIdx = Index->getValue().getZExtValue();
243272

244273
for (Operand *IAUse : IA->getUses()) {
245-
if (!handleTailAddr(TailIdx, IAUse->getUser(), TailStores))
274+
if (!handleTailAddr(TailIdx, IAUse->getUser(), NumTailTupleElements, TailStores))
246275
return false;
247276
}
248277
// Without an index_addr it's the first tail element.
249-
} else if (!handleTailAddr(/*TailIdx*/0, TailUser, TailStores)) {
278+
} else if (!handleTailAddr(/*TailIdx*/0, TailUser, NumTailTupleElements, TailStores)) {
250279
return false;
251280
}
252281
}
@@ -280,9 +309,7 @@ class GlobalVariableMangler : public Mangle::ASTMangler {
280309
/// func getarray() -> [Int] {
281310
/// return [1, 2, 3]
282311
/// }
283-
bool ObjectOutliner::optimizeObjectAllocation(
284-
AllocRefInst *ARI, llvm::SmallVector<SILInstruction *, 4> &ToRemove) {
285-
312+
bool ObjectOutliner::optimizeObjectAllocation(AllocRefInst *ARI) {
286313
if (ARI->isObjC())
287314
return false;
288315

@@ -316,13 +343,30 @@ bool ObjectOutliner::optimizeObjectAllocation(
316343
llvm::SmallVector<VarDecl *, 16> Fields;
317344
getFields(Cl, Fields);
318345

319-
// Get the initialization stores of the object's properties and tail
320-
// allocated elements. Also check if there are any "bad" uses of the object.
321346
llvm::DenseMap<VarDecl *, StoreInst *> MemberStores;
347+
348+
// A store for each element of the tail allocated array. In case of a tuple, there is a store
349+
// for each tuple element. For example, a 3 element array of 2-element tuples
350+
// [ (i0, i1), (i2, i3), (i4, i5) ]
351+
// results in following store instructions, collected in TailStores:
352+
// [ store i0, store i1, store i2, store i3, store i4, store i5 ]
322353
llvm::SmallVector<StoreInst *, 16> TailStores;
323-
TailStores.resize(NumTailElems);
354+
355+
unsigned NumStores = NumTailElems;
356+
unsigned NumTailTupleElems = 0;
357+
if (auto Tuple = TailType.getAs<TupleType>()) {
358+
NumTailTupleElems = Tuple->getNumElements();
359+
if (NumTailTupleElems == 0)
360+
return false;
361+
NumStores *= NumTailTupleElems;
362+
}
363+
364+
TailStores.resize(NumStores);
324365
ApplyInst *FindStringCall = nullptr;
325-
if (!getObjectInitVals(ARI, MemberStores, TailStores, &FindStringCall))
366+
367+
// Get the initialization stores of the object's properties and tail
368+
// allocated elements. Also check if there are any "bad" uses of the object.
369+
if (!getObjectInitVals(ARI, MemberStores, TailStores, NumTailTupleElems, &FindStringCall))
326370
return false;
327371

328372
// Is there a store for all the class properties?
@@ -372,13 +416,32 @@ bool ObjectOutliner::optimizeObjectAllocation(
372416
cast<SingleValueInstruction>(MemberStore->getSrc())));
373417
ToRemove.push_back(MemberStore);
374418
}
375-
// Create the initializers for the tail elements.
376419
unsigned NumBaseElements = ObjectArgs.size();
377-
for (StoreInst *TailStore : TailStores) {
378-
ObjectArgs.push_back(Cloner.clone(
379-
cast<SingleValueInstruction>(TailStore->getSrc())));
380-
ToRemove.push_back(TailStore);
420+
421+
// Create the initializers for the tail elements.
422+
if (NumTailTupleElems == 0) {
423+
// The non-tuple element case.
424+
for (StoreInst *TailStore : TailStores) {
425+
ObjectArgs.push_back(Cloner.clone(
426+
cast<SingleValueInstruction>(TailStore->getSrc())));
427+
ToRemove.push_back(TailStore);
428+
}
429+
} else {
430+
// The elements are tuples: combine NumTailTupleElems elements from TailStores to a single tuple
431+
// instruction.
432+
for (unsigned EIdx = 0; EIdx < NumTailElems; EIdx++) {
433+
SmallVector<SILValue, 8> TupleElems;
434+
for (unsigned TIdx = 0; TIdx < NumTailTupleElems; TIdx++) {
435+
StoreInst *TailStore = TailStores[EIdx * NumTailTupleElems + TIdx];
436+
SILValue V = Cloner.clone(cast<SingleValueInstruction>(TailStore->getSrc()));
437+
TupleElems.push_back(V);
438+
ToRemove.push_back(TailStore);
439+
}
440+
auto *TI = Cloner.getBuilder().createTuple(ARI->getLoc(), TailType, TupleElems);
441+
ObjectArgs.push_back(TI);
442+
}
381443
}
444+
382445
// Create the initializer for the object itself.
383446
SILBuilder StaticInitBuilder(Glob);
384447
StaticInitBuilder.createObject(ArtificialUnreachableLocation(),

test/SILOptimizer/static_arrays.swift

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,22 @@
5050
// CHECK: object {{.*}} ({{[^,]*}}, [tail_elems] {{[^,]*}}, {{[^,]*}})
5151
// CHECK-NEXT: }
5252

53+
// CHECK-LABEL: outlined variable #0 of returnDictionary()
54+
// CHECK-NEXT: sil_global private @{{.*}}returnDictionary{{.*}} = {
55+
// CHECK-DAG: integer_literal $Builtin.Int{{[0-9]+}}, 5
56+
// CHECK-DAG: integer_literal $Builtin.Int{{[0-9]+}}, 4
57+
// CHECK-DAG: integer_literal $Builtin.Int{{[0-9]+}}, 2
58+
// CHECK-DAG: integer_literal $Builtin.Int{{[0-9]+}}, 1
59+
// CHECK-DAG: integer_literal $Builtin.Int{{[0-9]+}}, 6
60+
// CHECK-DAG: integer_literal $Builtin.Int{{[0-9]+}}, 3
61+
// CHECK: object {{.*}} ({{[^,]*}}, [tail_elems]
62+
// CHECK-NEXT: }
63+
64+
// CHECK-LABEL: outlined variable #0 of returnStringDictionary()
65+
// CHECK-NEXT: sil_global private @{{.*}}returnStringDictionary{{.*}} = {
66+
// CHECK: object {{.*}} ({{[^,]*}}, [tail_elems]
67+
// CHECK-NEXT: }
68+
5369
// CHECK-LABEL: sil_global private @{{.*}}main{{.*}} = {
5470
// CHECK-DAG: integer_literal $Builtin.Int{{[0-9]+}}, 100
5571
// CHECK-DAG: integer_literal $Builtin.Int{{[0-9]+}}, 101
@@ -121,6 +137,22 @@ func arrayWithEmptyElements() -> [Empty] {
121137
return [Empty()]
122138
}
123139

140+
// CHECK-LABEL: sil {{.*}}returnDictionary{{.*}} : $@convention(thin) () -> @owned Dictionary<Int, Int> {
141+
// CHECK: global_value @{{.*}}returnDictionary{{.*}}
142+
// CHECK: return
143+
@inline(never)
144+
public func returnDictionary() -> [Int:Int] {
145+
return [1:2, 3:4, 5:6]
146+
}
147+
148+
// CHECK-LABEL: sil {{.*}}returnStringDictionary{{.*}} : $@convention(thin) () -> @owned Dictionary<String, String> {
149+
// CHECK: global_value @{{.*}}returnStringDictionary{{.*}}
150+
// CHECK: return
151+
@inline(never)
152+
public func returnStringDictionary() -> [String:String] {
153+
return ["1":"2", "3":"4", "5":"6"]
154+
}
155+
124156
// CHECK-OUTPUT: [100, 101, 102]
125157
print(globalVariable)
126158
// CHECK-OUTPUT-NEXT: 11
@@ -136,9 +168,13 @@ storeArray()
136168
// CHECK-OUTPUT-NEXT: [227, 228]
137169
print(gg!)
138170

171+
let dict = returnDictionary()
172+
// CHECK-OUTPUT-NEXT: dict 3: 2, 4, 6
173+
print("dict \(dict.count): \(dict[1]!), \(dict[3]!), \(dict[5]!)")
139174

140-
141-
175+
let sdict = returnStringDictionary()
176+
// CHECK-OUTPUT-NEXT: sdict 3: 2, 4, 6
177+
print("sdict \(sdict.count): \(sdict["1"]!), \(sdict["3"]!), \(sdict["5"]!)")
142178

143179

144180
public class SwiftClass {}

0 commit comments

Comments
 (0)