Skip to content

Commit 9870324

Browse files
authored
Merge pull request swiftlang#20367 from atrick/fix-access-sink
Fix AccessEnforcementReleaseSinking. Check for illegal cases.
2 parents de1b409 + 5983aae commit 9870324

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)