Skip to content

Commit cdae12c

Browse files
committed
ARCAnalysis: fix canNeverUseObject to correctly handle builtins.
This analysis helper was inverting the result for builtins. Builtins such as "copyMemory" were treated as never using a value. This manifested in a crash in TestFoundation. NSDictionary's initializer released the incoming array before copying it. This crashed later during dictionary destruction. The crash was hidden by a secondary bug in mayHaveSymmetricInterference that effectively ignored the result from canNeverUseValue. Rename the helper to canUseObject, invert the result for builtins, and fix mayHaveSymmetricInterference to respect the result of canUseObject. Note that instructions that cannot access a referenced object obviously cannot not "interfere" with a release. Fixing these bugs now allows ARC optimization around dealloc_stack and other operations that don't care about the reference count.
1 parent f602a75 commit cdae12c

File tree

5 files changed

+88
-36
lines changed

5 files changed

+88
-36
lines changed

include/swift/SILOptimizer/Analysis/ARCAnalysis.h

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,12 +72,13 @@ bool mustGuaranteedUseValue(SILInstruction *User, SILValue Ptr,
7272
/// Returns true if \p Inst can never conservatively decrement reference counts.
7373
bool canNeverDecrementRefCounts(SILInstruction *Inst);
7474

75-
/// \returns True if \p User can never use a value in a way that requires the
76-
/// value to be alive.
75+
/// Returns true if \p Inst may access any indirect object either via an address
76+
/// or reference.
7777
///
78-
/// This is purposefully a negative query to contrast with canUseValue which is
79-
/// about a specific value while this is about general values.
80-
bool canNeverUseValues(SILInstruction *User);
78+
/// If false is returned and \p Inst has an address or reference type operand,
79+
/// then \p Inst only operates on the value of the address itself, not the
80+
/// memory. i.e. it does not dereference the address.
81+
bool canUseObject(SILInstruction *Inst);
8182

8283
/// \returns true if the user \p User may use \p Ptr in a manner that requires
8384
/// Ptr's life to be guaranteed to exist at this point.

lib/SILOptimizer/ARC/ARCRegionState.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -531,7 +531,9 @@ void ARCRegionState::summarizeBlock(SILBasicBlock *BB) {
531531
SummarizedInterestingInsts.clear();
532532

533533
for (auto &I : *BB)
534-
if (!canNeverUseValues(&I) || I.mayReleaseOrReadRefCount() ||
534+
// FIXME: mayReleaseOrReadRefCount should be a strict subset of
535+
// canUseObject. If not, there is a bug in canUseObject.
536+
if (canUseObject(&I) || I.mayReleaseOrReadRefCount() ||
535537
isStrongEntranceInstruction(I))
536538
SummarizedInterestingInsts.push_back(&I);
537539
}

lib/SILOptimizer/Analysis/ARCAnalysis.cpp

Lines changed: 37 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ bool swift::mayDecrementRefCount(SILInstruction *User,
9191
// Use Analysis
9292
//===----------------------------------------------------------------------===//
9393

94-
/// Returns true if a builtin apply cannot use reference counted values.
94+
/// Returns true if a builtin apply can use reference counted values.
9595
///
9696
/// The main case that this handles here are builtins that via read none imply
9797
/// that they cannot read globals and at the same time do not take any
@@ -106,28 +106,33 @@ static bool canApplyOfBuiltinUseNonTrivialValues(BuiltinInst *BInst) {
106106
if (II.hasAttribute(llvm::Attribute::ReadNone)) {
107107
for (auto &Op : BInst->getAllOperands()) {
108108
if (!Op.get()->getType().isTrivial(*F)) {
109-
return false;
109+
return true;
110110
}
111111
}
112112
}
113113

114-
return true;
114+
return false;
115115
}
116116

117117
auto &BI = BInst->getBuiltinInfo();
118-
if (BI.isReadNone()) {
119-
for (auto &Op : BInst->getAllOperands()) {
120-
if (!Op.get()->getType().isTrivial(*F)) {
121-
return false;
122-
}
118+
if (!BI.isReadNone())
119+
return true;
120+
121+
for (auto &Op : BInst->getAllOperands()) {
122+
if (!Op.get()->getType().isTrivial(*F)) {
123+
return true;
123124
}
124125
}
125-
126-
return true;
126+
return false;
127127
}
128128

129-
/// Returns true if Inst is a function that we know never uses ref count values.
130-
bool swift::canNeverUseValues(SILInstruction *Inst) {
129+
/// Returns true if \p Inst may access any indirect object either via an address
130+
/// or reference.
131+
///
132+
/// If these instructions do have an address or reference type operand, then
133+
/// they only operate on the value of the address itself, not the
134+
/// memory. i.e. they don't dereference the address.
135+
bool swift::canUseObject(SILInstruction *Inst) {
131136
switch (Inst->getKind()) {
132137
// These instructions do not use other values.
133138
case SILInstructionKind::FunctionRefInst:
@@ -142,33 +147,36 @@ bool swift::canNeverUseValues(SILInstruction *Inst) {
142147
case SILInstructionKind::AllocBoxInst:
143148
case SILInstructionKind::MetatypeInst:
144149
case SILInstructionKind::WitnessMethodInst:
145-
return true;
150+
return false;
146151

147152
// DeallocStackInst do not use reference counted values.
148153
case SILInstructionKind::DeallocStackInst:
149-
return true;
154+
return false;
150155

151156
// Debug values do not use referenced counted values in a manner we care
152157
// about.
153158
case SILInstructionKind::DebugValueInst:
154159
case SILInstructionKind::DebugValueAddrInst:
155-
return true;
160+
return false;
156161

157162
// Casts do not use pointers in a manner that we care about since we strip
158163
// them during our analysis. The reason for this is if the cast is not dead
159164
// then there must be some other use after the cast that we will protect if a
160165
// release is not in between the cast and the use.
166+
//
167+
// Note: UncheckedRefCastAddrInst moves a reference into a new object. While
168+
// the net reference count should be zero, there's no guarantee it won't
169+
// access the object.
161170
case SILInstructionKind::UpcastInst:
162171
case SILInstructionKind::AddressToPointerInst:
163172
case SILInstructionKind::PointerToAddressInst:
164173
case SILInstructionKind::UncheckedRefCastInst:
165-
case SILInstructionKind::UncheckedRefCastAddrInst:
166174
case SILInstructionKind::UncheckedAddrCastInst:
167175
case SILInstructionKind::RefToRawPointerInst:
168176
case SILInstructionKind::RawPointerToRefInst:
169177
case SILInstructionKind::UnconditionalCheckedCastInst:
170178
case SILInstructionKind::UncheckedBitwiseCastInst:
171-
return true;
179+
return false;
172180

173181
// If we have a trivial bit cast between trivial types, it is not something
174182
// that can use ref count ops in a way we care about. We do need to be careful
@@ -183,7 +191,7 @@ bool swift::canNeverUseValues(SILInstruction *Inst) {
183191
// safe.
184192
case SILInstructionKind::UncheckedTrivialBitCastInst: {
185193
SILValue Op = cast<UncheckedTrivialBitCastInst>(Inst)->getOperand();
186-
return Op->getType().isTrivial(*Inst->getFunction());
194+
return !Op->getType().isTrivial(*Inst->getFunction());
187195
}
188196

189197
// Typed GEPs do not use pointers. The user of the typed GEP may but we will
@@ -198,18 +206,18 @@ bool swift::canNeverUseValues(SILInstruction *Inst) {
198206
case SILInstructionKind::UncheckedEnumDataInst:
199207
case SILInstructionKind::IndexAddrInst:
200208
case SILInstructionKind::IndexRawPointerInst:
201-
return true;
209+
return false;
202210

203211
// Aggregate formation by themselves do not create new uses since it is their
204212
// users that would create the appropriate uses.
205213
case SILInstructionKind::EnumInst:
206214
case SILInstructionKind::StructInst:
207215
case SILInstructionKind::TupleInst:
208-
return true;
216+
return false;
209217

210218
// Only uses non reference counted values.
211219
case SILInstructionKind::CondFailInst:
212-
return true;
220+
return false;
213221

214222
case SILInstructionKind::BuiltinInst: {
215223
auto *BI = cast<BuiltinInst>(Inst);
@@ -221,9 +229,9 @@ bool swift::canNeverUseValues(SILInstruction *Inst) {
221229
// dead, LLVM will clean it up.
222230
case SILInstructionKind::BranchInst:
223231
case SILInstructionKind::CondBranchInst:
224-
return true;
225-
default:
226232
return false;
233+
default:
234+
return true;
227235
}
228236
}
229237

@@ -268,14 +276,15 @@ static bool canTerminatorUseValue(TermInst *TI, SILValue Ptr,
268276

269277

270278
bool swift::mayHaveSymmetricInterference(SILInstruction *User, SILValue Ptr, AliasAnalysis *AA) {
279+
// If Inst is an instruction that we know can never use values with reference
280+
// semantics, return true. Check this before AliasAnalysis because some memory
281+
// operations, like dealloc_stack, don't use ref counted values.
282+
if (!canUseObject(User))
283+
return false;
284+
271285
// Check whether releasing this value can call deinit and interfere with User.
272286
if (AA->mayValueReleaseInterfereWithInstruction(User, Ptr))
273287
return true;
274-
275-
// If Inst is an instruction that we know can never use values with reference
276-
// semantics, return true.
277-
if (canNeverUseValues(User))
278-
return false;
279288

280289
// If the user is a load or a store and we can prove that it does not access
281290
// the object then return true.

test/SILOptimizer/existential_transform.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -301,8 +301,8 @@ func wrap_gcp<T:GP>(_ a:T,_ b:GP) -> Int {
301301
// CHECK: strong_retain
302302
// CHECK: apply
303303
// CHECK: destroy_addr
304-
// CHECK: dealloc_stack
305304
// CHECK: strong_release
305+
// CHECK: dealloc_stack
306306
// CHECK: return
307307
// CHECK: } // end sil function '$s21existential_transform3gcpySixAA2GPRzlF'
308308
@inline(never) func gcp<T:GP>(_ a:T) -> Int {
@@ -328,8 +328,8 @@ func wrap_gcp_arch<T:GP>(_ a:T,_ b:GP, _ c:inout Array<T>) -> Int {
328328
// CHECK: strong_retain
329329
// CHECK: apply
330330
// CHECK: destroy_addr
331-
// CHECK: dealloc_stack
332331
// CHECK: strong_release
332+
// CHECK: dealloc_stack
333333
// CHECK: return
334334
// CHECK-LABEL: } // end sil function '$s21existential_transform8gcp_archySix_SayxGztAA2GPRzlF'
335335
@inline(never) func gcp_arch<T:GP>(_ a:T, _ b:inout Array<T>) -> Int {

test/SILOptimizer/retain_release_code_motion.sil

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -718,3 +718,43 @@ bb2:
718718
return %9999 : $()
719719
}
720720

721+
// Hoist releases above dealloc_stack
722+
// CHECK-LABEL: sil @testReleaseHoistDeallocStack : $@convention(thin) (AnyObject) -> () {
723+
// CHECK: bb0(%0 : $AnyObject):
724+
// CHECK-NOT: retain
725+
// CHECK: [[A:%.*]] = alloc_stack $Int64
726+
// CHECK-NEXT dealloc_stack [[A]] : $*Int64
727+
// CHECK-NOT: release
728+
// CHECK-LABEL: } // end sil function 'testReleaseHoistDeallocStack'
729+
sil @testReleaseHoistDeallocStack : $@convention(thin) (AnyObject)->() {
730+
bb0(%0 : $AnyObject):
731+
strong_retain %0 : $AnyObject
732+
%alloc = alloc_stack $Int64
733+
dealloc_stack %alloc : $*Int64
734+
strong_release %0 : $AnyObject
735+
%34 = tuple ()
736+
return %34 : $()
737+
}
738+
739+
// Do not hoist releases above builtins that operate on object references.
740+
//
741+
// CHECK-RELEASE-HOISTING-LABEL: sil @testCopyArray : $@convention(thin) (_ContiguousArrayBuffer<AnyObject>, Builtin.Word, Builtin.Word) -> Builtin.RawPointer {
742+
// CHECK-RELEASE-HOISTING: bb0(%0 : $_ContiguousArrayBuffer<AnyObject>, %1 : $Builtin.Word, %2 : $Builtin.Word):
743+
// CHECK-RELEASE-HOISTING: builtin "copyArray"<AnyObject>
744+
// CHECK-RELEASE-HOISTING: release_value %0 : $_ContiguousArrayBuffer<AnyObject>
745+
// CHECK-RELEASE-HOISTING-LABEL: } // end sil function 'testCopyArray'
746+
sil @testCopyArray : $@convention(thin) (_ContiguousArrayBuffer<AnyObject>, Builtin.Word, Builtin.Word) -> Builtin.RawPointer {
747+
bb0(%0 : $_ContiguousArrayBuffer<AnyObject>, %1 : $Builtin.Word, %2 : $Builtin.Word):
748+
%eltty = metatype $@thick AnyObject.Protocol
749+
%newptr = builtin "allocRaw"(%1 : $Builtin.Word, %1 : $Builtin.Word) : $Builtin.RawPointer
750+
bind_memory %newptr : $Builtin.RawPointer, %1 : $Builtin.Word to $*AnyObject
751+
%storage = struct_extract %0 : $_ContiguousArrayBuffer<AnyObject>, #_ContiguousArrayBuffer._storage
752+
%elements = ref_tail_addr %storage : $__ContiguousArrayStorageBase, $AnyObject
753+
%eltptr = address_to_pointer %elements : $*AnyObject to $Builtin.RawPointer
754+
%objptr = struct $UnsafePointer<AnyObject> (%eltptr : $Builtin.RawPointer)
755+
%ptrdep = mark_dependence %objptr : $UnsafePointer<AnyObject> on %storage : $__ContiguousArrayStorageBase
756+
%rawptr = struct_extract %ptrdep : $UnsafePointer<AnyObject>, #UnsafePointer._rawValue
757+
%copy = builtin "copyArray"<AnyObject>(%eltty : $@thick AnyObject.Protocol, %newptr : $Builtin.RawPointer, %rawptr : $Builtin.RawPointer, %1 : $Builtin.Word) : $()
758+
release_value %0 : $_ContiguousArrayBuffer<AnyObject>
759+
return %newptr : $Builtin.RawPointer
760+
}

0 commit comments

Comments
 (0)