Skip to content

Commit a0c03e5

Browse files
committed
stdlib, SIL optimizer: use the SIL copy-on-write representation in the Array types.
Use the new builtins for COW representation in Array, ContiguousArray and ArraySlice. The basic idea is to strictly separate code which mutates an array buffer from code which reads from an array. The concept is explained in more detail in docs/SIL.rst, section "Copy-on-Write Representation". The main change is to use beginCOWMutation() instead of isUniquelyReferenced() and insert endCOWMutation() at the end of all mutating functions. Also, reading from the array buffer must be done differently, depending on if the buffer is in a mutable or immutable state. All the required invariants are enforced by runtime checks - but only in an assert-build of the library: a bit in the buffer flags indicates if the buffer is mutable or not. Along with the library changes, also two optimizations needed to be updated: COWArrayOpt and ObjectOutliner.
1 parent d24f4bc commit a0c03e5

19 files changed

+1079
-474
lines changed

lib/SILOptimizer/LoopTransforms/ArrayOpt.h

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -118,15 +118,17 @@ class StructUseCollector {
118118
}
119119
}
120120

121-
/// Returns true if there is a single address user of the value.
122-
bool hasSingleAddressUse(SILInstruction *SingleAddressUser) {
121+
/// Returns true if there are only address users of the value.
122+
bool hasOnlyAddressUses(ApplyInst *use1, ApplyInst *use2) {
123123
if (!AggregateAddressUsers.empty())
124124
return false;
125125
if (!ElementAddressUsers.empty())
126126
return false;
127-
if (StructAddressUsers.size() != 1)
128-
return false;
129-
return StructAddressUsers[0] == SingleAddressUser;
127+
for (SILInstruction *user : StructAddressUsers) {
128+
if (user != use1 && user != use2)
129+
return false;
130+
}
131+
return true;
130132
}
131133

132134
protected:

lib/SILOptimizer/LoopTransforms/COWArrayOpt.cpp

Lines changed: 57 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,18 @@ bool COWArrayOpt::checkSafeArrayAddressUses(UserList &AddressUsers) {
436436
return true;
437437
}
438438

439+
template <typename UserRange>
440+
ArraySemanticsCall getEndMutationCall(const UserRange &AddressUsers) {
441+
for (auto *UseInst : AddressUsers) {
442+
if (auto *AI = dyn_cast<ApplyInst>(UseInst)) {
443+
ArraySemanticsCall ASC(AI);
444+
if (ASC.getKind() == ArrayCallKind::kEndMutation)
445+
return ASC;
446+
}
447+
}
448+
return ArraySemanticsCall();
449+
}
450+
439451
/// Returns true if this instruction is a safe array use if all of its users are
440452
/// also safe array users.
441453
static SILValue isTransitiveSafeUser(SILInstruction *I) {
@@ -811,8 +823,14 @@ void COWArrayOpt::hoistAddressProjections(Operand &ArrayOp) {
811823
}
812824
}
813825

814-
/// Check if this call to "make_mutable" is hoistable, and move it, or delete it
815-
/// if it's already hoisted.
826+
/// Check if this call to "make_mutable" is hoistable, and copy it, along with
827+
/// the corresponding end_mutation call, to the loop pre-header.
828+
///
829+
/// The origial make_mutable/end_mutation calls remain in the loop, because
830+
/// removing them would violate the COW representation rules.
831+
/// Having those calls in the pre-header will then enable COWOpts (after
832+
/// inlining) to constant fold the uniqueness check of the begin_cow_mutation
833+
/// in the loop.
816834
bool COWArrayOpt::hoistMakeMutable(ArraySemanticsCall MakeMutable,
817835
bool dominatesExits) {
818836
LLVM_DEBUG(llvm::dbgs() << " Checking mutable array: " <<CurrentArrayAddr);
@@ -872,6 +890,18 @@ bool COWArrayOpt::hoistMakeMutable(ArraySemanticsCall MakeMutable,
872890
return false;
873891
}
874892

893+
auto ArrayUsers = llvm::map_range(MakeMutable.getSelf()->getUses(),
894+
ValueBase::UseToUser());
895+
896+
// There should be a call to end_mutation. Find it so that we can copy it to
897+
// the pre-header.
898+
ArraySemanticsCall EndMutation = getEndMutationCall(ArrayUsers);
899+
if (!EndMutation) {
900+
EndMutation = getEndMutationCall(StructUses.StructAddressUsers);
901+
if (!EndMutation)
902+
return false;
903+
}
904+
875905
// Hoist the make_mutable.
876906
LLVM_DEBUG(llvm::dbgs() << " Hoisting make_mutable: " << *MakeMutable);
877907

@@ -880,12 +910,18 @@ bool COWArrayOpt::hoistMakeMutable(ArraySemanticsCall MakeMutable,
880910
assert(MakeMutable.canHoist(Preheader->getTerminator(), DomTree) &&
881911
"Should be able to hoist make_mutable");
882912

883-
MakeMutable.hoist(Preheader->getTerminator(), DomTree);
913+
// Copy the make_mutable and end_mutation calls to the pre-header.
914+
TermInst *insertionPoint = Preheader->getTerminator();
915+
ApplyInst *hoistedMM = MakeMutable.copyTo(insertionPoint, DomTree);
916+
ApplyInst *EMInst = EndMutation;
917+
ApplyInst *hoistedEM = cast<ApplyInst>(EMInst->clone(insertionPoint));
918+
hoistedEM->setArgument(0, hoistedMM->getArgument(0));
919+
placeFuncRef(hoistedEM, DomTree);
884920

885921
// Register array loads. This is needed for hoisting make_mutable calls of
886922
// inner arrays in the two-dimensional case.
887923
if (arrayContainerIsUnique &&
888-
StructUses.hasSingleAddressUse((ApplyInst *)MakeMutable)) {
924+
StructUses.hasOnlyAddressUses((ApplyInst *)MakeMutable, EMInst)) {
889925
for (auto use : MakeMutable.getSelf()->getUses()) {
890926
if (auto *LI = dyn_cast<LoadInst>(use->getUser()))
891927
HoistableLoads.insert(LI);
@@ -917,39 +953,33 @@ bool COWArrayOpt::run() {
917953
// is only mapped to a call once the analysis has determined that no
918954
// make_mutable calls are required within the loop body for that array.
919955
llvm::SmallDenseMap<SILValue, ApplyInst*> ArrayMakeMutableMap;
920-
956+
957+
llvm::SmallVector<ArraySemanticsCall, 8> makeMutableCalls;
958+
921959
for (auto *BB : Loop->getBlocks()) {
922960
if (ColdBlocks.isCold(BB))
923961
continue;
924-
bool dominatesExits = dominatesExitingBlocks(BB);
925-
for (auto II = BB->begin(), IE = BB->end(); II != IE;) {
926-
// Inst may be moved by hoistMakeMutable.
927-
SILInstruction *Inst = &*II;
928-
++II;
929-
ArraySemanticsCall MakeMutableCall(Inst, "array.make_mutable");
930-
if (!MakeMutableCall)
931-
continue;
962+
963+
// Instructions are getting moved around. To not mess with iterator
964+
// invalidation, first collect all calls, and then do the transformation.
965+
for (SILInstruction &I : *BB) {
966+
ArraySemanticsCall MakeMutableCall(&I, "array.make_mutable");
967+
if (MakeMutableCall)
968+
makeMutableCalls.push_back(MakeMutableCall);
969+
}
932970

971+
bool dominatesExits = dominatesExitingBlocks(BB);
972+
for (ArraySemanticsCall MakeMutableCall : makeMutableCalls) {
933973
CurrentArrayAddr = MakeMutableCall.getSelf();
934974
auto HoistedCallEntry = ArrayMakeMutableMap.find(CurrentArrayAddr);
935975
if (HoistedCallEntry == ArrayMakeMutableMap.end()) {
936-
if (!hoistMakeMutable(MakeMutableCall, dominatesExits)) {
976+
if (hoistMakeMutable(MakeMutableCall, dominatesExits)) {
977+
ArrayMakeMutableMap[CurrentArrayAddr] = MakeMutableCall;
978+
HasChanged = true;
979+
} else {
937980
ArrayMakeMutableMap[CurrentArrayAddr] = nullptr;
938-
continue;
939981
}
940-
941-
ArrayMakeMutableMap[CurrentArrayAddr] = MakeMutableCall;
942-
HasChanged = true;
943-
continue;
944982
}
945-
946-
if (!HoistedCallEntry->second)
947-
continue;
948-
949-
LLVM_DEBUG(llvm::dbgs() << " Removing make_mutable call: "
950-
<< *MakeMutableCall);
951-
MakeMutableCall.removeCall();
952-
HasChanged = true;
953983
}
954984
}
955985
return HasChanged;

lib/SILOptimizer/Transforms/ObjectOutliner.cpp

Lines changed: 72 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,14 @@ class ObjectOutliner {
3535
return type.getNominalOrBoundGenericNominal() == ArrayDecl;
3636
}
3737

38-
bool isValidUseOfObject(SILInstruction *Val,
39-
bool isCOWObject,
40-
ApplyInst **FindStringCall = nullptr);
38+
bool isValidUseOfObject(SILInstruction *Val);
39+
40+
ApplyInst *findFindStringCall(SILValue V);
4141

4242
bool getObjectInitVals(SILValue Val,
4343
llvm::DenseMap<VarDecl *, StoreInst *> &MemberStores,
4444
llvm::SmallVectorImpl<StoreInst *> &TailStores,
45-
unsigned NumTailTupleElements,
46-
ApplyInst **FindStringCall);
45+
unsigned NumTailTupleElements);
4746
bool handleTailAddr(int TailIdx, SILInstruction *I, unsigned NumTailTupleElements,
4847
llvm::SmallVectorImpl<StoreInst *> &TailStores);
4948

@@ -116,13 +115,7 @@ static bool isValidInitVal(SILValue V) {
116115
}
117116

118117
/// Check if a use of an object may prevent outlining the object.
119-
///
120-
/// If \p isCOWObject is true, then the object reference is wrapped into a
121-
/// COW container. Currently this is just Array<T>.
122-
/// If a use is a call to the findStringSwitchCase semantic call, the apply
123-
/// is returned in \p FindStringCall.
124-
bool ObjectOutliner::isValidUseOfObject(SILInstruction *I, bool isCOWObject,
125-
ApplyInst **FindStringCall) {
118+
bool ObjectOutliner::isValidUseOfObject(SILInstruction *I) {
126119
switch (I->getKind()) {
127120
case SILInstructionKind::DebugValueAddrInst:
128121
case SILInstructionKind::DebugValueInst:
@@ -132,51 +125,25 @@ bool ObjectOutliner::isValidUseOfObject(SILInstruction *I, bool isCOWObject,
132125
case SILInstructionKind::StrongReleaseInst:
133126
case SILInstructionKind::FixLifetimeInst:
134127
case SILInstructionKind::SetDeallocatingInst:
128+
case SILInstructionKind::EndCOWMutationInst:
135129
return true;
136130

137-
case SILInstructionKind::ReturnInst:
138-
case SILInstructionKind::TryApplyInst:
139-
case SILInstructionKind::PartialApplyInst:
140-
case SILInstructionKind::StoreInst:
141-
/// We don't have a representation for COW objects in SIL, so we do some
142-
/// ad-hoc testing: We can ignore uses of a COW object if any use after
143-
/// this will do a uniqueness checking before the object is modified.
144-
return isCOWObject;
145-
146-
case SILInstructionKind::ApplyInst:
147-
if (!isCOWObject)
148-
return false;
149-
// There should only be a single call to findStringSwitchCase. But even
150-
// if there are multiple calls, it's not problem - we'll just optimize the
151-
// last one we find.
152-
if (cast<ApplyInst>(I)->hasSemantics(semantics::FIND_STRING_SWITCH_CASE))
153-
*FindStringCall = cast<ApplyInst>(I);
154-
return true;
155-
156-
case SILInstructionKind::StructInst:
157-
if (isCOWType(cast<StructInst>(I)->getType())) {
158-
// The object is wrapped into a COW container.
159-
isCOWObject = true;
160-
}
161-
break;
162-
163-
case SILInstructionKind::UncheckedRefCastInst:
164131
case SILInstructionKind::StructElementAddrInst:
165132
case SILInstructionKind::AddressToPointerInst:
166-
assert(!isCOWObject && "instruction cannot have a COW object as operand");
167-
break;
168-
133+
case SILInstructionKind::StructInst:
169134
case SILInstructionKind::TupleInst:
170135
case SILInstructionKind::TupleExtractInst:
171136
case SILInstructionKind::EnumInst:
172-
break;
173-
174137
case SILInstructionKind::StructExtractInst:
175-
// To be on the safe side we don't consider the object as COW if it is
176-
// extracted again from the COW container: the uniqueness check may be
177-
// optimized away in this case.
178-
isCOWObject = false;
179-
break;
138+
case SILInstructionKind::UncheckedRefCastInst:
139+
case SILInstructionKind::UpcastInst: {
140+
auto SVI = cast<SingleValueInstruction>(I);
141+
for (Operand *Use : getNonDebugUses(SVI)) {
142+
if (!isValidUseOfObject(Use->getUser()))
143+
return false;
144+
}
145+
return true;
146+
}
180147

181148
case SILInstructionKind::BuiltinInst: {
182149
// Handle the case for comparing addresses. This occurs when the Array
@@ -198,13 +165,37 @@ bool ObjectOutliner::isValidUseOfObject(SILInstruction *I, bool isCOWObject,
198165
default:
199166
return false;
200167
}
168+
}
201169

202-
auto SVI = cast<SingleValueInstruction>(I);
203-
for (Operand *Use : getNonDebugUses(SVI)) {
204-
if (!isValidUseOfObject(Use->getUser(), isCOWObject, FindStringCall))
205-
return false;
170+
/// Finds a call to findStringSwitchCase in the uses of \p V.
171+
ApplyInst *ObjectOutliner::findFindStringCall(SILValue V) {
172+
for (Operand *use : V->getUses()) {
173+
SILInstruction *user = use->getUser();
174+
switch (user->getKind()) {
175+
case SILInstructionKind::ApplyInst:
176+
// There should only be a single call to findStringSwitchCase. But even
177+
// if there are multiple calls, it's not problem - we'll just optimize the
178+
// last one we find.
179+
if (cast<ApplyInst>(user)->hasSemantics(semantics::FIND_STRING_SWITCH_CASE))
180+
return cast<ApplyInst>(user);
181+
break;
182+
183+
case SILInstructionKind::StructInst:
184+
case SILInstructionKind::TupleInst:
185+
case SILInstructionKind::UncheckedRefCastInst:
186+
case SILInstructionKind::UpcastInst: {
187+
if (ApplyInst *foundCall =
188+
findFindStringCall(cast<SingleValueInstruction>(user))) {
189+
return foundCall;
190+
}
191+
break;
192+
}
193+
194+
default:
195+
break;
196+
}
206197
}
207-
return true;
198+
return nullptr;
208199
}
209200

210201
/// Handle the address of a tail element.
@@ -232,20 +223,19 @@ bool ObjectOutliner::handleTailAddr(int TailIdx, SILInstruction *TailAddr,
232223
}
233224
}
234225
}
235-
return isValidUseOfObject(TailAddr, /*isCOWObject*/false);
226+
return isValidUseOfObject(TailAddr);
236227
}
237228

238229
/// Get the init values for an object's stored properties and its tail elements.
239230
bool ObjectOutliner::getObjectInitVals(SILValue Val,
240231
llvm::DenseMap<VarDecl *, StoreInst *> &MemberStores,
241232
llvm::SmallVectorImpl<StoreInst *> &TailStores,
242-
unsigned NumTailTupleElements,
243-
ApplyInst **FindStringCall) {
233+
unsigned NumTailTupleElements) {
244234
for (Operand *Use : Val->getUses()) {
245235
SILInstruction *User = Use->getUser();
246236
if (auto *UC = dyn_cast<UpcastInst>(User)) {
247237
// Upcast is transparent.
248-
if (!getObjectInitVals(UC, MemberStores, TailStores, NumTailTupleElements, FindStringCall))
238+
if (!getObjectInitVals(UC, MemberStores, TailStores, NumTailTupleElements))
249239
return false;
250240
} else if (auto *REA = dyn_cast<RefElementAddrInst>(User)) {
251241
// The address of a stored property.
@@ -255,7 +245,7 @@ bool ObjectOutliner::getObjectInitVals(SILValue Val,
255245
if (!isValidInitVal(SI->getSrc()) || MemberStores[REA->getField()])
256246
return false;
257247
MemberStores[REA->getField()] = SI;
258-
} else if (!isValidUseOfObject(ElemAddrUser, /*isCOWObject*/false)) {
248+
} else if (!isValidUseOfObject(ElemAddrUser)) {
259249
return false;
260250
}
261251
}
@@ -280,7 +270,7 @@ bool ObjectOutliner::getObjectInitVals(SILValue Val,
280270
return false;
281271
}
282272
}
283-
} else if (!isValidUseOfObject(User, /*isCOWObject*/false, FindStringCall)) {
273+
} else if (!isValidUseOfObject(User)) {
284274
return false;
285275
}
286276
}
@@ -305,7 +295,8 @@ class GlobalVariableMangler : public Mangle::ASTMangler {
305295
/// Try to convert an object allocation into a statically initialized object.
306296
///
307297
/// In general this works for any class, but in practice it will only kick in
308-
/// for array buffer objects. The use cases are array literals in a function.
298+
/// for copy-on-write buffers, like array buffer objects.
299+
/// The use cases are array literals in a function.
309300
/// For example:
310301
/// func getarray() -> [Int] {
311302
/// return [1, 2, 3]
@@ -314,6 +305,19 @@ bool ObjectOutliner::optimizeObjectAllocation(AllocRefInst *ARI) {
314305
if (ARI->isObjC())
315306
return false;
316307

308+
// Find the end_cow_mutation. Only for such COW buffer objects we do the
309+
// transformation.
310+
EndCOWMutationInst *endCOW = nullptr;
311+
for (Operand *use : ARI->getUses()) {
312+
if (auto *ecm = dyn_cast<EndCOWMutationInst>(use->getUser())) {
313+
if (endCOW)
314+
return false;
315+
endCOW = ecm;
316+
}
317+
}
318+
if (!endCOW || endCOW->doKeepUnique())
319+
return false;
320+
317321
// Check how many tail allocated elements are on the object.
318322
ArrayRef<Operand> TailCounts = ARI->getTailAllocatedCounts();
319323
SILType TailType;
@@ -363,11 +367,10 @@ bool ObjectOutliner::optimizeObjectAllocation(AllocRefInst *ARI) {
363367
}
364368

365369
TailStores.resize(NumStores);
366-
ApplyInst *FindStringCall = nullptr;
367370

368371
// Get the initialization stores of the object's properties and tail
369372
// allocated elements. Also check if there are any "bad" uses of the object.
370-
if (!getObjectInitVals(ARI, MemberStores, TailStores, NumTailTupleElems, &FindStringCall))
373+
if (!getObjectInitVals(ARI, MemberStores, TailStores, NumTailTupleElems))
371374
return false;
372375

373376
// Is there a store for all the class properties?
@@ -452,6 +455,12 @@ bool ObjectOutliner::optimizeObjectAllocation(AllocRefInst *ARI) {
452455
SILBuilder B(ARI);
453456
GlobalValueInst *GVI = B.createGlobalValue(ARI->getLoc(), Glob);
454457
B.createStrongRetain(ARI->getLoc(), GVI, B.getDefaultAtomicity());
458+
459+
ApplyInst *FindStringCall = findFindStringCall(endCOW);
460+
assert(endCOW->getOperand() == ARI);
461+
endCOW->replaceAllUsesWith(ARI);
462+
ToRemove.push_back(endCOW);
463+
455464
llvm::SmallVector<Operand *, 8> Worklist(ARI->use_begin(), ARI->use_end());
456465
while (!Worklist.empty()) {
457466
auto *Use = Worklist.pop_back_val();

0 commit comments

Comments
 (0)