Skip to content

Commit c2cd172

Browse files
authored
Merge pull request #83999 from eeckstein/access-enforcement-improvements
Optimizer: some small fixes and improvements for exclusivity checking
2 parents 50b5ecf + c790052 commit c2cd172

16 files changed

+205
-23
lines changed

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/AllocBoxToStack.swift

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ import SIL
5454
let allocBoxToStack = FunctionPass(name: "allocbox-to-stack") {
5555
(function: Function, context: FunctionPassContext) in
5656

57-
_ = tryConvertBoxesToStack(in: function, context)
57+
_ = tryConvertBoxesToStack(in: function, isMandatory: false, context)
5858
}
5959

6060
/// The "mandatory" version of the pass, which runs in the mandatory pipeline.
@@ -72,7 +72,7 @@ let mandatoryAllocBoxToStack = ModulePass(name: "mandatory-allocbox-to-stack") {
7272

7373
while let function = worklist.pop() {
7474
moduleContext.transform(function: function) { context in
75-
let specFns = tryConvertBoxesToStack(in: function, context)
75+
let specFns = tryConvertBoxesToStack(in: function, isMandatory: true, context)
7676
worklist.pushIfNotVisited(contentsOf: specFns.specializedFunctions)
7777
originalsOfSpecializedFunctions.pushIfNotVisited(contentsOf: specFns.originalFunctions)
7878
}
@@ -86,9 +86,11 @@ let mandatoryAllocBoxToStack = ModulePass(name: "mandatory-allocbox-to-stack") {
8686
/// Converts all non-escaping `alloc_box` to `alloc_stack` and specializes called functions if a
8787
/// box is passed to a function.
8888
/// Returns the list of original functions for which a specialization has been created.
89-
private func tryConvertBoxesToStack(in function: Function, _ context: FunctionPassContext) -> FunctionSpecializations {
89+
private func tryConvertBoxesToStack(in function: Function, isMandatory: Bool,
90+
_ context: FunctionPassContext
91+
) -> FunctionSpecializations {
9092
var promotableBoxes = Array<(AllocBoxInst, Flags)>()
91-
var functionsToSpecialize = FunctionSpecializations()
93+
var functionsToSpecialize = FunctionSpecializations(isMandatory: isMandatory)
9294

9395
findPromotableBoxes(in: function, &promotableBoxes, &functionsToSpecialize)
9496

@@ -189,6 +191,9 @@ private struct FunctionSpecializations {
189191
private var promotableArguments = CrossFunctionValueWorklist()
190192
private var originals = FunctionWorklist()
191193
private var originalToSpecialized = Dictionary<Function, Function>()
194+
private let isMandatory: Bool
195+
196+
init(isMandatory: Bool) { self.isMandatory = isMandatory }
192197

193198
var originalFunctions: [Function] { originals.functions }
194199
var specializedFunctions: [Function] { originals.functions.lazy.map { originalToSpecialized[$0]! } }
@@ -221,6 +226,12 @@ private struct FunctionSpecializations {
221226
context.erase(instruction: user)
222227
case let projectBox as ProjectBoxInst:
223228
assert(projectBox.fieldIndex == 0, "only single-field boxes are handled")
229+
if isMandatory {
230+
// Once we have promoted the box to stack, access violations can be detected statically by the
231+
// DiagnoseStaticExclusivity pass (which runs after MandatoryAllocBoxToStack).
232+
// Therefore we can convert dynamic accesses to static accesses.
233+
makeAccessesStatic(of: projectBox, context)
234+
}
224235
projectBox.replace(with: stack, context)
225236
case is MarkUninitializedInst, is CopyValueInst, is BeginBorrowInst, is MoveValueInst:
226237
// First, replace the instruction with the original `box`, which adds more uses to `box`.
@@ -478,6 +489,14 @@ private func hoistMarkUnresolvedInsts(stackAddress: Value,
478489
.replaceAll(with: mu, context)
479490
}
480491

492+
private func makeAccessesStatic(of address: Value, _ context: FunctionPassContext) {
493+
for beginAccess in address.uses.users(ofType: BeginAccessInst.self) {
494+
if beginAccess.enforcement == .dynamic {
495+
beginAccess.set(enforcement: .static, context: context)
496+
}
497+
}
498+
}
499+
481500
private extension ApplySite {
482501
func getSpecializableCallee() -> Function? {
483502
if let callee = referencedFunction,

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/TempLValueElimination.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,13 @@ private func tryEliminate(copy: CopyLikeInstruction, _ context: FunctionPassCont
113113
return
114114
}
115115

116+
// If exclusivity is checked for the alloc_stack we must not replace it with the copy-destination.
117+
// If the copy-destination is also in an access-scope this would result in an exclusivity violation
118+
// which was not there before.
119+
guard allocStack.uses.users(ofType: BeginAccessInst.self).isEmpty else {
120+
return
121+
}
122+
116123
var worklist = InstructionWorklist(context)
117124
defer { worklist.deinitialize() }
118125
worklist.pushIfNotVisited(firstUseOfAllocStack)

SwiftCompilerSources/Sources/SIL/Instruction.swift

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1629,12 +1629,39 @@ final public class BeginAccessInst : SingleValueInstruction, UnaryInstruction {
16291629
public var accessKind: AccessKind {
16301630
AccessKind(rawValue: bridged.BeginAccessInst_getAccessKind())!
16311631
}
1632-
public func set(accessKind: BeginAccessInst.AccessKind, context: some MutatingContext) {
1632+
public func set(accessKind: AccessKind, context: some MutatingContext) {
16331633
context.notifyInstructionsChanged()
16341634
bridged.BeginAccess_setAccessKind(accessKind.rawValue)
16351635
context.notifyInstructionChanged(self)
16361636
}
16371637

1638+
// The raw values must match SILAccessEnforcement.
1639+
public enum Enforcement: Int {
1640+
/// The access's enforcement has not yet been determined.
1641+
case unknown = 0
1642+
1643+
/// The access is statically known to not conflict with other accesses.
1644+
case `static` = 1
1645+
1646+
/// The access is not statically known to not conflict with anything and must be dynamically checked.
1647+
case dynamic = 2
1648+
1649+
/// The access is not statically known to not conflict with anything but dynamic checking should
1650+
/// be suppressed, leaving it undefined behavior.
1651+
case unsafe = 3
1652+
1653+
/// Access to pointers that are signed via pointer authentication.
1654+
case signed = 4
1655+
}
1656+
public var enforcement: Enforcement {
1657+
Enforcement(rawValue: bridged.BeginAccessInst_getEnforcement())!
1658+
}
1659+
public func set(enforcement: Enforcement, context: some MutatingContext) {
1660+
context.notifyInstructionsChanged()
1661+
bridged.BeginAccess_setEnforcement(enforcement.rawValue)
1662+
context.notifyInstructionChanged(self)
1663+
}
1664+
16381665
public var isStatic: Bool { bridged.BeginAccessInst_isStatic() }
16391666
public var isUnsafe: Bool { bridged.BeginAccessInst_isUnsafe() }
16401667

include/swift/SIL/SILBridging.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -824,6 +824,8 @@ struct BridgedInstruction {
824824
BRIDGED_INLINE bool BeginAccessInst_isStatic() const;
825825
BRIDGED_INLINE bool BeginAccessInst_isUnsafe() const;
826826
BRIDGED_INLINE void BeginAccess_setAccessKind(SwiftInt accessKind) const;
827+
BRIDGED_INLINE SwiftInt BeginAccessInst_getEnforcement() const;
828+
BRIDGED_INLINE void BeginAccess_setEnforcement(SwiftInt accessKind) const;
827829
BRIDGED_INLINE bool CopyAddrInst_isTakeOfSrc() const;
828830
BRIDGED_INLINE bool CopyAddrInst_isInitializationOfDest() const;
829831
BRIDGED_INLINE void CopyAddrInst_setIsTakeOfSrc(bool isTakeOfSrc) const;

include/swift/SIL/SILBridgingImpl.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1494,6 +1494,14 @@ void BridgedInstruction::BeginAccess_setAccessKind(SwiftInt accessKind) const {
14941494
getAs<swift::BeginAccessInst>()->setAccessKind((swift::SILAccessKind)accessKind);
14951495
}
14961496

1497+
SwiftInt BridgedInstruction::BeginAccessInst_getEnforcement() const {
1498+
return (SwiftInt)getAs<swift::BeginAccessInst>()->getEnforcement();
1499+
}
1500+
1501+
void BridgedInstruction::BeginAccess_setEnforcement(SwiftInt accessKind) const {
1502+
getAs<swift::BeginAccessInst>()->setEnforcement((swift::SILAccessEnforcement)accessKind);
1503+
}
1504+
14971505
bool BridgedInstruction::CopyAddrInst_isTakeOfSrc() const {
14981506
return getAs<swift::CopyAddrInst>()->isTakeOfSrc();
14991507
}

include/swift/SIL/SILInstruction.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4885,6 +4885,7 @@ class EndBorrowInst
48854885
};
48864886

48874887
/// Different kinds of access.
4888+
/// This enum must stay in sync with `BeginAccessInst.AccessKind` in SwiftCompilerSources.
48884889
enum class SILAccessKind : uint8_t {
48894890
/// An access which takes uninitialized memory and initializes it.
48904891
Init,
@@ -4907,6 +4908,7 @@ enum { NumSILAccessKindBits = 2 };
49074908
StringRef getSILAccessKindName(SILAccessKind kind);
49084909

49094910
/// Different kinds of exclusivity enforcement for accesses.
4911+
/// This enum must stay in sync with `BeginAccessInst.Enforcement` in SwiftCompilerSources.
49104912
enum class SILAccessEnforcement : uint8_t {
49114913
/// The access's enforcement has not yet been determined.
49124914
Unknown,

lib/SILOptimizer/Mandatory/RawSILInstLowering.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -290,13 +290,20 @@ lowerAssignOrInitInstruction(SILBuilderWithScope &b,
290290
auto isRefSelf = selfValue->getType().getASTType()->mayHaveSuperclass();
291291

292292
SILValue selfRef;
293+
bool needInsertEndAccess = false;
293294
if (isRefSelf) {
294295
selfRef = b.emitBeginBorrowOperation(loc, selfValue);
296+
} else if (isa<BeginAccessInst>(selfValue)) {
297+
// Don't insert an access scope if there is already one. This avoids
298+
// inserting a dynamic access check when the parent is static (and therefore
299+
// can be statically enforced).
300+
selfRef = selfValue;
295301
} else {
296302
selfRef = b.createBeginAccess(loc, selfValue, SILAccessKind::Modify,
297303
SILAccessEnforcement::Dynamic,
298304
/*noNestedConflict=*/false,
299305
/*fromBuiltin=*/false);
306+
needInsertEndAccess = true;
300307
}
301308

302309
auto emitFieldReference = [&](VarDecl *field,
@@ -341,7 +348,7 @@ lowerAssignOrInitInstruction(SILBuilderWithScope &b,
341348
if (isRefSelf) {
342349
if (selfRef != selfValue)
343350
b.emitEndBorrowOperation(loc, selfRef);
344-
} else {
351+
} else if (needInsertEndAccess) {
345352
b.createEndAccess(loc, selfRef, /*aborted=*/false);
346353
}
347354

lib/SILOptimizer/PassManager/PassPipeline.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,6 @@ static void addMandatoryDiagnosticOptPipeline(SILPassPipelinePlan &P) {
117117
P.startPipeline("Mandatory Diagnostic Passes + Enabling Optimization Passes");
118118
P.addDiagnoseInvalidEscapingCaptures();
119119
P.addReferenceBindingTransform();
120-
P.addDiagnoseStaticExclusivity();
121120
P.addNestedSemanticFunctionCheck();
122121
P.addCapturePromotion();
123122

@@ -130,6 +129,10 @@ static void addMandatoryDiagnosticOptPipeline(SILPassPipelinePlan &P) {
130129
#else
131130
P.addLegacyAllocBoxToStack();
132131
#endif
132+
// Needs to run after MandatoryAllocBoxToStack, because MandatoryAllocBoxToStack
133+
// can convert dynamic accesses to static accesses.
134+
P.addDiagnoseStaticExclusivity();
135+
133136
P.addNoReturnFolding();
134137
P.addBooleanLiteralFolding();
135138
addDefiniteInitialization(P);

test/SILGen/moveonly_escaping_closure.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ func testLocalLetClosureCaptureVar() {
143143
consumeVal(x) // expected-note {{consumed here}}
144144
// expected-note @-1 {{consumed again here}}
145145
borrowConsumeVal(x, x)
146-
// expected-error @-1 {{overlapping accesses, but deinitialization requires exclusive access}}
146+
// expected-error @-1 {{overlapping accesses to 'x', but deinitialization requires exclusive access}}
147147
// expected-note @-2 {{conflicting access is here}}
148148
// expected-note @-3 {{used here}}
149149
// expected-note @-4 {{used here}}
@@ -975,7 +975,7 @@ func testLocalLetClosureCaptureConsuming(_ x: consuming SingleElt) {
975975
consumeVal(x) // expected-note {{consumed here}}
976976
// expected-note @-1 {{consumed again here}}
977977
borrowConsumeVal(x, x) // expected-note {{used here}}
978-
// expected-error @-1 {{overlapping accesses, but deinitialization requires exclusive access}}
978+
// expected-error @-1 {{overlapping accesses to 'x', but deinitialization requires exclusive access}}
979979
// expected-note @-2 {{conflicting access is here}}
980980
// expected-note @-3 {{consumed here}}
981981
// expected-note @-4 {{used here}}

test/SILOptimizer/allocbox_to_stack_ownership.sil

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1435,3 +1435,19 @@ die:
14351435
destroy_value %copy
14361436
unreachable
14371437
}
1438+
1439+
// CHECK-LABEL: sil [ossa] @test_access_enforcement :
1440+
// OPT: begin_access [modify] [dynamic]
1441+
// MANDATORY: begin_access [modify] [static]
1442+
// CHECK-LABEL: } // end sil function 'test_access_enforcement'
1443+
sil [ossa] @test_access_enforcement : $(Int) -> Int {
1444+
bb0(%0 : $Int):
1445+
%1 = alloc_box ${ var Int }
1446+
%2 = project_box %1 : ${ var Int }, 0
1447+
%3 = begin_access [modify] [dynamic] %2
1448+
store %0 to [trivial] %3
1449+
end_access %3
1450+
%4 = load [trivial] %2
1451+
destroy_value %1
1452+
return %4 : $Int
1453+
}

0 commit comments

Comments
 (0)