Skip to content

Commit a402544

Browse files
committed
SILOptimizer: support for Dictionary literals generated in the data section.
Actually: generate the array of (key, value) tuples in the data section, which is then passed to Dictionary.init(dictionaryLiteral:) We already do this for simple arrays, e.g. arrays with trivial element types. The only change needed for dictionary literals is to support tuple types in the ObjectOutliner. The effect of this optimization is a significant reduction in code size for dictionary literals - and an increase in data size. But in most cases there is a considerable net win for code+data size in total.
1 parent a97a519 commit a402544

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)