Skip to content

Commit 5983aae

Browse files
committed
Fix AccessEnforcementReleaseSinking. Check for illegal cases.
In the included, test case, the optimization was sinking releases past is_escaping_closure. Rewrite the isBarrier logic to be conservative and define the mayCheckRefCount property in SIL/InstructionUtils. Properties that may need to be updated when SIL changes belong there. Note that it is particularly bad behavior if the presence of access markers in the code cause miscompiles unrelated to access enforcement. Fixes <rdar://problem/45846920> TestFoundation, TestProcess, closure argument passed as @NoEscape to Objective-C has escaped.
1 parent fcff56d commit 5983aae

File tree

7 files changed

+144
-17
lines changed

7 files changed

+144
-17
lines changed

include/swift/SIL/InstructionUtils.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@
1717

1818
namespace swift {
1919

20+
//===----------------------------------------------------------------------===//
21+
// SSA Use-Def Helpers
22+
//===----------------------------------------------------------------------===//
23+
2024
/// Strip off casts/indexing insts/address projections from V until there is
2125
/// nothing left to strip.
2226
SILValue getUnderlyingObject(SILValue V);
@@ -78,6 +82,10 @@ SILValue stripExpectIntrinsic(SILValue V);
7882
/// ust return V.
7983
SILValue stripBorrow(SILValue V);
8084

85+
//===----------------------------------------------------------------------===//
86+
// Instruction Properties
87+
//===----------------------------------------------------------------------===//
88+
8189
/// Return a non-null SingleValueInstruction if the given instruction merely
8290
/// copies the value of its first operand, possibly changing its type or
8391
/// ownership state, but otherwise having no effect.
@@ -110,6 +118,10 @@ bool isIncidentalUse(SILInstruction *user);
110118
/// only used in recognizable patterns without otherwise "escaping".
111119
bool onlyAffectsRefCount(SILInstruction *user);
112120

121+
/// Returns true if the given user instruction checks the ref count of a
122+
/// pointer.
123+
bool mayCheckRefCount(SILInstruction *User);
124+
113125
/// Return true when the instruction represents added instrumentation for
114126
/// run-time sanitizers.
115127
bool isSanitizerInstrumentation(SILInstruction *Instruction);

include/swift/SILOptimizer/Analysis/ARCAnalysis.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,6 @@ bool isReleaseInstruction(SILInstruction *II);
5151
bool mayDecrementRefCount(SILInstruction *User, SILValue Ptr,
5252
AliasAnalysis *AA);
5353

54-
/// \returns True if the user \p User checks the ref count of a pointer.
55-
bool mayCheckRefCount(SILInstruction *User);
56-
5754
/// \returns True if the \p User might use the pointer \p Ptr in a manner that
5855
/// requires \p Ptr to be alive before Inst or the release of Ptr may use memory
5956
/// accessed by \p User.

lib/SIL/InstructionUtils.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,10 @@ bool swift::onlyAffectsRefCount(SILInstruction *user) {
296296
}
297297
}
298298

299+
bool swift::mayCheckRefCount(SILInstruction *User) {
300+
return isa<IsUniqueInst>(User) || isa<IsEscapingClosureInst>(User);
301+
}
302+
299303
bool swift::isSanitizerInstrumentation(SILInstruction *Instruction) {
300304
auto *BI = dyn_cast<BuiltinInst>(Instruction);
301305
if (!BI)

lib/SILOptimizer/Analysis/ARCAnalysis.cpp

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -87,11 +87,6 @@ bool swift::mayDecrementRefCount(SILInstruction *User,
8787
return true;
8888
}
8989

90-
bool swift::mayCheckRefCount(SILInstruction *User) {
91-
return isa<IsUniqueInst>(User) ||
92-
isa<IsEscapingClosureInst>(User);
93-
}
94-
9590
//===----------------------------------------------------------------------===//
9691
// Use Analysis
9792
//===----------------------------------------------------------------------===//

lib/SILOptimizer/Transforms/ARCCodeMotion.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,14 @@
7070
//===----------------------------------------------------------------------===//
7171

7272
#define DEBUG_TYPE "sil-rr-code-motion"
73+
#include "swift/SIL/InstructionUtils.h"
7374
#include "swift/SIL/SILBuilder.h"
7475
#include "swift/SILOptimizer/Analysis/ARCAnalysis.h"
7576
#include "swift/SILOptimizer/Analysis/AliasAnalysis.h"
7677
#include "swift/SILOptimizer/Analysis/EscapeAnalysis.h"
7778
#include "swift/SILOptimizer/Analysis/PostOrderAnalysis.h"
78-
#include "swift/SILOptimizer/Analysis/RCIdentityAnalysis.h"
7979
#include "swift/SILOptimizer/Analysis/ProgramTerminationAnalysis.h"
80+
#include "swift/SILOptimizer/Analysis/RCIdentityAnalysis.h"
8081
#include "swift/SILOptimizer/PassManager/Passes.h"
8182
#include "swift/SILOptimizer/PassManager/Transforms.h"
8283
#include "swift/SILOptimizer/Utils/CFG.h"

lib/SILOptimizer/Transforms/AccessEnforcementReleaseSinking.cpp

Lines changed: 61 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727

2828
#include "swift/SIL/ApplySite.h"
2929
#include "swift/SIL/DebugUtils.h"
30+
#include "swift/SIL/InstructionUtils.h"
3031
#include "swift/SIL/SILFunction.h"
3132
#include "swift/SILOptimizer/PassManager/Transforms.h"
3233

@@ -47,15 +48,67 @@ static bool isSinkable(SILInstruction &inst) {
4748
}
4849

4950
// Returns a bool: If this is a "barrier" instruction for this opt
50-
static bool isBarrier(SILInstruction &inst) {
51-
switch (inst.getKind()) {
52-
default:
53-
return FullApplySite::isa(&inst) != FullApplySite();
54-
case SILInstructionKind::BeginAccessInst:
55-
case SILInstructionKind::BeginUnpairedAccessInst:
56-
case SILInstructionKind::IsUniqueInst:
51+
static bool isBarrier(SILInstruction *inst) {
52+
// Calls hide many dangers, from checking reference counts, to beginning
53+
// keypath access, to forcing memory to be live. Checking for these and other
54+
// possible barries at ever call is certainly not worth it.
55+
if (FullApplySite::isa(inst) != FullApplySite())
56+
return true;
57+
58+
// Don't extend lifetime past any sort of uniqueness check.
59+
if (mayCheckRefCount(inst))
5760
return true;
61+
62+
// Don't extend object lifetime past deallocation.
63+
if (isa<DeallocationInst>(inst))
64+
return true;
65+
66+
// Avoid introducing access conflicts.
67+
if (isa<BeginAccessInst>(inst) || isa<BeginUnpairedAccessInst>(inst))
68+
return true;
69+
70+
if (auto *BI = dyn_cast<BuiltinInst>(inst)) {
71+
auto kind = BI->getBuiltinKind();
72+
if (!kind)
73+
return false; // LLVM intrinsics are not barriers.
74+
75+
// Whitelist the safe builtin categories. Builtins should generally be
76+
// treated conservatively, because introducing a new builtin does not
77+
// require updating all passes to be aware of it. Avoid a default to ensure
78+
// that all categories are covered.
79+
switch (kind.getValue()) {
80+
case BuiltinValueKind::None:
81+
llvm_unreachable("Builtin must has a non-empty kind.");
82+
#define BUILTIN_NO_BARRIER(Id) \
83+
case BuiltinValueKind::Id: \
84+
return false;
85+
#define BUILTIN_CAST_OPERATION(Id, Name, Attrs) BUILTIN_NO_BARRIER(Id)
86+
#define BUILTIN_CAST_OR_BITCAST_OPERATION(Id, Name, Attrs) \
87+
BUILTIN_NO_BARRIER(Id)
88+
#define BUILTIN_BINARY_OPERATION(Id, Name, Attrs, Overload) \
89+
BUILTIN_NO_BARRIER(Id)
90+
#define BUILTIN_BINARY_OPERATION_WITH_OVERFLOW(Id, Name, UncheckedID, Attrs, \
91+
Overload) \
92+
BUILTIN_NO_BARRIER(Id)
93+
#define BUILTIN_UNARY_OPERATION(Id, Name, Attrs, Overload) \
94+
BUILTIN_NO_BARRIER(Id)
95+
#define BUILTIN_BINARY_PREDICATE(Id, Name, Attrs, Overload) \
96+
BUILTIN_NO_BARRIER(Id)
97+
#define BUILTIN_SIL_OPERATION(Id, Name, Overload) \
98+
case BuiltinValueKind::Id: \
99+
llvm_unreachable("SIL operation must be lowered to instructions.");
100+
#define BUILTIN_RUNTIME_CALL(Id, Name, Attrs) \
101+
case BuiltinValueKind::Id: \
102+
return true; // A runtime call could be anything.
103+
#define BUILTIN_MISC_OPERATION(Id, Name, Attrs, Overload) BUILTIN_NO_BARRIER(Id)
104+
#define BUILTIN_SANITIZER_OPERATION(Id, Name, Attrs) BUILTIN_NO_BARRIER(Id)
105+
#define BUILTIN_TYPE_CHECKER_OPERATION(Id, Name) BUILTIN_NO_BARRIER(Id)
106+
#define BUILTIN_TYPE_TRAIT_OPERATION(Id, Name) BUILTIN_NO_BARRIER(Id)
107+
108+
#include "swift/AST/Builtins.def"
109+
}
58110
}
111+
return false;
59112
}
60113

61114
// Processes a block bottom-up, keeping a lookout for end_access instructions
@@ -70,7 +123,7 @@ static void processBlock(SILBasicBlock &block) {
70123
if (!bottomEndAccessInst) {
71124
bottomEndAccessInst = currEAI;
72125
}
73-
} else if (isBarrier(currIns)) {
126+
} else if (isBarrier(&currIns)) {
74127
LLVM_DEBUG(llvm::dbgs() << "Found a barrier " << currIns
75128
<< ", clearing last seen end_access\n");
76129
bottomEndAccessInst = nullptr;

test/SILOptimizer/access_sink.sil

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,3 +215,68 @@ bb0:
215215
%ret = tuple ()
216216
return %ret : $()
217217
}
218+
219+
// testSinkAfterEscapingClosureCheck:
220+
// <rdar://problem/45846920> TestFoundation, TestProcess, closure
221+
// argument passed as @noescape to Objective-C has escaped
222+
class IntWrapper {
223+
var value: Builtin.Int64
224+
225+
init(v: Builtin.Int64) {
226+
value = v
227+
}
228+
}
229+
230+
sil @takesNoEscapeBlockClosure : $@convention(thin) (@guaranteed @convention(block) @noescape () -> ()) -> ()
231+
232+
sil @closureThatModifiesCapture: $@convention(thin) ({ var Builtin.Int64 }) -> ()
233+
234+
sil [reabstraction_thunk] @thunkForCalleeGuaranteed : $@convention(c) (@inout_aliasable @block_storage @callee_guaranteed () -> ()) -> ()
235+
sil [reabstraction_thunk] @withoutActuallyEscapingThunk : $@convention(thin) (@noescape @callee_guaranteed () -> ()) -> ()
236+
237+
// The Copy release cannot be moved below the is_escaping_closure check.
238+
//
239+
// CHECK-LABEL: sil @testSinkAfterEscapingClosureCheck : $@convention(thin) (@guaranteed IntWrapper) -> () {
240+
// CHECK: bb0(%0 : $IntWrapper):
241+
// CHECK: [[BA:%.*]] = begin_access [read] [dynamic] %{{.*}} : $*Builtin.Int64
242+
// CHECK: [[COPY:%.*]] = copy_block %{{.*}} : $@convention(block) @noescape () -> ()
243+
// CHECK: [[F:%.*]] = function_ref @takesNoEscapeBlockClosure : $@convention(thin) (@guaranteed @convention(block) @noescape () -> ()) -> ()
244+
// CHECK: apply [[F]]([[COPY]]) : $@convention(thin) (@guaranteed @convention(block) @noescape () -> ()) -> ()
245+
// CHECK: strong_release [[COPY]] : $@convention(block) @noescape () -> ()
246+
// CHECK: is_escaping_closure
247+
// CHECK: cond_fail
248+
// CHECK: end_access [[BA]] : $*Builtin.Int64
249+
// CHECK-LABEL: } // end sil function 'testSinkAfterEscapingClosureCheck'
250+
sil @testSinkAfterEscapingClosureCheck : $@convention(thin) (@guaranteed IntWrapper) -> () {
251+
bb0(%0 : $IntWrapper):
252+
%va = ref_element_addr %0 : $IntWrapper, #IntWrapper.value
253+
%ba = begin_access [read] [dynamic] %va : $*Builtin.Int64
254+
%value = load %ba : $*Builtin.Int64
255+
%box = alloc_box ${ var Builtin.Int64 }
256+
%boxaddr = project_box %box : ${ var Builtin.Int64 }, 0
257+
store %value to %boxaddr : $*Builtin.Int64
258+
%closureF = function_ref @closureThatModifiesCapture : $@convention(thin) ({ var Builtin.Int64 }) -> ()
259+
%closure = partial_apply [callee_guaranteed] %closureF(%box) : $@convention(thin) ({ var Builtin.Int64 }) -> ()
260+
%conv = convert_escape_to_noescape %closure : $@callee_guaranteed () -> () to $@noescape @callee_guaranteed () -> ()
261+
%thunk = function_ref @withoutActuallyEscapingThunk : $@convention(thin) (@noescape @callee_guaranteed () -> ()) -> ()
262+
%pathunk = partial_apply [callee_guaranteed] %thunk(%conv) : $@convention(thin) (@noescape @callee_guaranteed () -> ()) -> ()
263+
%md = mark_dependence %pathunk : $@callee_guaranteed () -> () on %conv : $@noescape @callee_guaranteed () -> ()
264+
strong_retain %md : $@callee_guaranteed () -> ()
265+
%allocblock = alloc_stack $@block_storage @callee_guaranteed () -> ()
266+
%blockaddr = project_block_storage %allocblock : $*@block_storage @callee_guaranteed () -> ()
267+
store %md to %blockaddr : $*@callee_guaranteed () -> ()
268+
%blockF = function_ref @thunkForCalleeGuaranteed : $@convention(c) (@inout_aliasable @block_storage @callee_guaranteed () -> ()) -> ()
269+
%initblock = init_block_storage_header %allocblock : $*@block_storage @callee_guaranteed () -> (), invoke %blockF : $@convention(c) (@inout_aliasable @block_storage @callee_guaranteed () -> ()) -> (), type $@convention(block) @noescape () -> ()
270+
%copyblock = copy_block %initblock : $@convention(block) @noescape () -> ()
271+
%f = function_ref @takesNoEscapeBlockClosure : $@convention(thin) (@guaranteed @convention(block) @noescape () -> ()) -> ()
272+
%call = apply %f(%copyblock) : $@convention(thin) (@guaranteed @convention(block) @noescape () -> ()) -> ()
273+
strong_release %copyblock : $@convention(block) @noescape () -> ()
274+
%isescape = is_escaping_closure %md : $@callee_guaranteed () -> ()
275+
cond_fail %isescape : $Builtin.Int1
276+
end_access %ba : $*Builtin.Int64
277+
destroy_addr %blockaddr : $*@callee_guaranteed() -> ()
278+
dealloc_stack %allocblock : $*@block_storage @callee_guaranteed () -> ()
279+
strong_release %box : ${ var Builtin.Int64 }
280+
%ret = tuple ()
281+
return %ret : $()
282+
}

0 commit comments

Comments
 (0)