Skip to content

Commit 272eb46

Browse files
authored
Merge pull request #8798 from devincoughlin/static-access-enforcement-swap-suppression
2 parents 8fc5a66 + 8d180f4 commit 272eb46

File tree

3 files changed

+158
-1
lines changed

3 files changed

+158
-1
lines changed

include/swift/AST/KnownDecls.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,4 +70,6 @@ FUNC_DECL(GetErrorEmbeddedNSError, "_stdlib_getErrorEmbeddedNSError")
7070

7171
FUNC_DECL(UnsafeBitCast, "unsafeBitCast")
7272

73+
FUNC_DECL(Swap, "swap")
74+
7375
#undef FUNC_DECL

lib/SILOptimizer/Mandatory/DiagnoseStaticExclusivity.cpp

Lines changed: 108 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@
2525

2626
#define DEBUG_TYPE "static-exclusivity"
2727
#include "swift/AST/DiagnosticsSIL.h"
28+
#include "swift/AST/ASTWalker.h"
29+
#include "swift/AST/Decl.h"
30+
#include "swift/AST/Expr.h"
31+
#include "swift/AST/Stmt.h"
2832
#include "swift/SIL/CFG.h"
2933
#include "swift/SIL/SILInstruction.h"
3034
#include "swift/SILOptimizer/Analysis/PostOrderAnalysis.h"
@@ -163,6 +167,12 @@ enum class ExclusiveOrShared_t : unsigned {
163167
/// Tracks the in-progress accesses on per-storage-location basis.
164168
using StorageMap = llvm::SmallDenseMap<AccessedStorage, AccessInfo, 4>;
165169

170+
/// A pair of 'begin_access' instructions that conflict.
171+
struct ConflictingAccess {
172+
const BeginAccessInst *FirstAccess;
173+
const BeginAccessInst *SecondAccess;
174+
};
175+
166176
} // end anonymous namespace
167177

168178
namespace llvm {
@@ -214,6 +224,58 @@ static ExclusiveOrShared_t getRequiredAccess(const BeginAccessInst *BAI) {
214224
return ExclusiveOrShared_t::ExclusiveAccess;
215225
}
216226

227+
/// Perform a syntactic suppression that returns true when the accesses are for
228+
/// inout arguments to a call that corresponds to to one of the passed-in
229+
/// 'apply' instructions.
230+
static bool
231+
isConflictOnInoutArgumentsToSuppressed(const BeginAccessInst *Access1,
232+
const BeginAccessInst *Access2,
233+
ArrayRef<ApplyInst *> CallsToSuppress) {
234+
if (CallsToSuppress.empty())
235+
return false;
236+
237+
// Inout arguments must be modifications.
238+
if (Access1->getAccessKind() != SILAccessKind::Modify ||
239+
Access2->getAccessKind() != SILAccessKind::Modify) {
240+
return false;
241+
}
242+
243+
SILLocation Loc1 = Access1->getLoc();
244+
SILLocation Loc2 = Access2->getLoc();
245+
if (Loc1.isNull() || Loc2.isNull())
246+
return false;
247+
248+
auto *InOut1 = Loc1.getAsASTNode<InOutExpr>();
249+
auto *InOut2 = Loc2.getAsASTNode<InOutExpr>();
250+
if (!InOut1 || !InOut2)
251+
return false;
252+
253+
for (ApplyInst *AI : CallsToSuppress) {
254+
SILLocation CallLoc = AI->getLoc();
255+
if (CallLoc.isNull())
256+
continue;
257+
258+
auto *CE = CallLoc.getAsASTNode<CallExpr>();
259+
if (!CE)
260+
continue;
261+
262+
bool FoundTarget = false;
263+
CE->forEachChildExpr([=,&FoundTarget](Expr *Child) {
264+
if (Child == InOut1) {
265+
FoundTarget = true;
266+
// Stops the traversal.
267+
return (Expr *)nullptr;
268+
}
269+
return Child;
270+
}
271+
);
272+
if (FoundTarget)
273+
return true;
274+
}
275+
276+
return false;
277+
}
278+
217279
/// Emits a diagnostic if beginning an access with the given in-progress
218280
/// accesses violates the law of exclusivity. Returns true when a
219281
/// diagnostic was emitted.
@@ -256,6 +318,23 @@ static AccessedStorage findAccessedStorage(SILValue Source) {
256318
}
257319
}
258320

321+
/// Returns true when the apply calls the Standard Library swap().
322+
/// Used for diagnostic suppression.
323+
bool isCallToStandardLibrarySwap(ApplyInst *AI, ASTContext &Ctx) {
324+
SILFunction *SF = AI->getReferencedFunction();
325+
if (!SF)
326+
return false;
327+
328+
if (!SF->hasLocation())
329+
return false;
330+
331+
auto *FD = SF->getLocation().getAsASTNode<FuncDecl>();
332+
if (!FD)
333+
return false;
334+
335+
return FD == Ctx.getSwap(nullptr);
336+
}
337+
259338
static void checkStaticExclusivity(SILFunction &Fn, PostOrderFunctionInfo *PO) {
260339
// The implementation relies on the following SIL invariants:
261340
// - All incoming edges to a block must have the same in-progress
@@ -275,6 +354,15 @@ static void checkStaticExclusivity(SILFunction &Fn, PostOrderFunctionInfo *PO) {
275354
if (Fn.empty())
276355
return;
277356

357+
// Collects calls to functions for which diagnostics about conflicting inout
358+
// arguments should be suppressed.
359+
llvm::SmallVector<ApplyInst *, 8> CallsToSuppress;
360+
361+
// Stores the accesses that have been found to conflict. Used to defer
362+
// emitting diagnostics until we can determine whether they should
363+
// be suppressed.
364+
llvm::SmallVector<ConflictingAccess, 4> ConflictingAccesses;
365+
278366
// For each basic block, track the stack of current accesses on
279367
// exit from that block.
280368
llvm::SmallDenseMap<SILBasicBlock *, Optional<StorageMap>, 32>
@@ -316,7 +404,7 @@ static void checkStaticExclusivity(SILFunction &Fn, PostOrderFunctionInfo *PO) {
316404
if (Info.conflictsWithAccess(Kind) && !Info.alreadyHadConflict()) {
317405
const BeginAccessInst *Conflict = Info.getFirstAccess();
318406
assert(Conflict && "Must already have had access to conflict!");
319-
diagnoseExclusivityViolation(Conflict, BAI, Fn.getASTContext());
407+
ConflictingAccesses.push_back({ Conflict, BAI });
320408
}
321409

322410
Info.beginAccess(BAI);
@@ -335,12 +423,31 @@ static void checkStaticExclusivity(SILFunction &Fn, PostOrderFunctionInfo *PO) {
335423
continue;
336424
}
337425

426+
if (auto *AI = dyn_cast<ApplyInst>(&I)) {
427+
// Suppress for the arguments to the Standard Library's swap()
428+
// function until we can recommend a safe alternative.
429+
if (isCallToStandardLibrarySwap(AI, Fn.getASTContext()))
430+
CallsToSuppress.push_back(AI);
431+
}
432+
338433
if (isa<ReturnInst>(&I)) {
339434
// Sanity check to make sure entries are properly removed.
340435
assert(Accesses.size() == 0);
341436
}
342437
}
343438
}
439+
440+
// Now that we've collected violations and suppressed calls, emit
441+
// diagnostics.
442+
for (auto &Violation : ConflictingAccesses) {
443+
const BeginAccessInst *PriorAccess = Violation.FirstAccess;
444+
const BeginAccessInst *NewAccess = Violation.SecondAccess;
445+
if (isConflictOnInoutArgumentsToSuppressed(PriorAccess, NewAccess,
446+
CallsToSuppress))
447+
continue;
448+
449+
diagnoseExclusivityViolation(PriorAccess, NewAccess, Fn.getASTContext());
450+
}
344451
}
345452

346453
namespace {
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// RUN: %target-swift-frontend -enforce-exclusivity=checked -emit-sil -primary-file %s -o /dev/null -verify
2+
3+
import Swift
4+
5+
func takesTwoInouts(_ p1: inout Int, _ p2: inout Int) { }
6+
7+
func simpleInoutDiagnostic() {
8+
var i = 7
9+
10+
// expected-warning@+2{{modification requires exclusive access}}
11+
// expected-note@+1{{conflicting modification requires exclusive access}}
12+
takesTwoInouts(&i, &i)
13+
}
14+
15+
16+
func swapSuppression(_ i: Int, _ j: Int) {
17+
var a: [Int] = [1, 2, 3]
18+
19+
swap(&a[i], &a[j]) // no-warning
20+
21+
// expected-warning@+2{{modification requires exclusive access}}
22+
// expected-note@+1{{conflicting modification requires exclusive access}}
23+
takesTwoInouts(&a[i], &a[j])
24+
}
25+
26+
func missedSwapSuppression(_ i: Int, _ j: Int) {
27+
var a: [Int] = [1, 2, 3]
28+
29+
// We don't suppress when swap() is used as a value
30+
let mySwap: (inout Int, inout Int) -> () = swap
31+
32+
// expected-warning@+2{{modification requires exclusive access}}
33+
// expected-note@+1{{conflicting modification requires exclusive access}}
34+
mySwap(&a[i], &a[j])
35+
}
36+
37+
func dontSuppressUserSwap(_ i: Int, _ j: Int) {
38+
var a: [Int] = [1, 2, 3]
39+
40+
// Don't suppress on user-defined swap.
41+
func swap<T>(_ p1: inout T, _ p2: inout T) {
42+
return (p1, p2) = (p2, p1)
43+
}
44+
45+
// expected-warning@+2{{modification requires exclusive access}}
46+
// expected-note@+1{{conflicting modification requires exclusive access}}
47+
swap(&a[i], &a[j])
48+
}

0 commit comments

Comments
 (0)