Skip to content

Commit abab49e

Browse files
committed
Support exclusivity enforcement of KeyPath read-only access.
Use begin_unpaired_access [no_nested_conflict] for Builtin.performInstantaneousReadAccess. This can't be optimized away and is the proper marker to use when the access scope is unknown. Drop the requirement that _semantics("optimize.sil.preserve_exclusivity") be @inline(never). We actually want theses inlined into user code. Verify that the @_semantic functions are not inlined or otherwise tampered with prior to serialization. Make *no* change to propagate @inline(__always) into LLVM. This no longer has any relationship to this PR and can be investigated seperately.
1 parent ef4a89c commit abab49e

File tree

9 files changed

+306
-115
lines changed

9 files changed

+306
-115
lines changed

lib/IRGen/GenDecl.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2317,7 +2317,8 @@ llvm::Function *IRGenModule::getAddrOfSILFunction(SILFunction *f,
23172317
llvm::Attribute::NoInline);
23182318
break;
23192319
case AlwaysInline:
2320-
// We do not transfer AlwaysInline since we get weird test failures.
2320+
// FIXME: We do not currently transfer AlwaysInline since doing so results
2321+
// in test failures, which must be investigated first.
23212322
case InlineDefault:
23222323
break;
23232324
}

lib/SILGen/SILGenBuiltin.cpp

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -574,10 +574,11 @@ static ManagedValue emitBuiltinBeginUnpairedModifyAccess(SILGenFunction &SGF,
574574
SILType valueBufferTy =
575575
SGF.getLoweredType(SGF.getASTContext().TheUnsafeValueBufferType);
576576

577-
SILValue buffer = SGF.B.createPointerToAddress(loc, args[1].getUnmanagedValue(),
578-
valueBufferTy.getAddressType(),
579-
/*strict*/ true,
580-
/*invariant*/ false);
577+
SILValue buffer =
578+
SGF.B.createPointerToAddress(loc, args[1].getUnmanagedValue(),
579+
valueBufferTy.getAddressType(),
580+
/*strict*/ true,
581+
/*invariant*/ false);
581582
SGF.B.createBeginUnpairedAccess(loc, addr, buffer, SILAccessKind::Modify,
582583
SILAccessEnforcement::Dynamic,
583584
/*noNestedConflict*/ false);
@@ -586,11 +587,10 @@ static ManagedValue emitBuiltinBeginUnpairedModifyAccess(SILGenFunction &SGF,
586587
}
587588

588589
/// Specialized emitter for Builtin.performInstantaneousReadAccess
589-
static ManagedValue emitBuiltinPerformInstantaneousReadAccess(SILGenFunction &SGF,
590-
SILLocation loc,
591-
SubstitutionList substitutions,
592-
ArrayRef<ManagedValue> args,
593-
SGFContext C) {
590+
static ManagedValue emitBuiltinPerformInstantaneousReadAccess(
591+
SILGenFunction &SGF, SILLocation loc, SubstitutionList substitutions,
592+
ArrayRef<ManagedValue> args, SGFContext C) {
593+
594594
assert(substitutions.size() == 1 &&
595595
"Builtin.performInstantaneousReadAccess should have one substitution");
596596
assert(args.size() == 2 &&
@@ -603,13 +603,21 @@ static ManagedValue emitBuiltinPerformInstantaneousReadAccess(SILGenFunction &SG
603603
/*strict*/ true,
604604
/*invariant*/ false);
605605

606-
// Begin and then immediately end a read access. No nested conflict is
607-
// possible because there are no instructions between the begin and the
608-
// end of the access.
609-
SILValue access = SGF.B.createBeginAccess(loc, addr, SILAccessKind::Read,
610-
SILAccessEnforcement::Dynamic,
611-
/*noNestedConflict*/ true);
612-
SGF.B.createEndAccess(loc, access, /*aborted*/ false);
606+
SILType valueBufferTy =
607+
SGF.getLoweredType(SGF.getASTContext().TheUnsafeValueBufferType);
608+
SILValue unusedBuffer = SGF.emitTemporaryAllocation(loc, valueBufferTy);
609+
610+
// Begin an "unscoped" read access. No nested conflict is possible because
611+
// the compiler should generate the actual read for the KeyPath expression
612+
// immediately after the call to this builtin, which forms the address of
613+
// that real access. When noNestedConflict=true, no EndUnpairedAccess should
614+
// be emitted.
615+
//
616+
// Unpaired access is necessary because a BeginAccess/EndAccess pair with no
617+
// use will be trivially optimized away.
618+
SGF.B.createBeginUnpairedAccess(loc, addr, unusedBuffer, SILAccessKind::Read,
619+
SILAccessEnforcement::Dynamic,
620+
/*noNestedConflict*/ true);
613621

614622
return ManagedValue::forUnmanaged(SGF.emitEmptyTuple(loc));
615623
}

lib/SILOptimizer/Mandatory/AccessMarkerElimination.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@
1919
/// test cases through the pipeline and exercising SIL verification before all
2020
/// passes support access markers.
2121
///
22+
/// This must only run before inlining _semantic calls. If we inline and drop
23+
/// the @_semantics("optimize.sil.preserve_exclusivity") attribute, the inlined
24+
/// markers will be eliminated, but the noninlined markers will not. This would
25+
/// result in inconsistent begin/end_unpaired_access resulting in unpredictable,
26+
/// potentially catastrophic runtime behavior.
27+
///
2228
//===----------------------------------------------------------------------===//
2329

2430
#define DEBUG_TYPE "access-marker-elim"

lib/SILOptimizer/Mandatory/IRGenPrepare.cpp

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -72,14 +72,6 @@ class IRGenPrepare : public SILFunctionTransform {
7272
void run() override {
7373
SILFunction *F = getFunction();
7474

75-
if (F->hasSemanticsAttr(OPTIMIZE_SIL_PRESERVE_EXCLUSIVITY)) {
76-
assert(F->getInlineStrategy() == NoInline &&
77-
"All OPTIMIZE_SIL_PRESERVE_EXCLUSIVITY functions should have "
78-
"no-inline");
79-
// We always want to inline these at the llvm level.
80-
F->setInlineStrategy(AlwaysInline);
81-
}
82-
8375
bool shouldInvalidate = cleanFunction(*F);
8476

8577
if (shouldInvalidate)

stdlib/public/core/KeyPath.swift

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,41 @@ internal enum KeyPathComponent: Hashable {
625625
}
626626
}
627627

628+
// _semantics("optimize.sil.preserve_exclusivity" forces the compiler to
629+
// generate access markers regardless of the current build settings. This way,
630+
// user code that accesses keypaths are properly enforced even if the standard
631+
// library has exclusivity checking internally disabled. The semantic attribute
632+
// must be consistently applied to both the begin and end unpaired access
633+
// markers, otherwise the runtime will fail catastrophically and unpredictably.
634+
// This relies on Swift module serialization occurring before these _semantic
635+
// function are inlined, since inlining would unpredictably cause the attribute
636+
// to be dropped.
637+
//
638+
// An exclusivity violation will report the caller's stack frame location.
639+
// Runtime diagnostics will be improved by inlining this function. However, this
640+
// cannot be marked @transparent because it can't be inlined prior to
641+
// serialization.
642+
@inlinable
643+
@inline(__always)
644+
@_semantics("optimize.sil.preserve_exclusivity")
645+
func beginAccessHelper<T>(_ address: Builtin.RawPointer, _ accessRecordPtr: Builtin.RawPointer, _ type: T.Type) {
646+
Builtin.beginUnpairedModifyAccess(address, accessRecordPtr, type)
647+
}
648+
649+
@inlinable
650+
@inline(__always)
651+
@_semantics("optimize.sil.preserve_exclusivity")
652+
func endAccessHelper(_ accessRecordPtr: Builtin.RawPointer) {
653+
Builtin.endUnpairedAccess(accessRecordPtr)
654+
}
655+
656+
@inlinable
657+
@inline(__always)
658+
@_semantics("optimize.sil.preserve_exclusivity")
659+
func instantaneousAccessHelper<T>(_ address: Builtin.RawPointer, _ type: T.Type) {
660+
Builtin.performInstantaneousReadAccess(address, T.self)
661+
}
662+
628663
// A class that maintains ownership of another object while a mutable projection
629664
// into it is underway. The lifetime of the instance of this class is also used
630665
// to begin and end exclusive 'modify' access to the projected address.
@@ -648,7 +683,6 @@ internal final class ClassHolder<ProjectionType> {
648683
}
649684

650685
@inlinable // FIXME(sil-serialize-all)
651-
@usableFromInline // FIXME(sil-serialize-all)
652686
internal final class func _create(
653687
previous: AnyObject?,
654688
instance: AnyObject,
@@ -680,18 +714,17 @@ internal final class ClassHolder<ProjectionType> {
680714

681715
// Begin a 'modify' access to the address. This access is ended in
682716
// ClassHolder's deinitializer.
683-
Builtin.beginUnpairedModifyAccess(address._rawValue, accessRecordPtr, type)
717+
beginAccessHelper(address._rawValue, accessRecordPtr, type)
684718

685719
return holder
686720
}
687721

688722
@inlinable // FIXME(sil-serialize-all)
689-
@usableFromInline // FIXME(sil-serialize-all)
690723
deinit {
691724
let accessRecordPtr = Builtin.projectTailElems(self, AccessRecord.self)
692725

693726
// Ends the access begun in _create().
694-
Builtin.endUnpairedAccess(accessRecordPtr)
727+
endAccessHelper(accessRecordPtr)
695728
}
696729
}
697730

@@ -1242,8 +1275,7 @@ internal struct RawKeyPathComponent {
12421275
// Perform an instaneous record access on the address in order to
12431276
// ensure that the read will not conflict with an already in-progress
12441277
// 'modify' access.
1245-
Builtin.performInstantaneousReadAccess(offsetAddress._rawValue,
1246-
NewValue.self)
1278+
instantaneousAccessHelper(offsetAddress._rawValue, NewValue.self)
12471279
return .continue(offsetAddress
12481280
.assumingMemoryBound(to: NewValue.self)
12491281
.pointee)

test/IRGen/preserve_exclusivity.swift

Lines changed: 74 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
1-
// RUN: %target-swift-frontend -parse-stdlib -parse-stdlib -emit-ir -Onone %s | %FileCheck --check-prefix=IR-Onone %s
2-
3-
// We check separately that:
1+
// RUN: %target-swift-frontend -parse-stdlib -emit-ir -Onone %s | %FileCheck --check-prefix=IR-Onone %s
42
//
5-
// 1. We properly emit our special functions as inline always.
6-
// 2. end-to-end we inline the access markers.
3+
// Check that access markers in @_semantics("optimize.sil.preserve_exclusivity") functions generate runtime calls.
74

8-
// RUN: %target-swift-frontend -parse-stdlib -Xllvm -sil-disable-pass=FunctionSignatureOpts -Xllvm -sil-disable-pass=GenericSpecializer -parse-stdlib -emit-ir -O -disable-llvm-optzns %s | %FileCheck --check-prefix=IR-Osil %s
9-
// RUN: %target-swift-frontend -parse-stdlib -Xllvm -sil-disable-pass=FunctionSignatureOpts -Xllvm -sil-disable-pass=GenericSpecializer -parse-stdlib -emit-ir -O %s | %FileCheck --check-prefix=IR-Ollvm %s
5+
// RUN: %target-swift-frontend -parse-stdlib -Xllvm -sil-disable-pass=FunctionSignatureOpts -Xllvm -sil-disable-pass=GenericSpecializer -emit-ir -O %s | %FileCheck --check-prefix=IR-O %s
6+
//
7+
// Check that the -O pipeline preserves the runtime calls for @_semantics("optimize.sil.preserve_exclusivity") functions.
108

119
@_silgen_name("marker1")
1210
func marker1() -> ()
@@ -20,83 +18,119 @@ func marker3() -> ()
2018
@_silgen_name("marker4")
2119
func marker4() -> ()
2220

23-
// IR-Onone: define swiftcc void @"$S20preserve_exclusivity10beginNoOptyyBp_BpxmtlF"(i8*, i8*, %swift.type*, %swift.type* %T1)
21+
@_silgen_name("marker5")
22+
func marker5() -> ()
23+
24+
@_silgen_name("marker6")
25+
func marker6() -> ()
26+
27+
// IR-Onone-LABEL: define {{.*}}swiftcc void @"$S20preserve_exclusivity10beginNoOptyyBp_BpxmtlF"(i8*, i8*, %swift.type*, %swift.type* %T1)
2428
// IR-Onone: call void @swift_beginAccess
2529
// IR-Onone-NEXT: ret void
2630

27-
// IR-Osil: define swiftcc void @"$S20preserve_exclusivity10beginNoOptyyBp_BpxmtlF"(i8*, i8*, %swift.type*, %swift.type* %T1) [[ATTR:#[0-9][0-9]*]] {
28-
// IR-Osil: call void @swift_beginAccess
29-
// IR-Osil-NEXT: ret void
31+
// IR-O-LABEL: define {{.*}}swiftcc void @"$S20preserve_exclusivity10beginNoOptyyBp_BpxmtlF"(i8*, i8*, %swift.type*{{.*}}, %swift.type*{{.*}} %T1)
32+
// IR-O: call void @swift_beginAccess
33+
// IR-O-NEXT: ret void
3034

31-
@inline(never)
3235
@_semantics("optimize.sil.preserve_exclusivity")
3336
public func beginNoOpt<T1>(_ address: Builtin.RawPointer, _ scratch: Builtin.RawPointer, _ ty1: T1.Type) {
3437
marker1()
3538
Builtin.beginUnpairedModifyAccess(address, scratch, ty1);
3639
}
3740

38-
// IR-Onone: define swiftcc void @"$S20preserve_exclusivity8endNoOptyyBpF"(i8*)
41+
// IR-Onone-LABEL: define {{.*}}swiftcc void @"$S20preserve_exclusivity8endNoOptyyBpF"(i8*)
3942
// IR-Onone: call void @swift_endAccess
4043
// IR-Onone-NEXT: ret void
4144

42-
// IR-Osil: define swiftcc void @"$S20preserve_exclusivity8endNoOptyyBpF"(i8*) [[ATTR]]
43-
// IR-Osil: call void @swift_endAccess
44-
// IR-Osil-NEXT: ret void
45-
@inline(never)
45+
// IR-O-LABEL: define {{.*}}swiftcc void @"$S20preserve_exclusivity8endNoOptyyBpF"(i8*)
46+
// IR-O: call void @swift_endAccess
47+
// IR-O-NEXT: ret void
4648
@_semantics("optimize.sil.preserve_exclusivity")
4749
public func endNoOpt(_ address: Builtin.RawPointer) {
4850
marker2()
4951
Builtin.endUnpairedAccess(address)
5052
}
5153

52-
class Klass {}
54+
// IR-Onone-LABEL: define {{.*}}swiftcc void @"$S20preserve_exclusivity9readNoOptyyBp_xmtlF"(i8*, %swift.type*, %swift.type* %T1)
55+
// IR-Onone: call void @swift_beginAccess
56+
// IR-Onone: ret void
57+
58+
// IR-O-LABEL: define {{.*}}swiftcc void @"$S20preserve_exclusivity9readNoOptyyBp_xmtlF"(i8*, %swift.type*{{.*}}, %swift.type*{{.*}} %T1)
59+
// IR-O: call void @swift_beginAccess
60+
// IR-O: ret void
61+
@_semantics("optimize.sil.preserve_exclusivity")
62+
public func readNoOpt<T1>(_ address: Builtin.RawPointer, _ ty1: T1.Type) {
63+
marker3()
64+
Builtin.performInstantaneousReadAccess(address, ty1);
65+
}
5366

5467
// Make sure testNoOpt properly inlines in our functions.
5568
//
56-
// IR-Ollvm: define swiftcc void @"$S20preserve_exclusivity9testNoOptyyBpF"(i8*)
57-
// IR-Ollvm: call swiftcc void @marker1
58-
// IR-Ollvm: call void @swift_beginAccess
59-
// IR-Ollvm: call swiftcc void @marker2
60-
// IR-Ollvm: call void @swift_endAccess
61-
// IR-Ollvm-NEXT: ret void
69+
// IR-O-LABEL: define {{.*}}swiftcc void @"$S20preserve_exclusivity9testNoOptyyBpF"(i8*)
70+
// IR-O: call swiftcc void @marker1
71+
// IR-O: call void @swift_beginAccess
72+
// IR-O: call swiftcc void @marker2
73+
// IR-O: call void @swift_endAccess
74+
// IR-O: call swiftcc void @marker3
75+
// IR-O: call void @swift_beginAccess
76+
// IR-O: ret void
6277
public func testNoOpt(_ k1: Builtin.RawPointer) {
6378
beginNoOpt(k1, k1, Builtin.RawPointer.self)
6479
endNoOpt(k1)
80+
readNoOpt(k1, Builtin.RawPointer.self)
6581
}
6682

67-
// IR-Onone: define swiftcc void @"$S20preserve_exclusivity8beginOptyyBp_BpxmtlF"(i8*, i8*, %swift.type*, %swift.type* %T1)
83+
// IR-Onone-LABEL: define {{.*}}swiftcc void @"$S20preserve_exclusivity8beginOptyyBp_BpxmtlF"(i8*, i8*, %swift.type*, %swift.type* %T1)
6884
// IR-Onone: call void @swift_beginAccess
6985
// IR-Onone-NEXT: ret void
7086

71-
// IR-Osil: define swiftcc void @"$S20preserve_exclusivity8beginOptyyBp_BpxmtlF"(i8*, i8*, %swift.type*, %swift.type* %T1)
72-
// IR-Osil-NEXT: entry
73-
// IR-Osil-NEXT: call swiftcc void @marker3
74-
// IR-Osil-NEXT: ret void
87+
// IR-O-LABEL: define {{.*}}swiftcc void @"$S20preserve_exclusivity8beginOptyyBp_BpxmtlF"(i8*{{.*}}, i8*{{.*}}, %swift.type*{{.*}}, %swift.type*{{.*}} %T1)
88+
// IR-O-NEXT: entry
89+
// IR-O-NEXT: call swiftcc void @marker4
90+
// IR-O-NEXT: ret void
7591

76-
@inline(never)
7792
public func beginOpt<T1>(_ address: Builtin.RawPointer, _ scratch: Builtin.RawPointer, _ ty1: T1.Type) {
78-
marker3()
93+
marker4()
7994
Builtin.beginUnpairedModifyAccess(address, scratch, ty1);
8095
}
8196

82-
// IR-Onone: define swiftcc void @"$S20preserve_exclusivity6endOptyyBpF"(i8*)
97+
// IR-Onone-LABEL: define {{.*}}swiftcc void @"$S20preserve_exclusivity6endOptyyBpF"(i8*)
8398
// IR-Onone: call void @swift_endAccess
8499
// IR-Onone-NEXT: ret void
85100

86-
// IR-Osil: define swiftcc void @"$S20preserve_exclusivity6endOptyyBpF"(i8*)
87-
// IR-Osil-NEXT: entry
88-
// IR-Osil-NEXT: call swiftcc void @marker4
89-
// IR-Osil-NEXT: ret void
101+
// IR-O-LABEL: define {{.*}}swiftcc void @"$S20preserve_exclusivity6endOptyyBpF"(i8*{{.*}})
102+
// IR-O-NEXT: entry
103+
// IR-O-NEXT: call swiftcc void @marker5
104+
// IR-O-NEXT: ret void
90105

91-
@inline(never)
92106
public func endOpt(_ address: Builtin.RawPointer) {
93-
marker4()
107+
marker5()
94108
Builtin.endUnpairedAccess(address)
95109
}
96110

111+
// IR-Onone-LABEL: define {{.*}}swiftcc void @"$S20preserve_exclusivity7readOptyyBp_xmtlF"(i8*, %swift.type*, %swift.type* %T1)
112+
// IR-Onone: call void @swift_beginAccess
113+
// IR-Onone: ret void
114+
115+
// IR-O-LABEL: define {{.*}}swiftcc void @"$S20preserve_exclusivity7readOptyyBp_xmtlF"(i8*{{.*}}, %swift.type*{{.*}}, %swift.type*{{.*}} %T1)
116+
// IR-O-NEXT: entry
117+
// IR-O-NEXT: call swiftcc void @marker6
118+
// IR-O-NEXT: ret void
119+
120+
public func readOpt<T1>(_ address: Builtin.RawPointer, _ ty1: T1.Type) {
121+
marker6()
122+
Builtin.performInstantaneousReadAccess(address, ty1);
123+
}
124+
125+
// Make sure testOpt properly inlines in our functions.
126+
//
127+
// IR-O-LABEL: define {{.*}}swiftcc void @"$S20preserve_exclusivity7testOptyyBpF"(i8*{{.*}})
128+
// IR-O: call swiftcc void @marker4
129+
// IR-O: call swiftcc void @marker5
130+
// IR-O: call swiftcc void @marker6
131+
// IR-O: ret void
97132
public func testOpt(_ k1: Builtin.RawPointer) {
98133
beginOpt(k1, k1, Builtin.RawPointer.self)
99134
endOpt(k1)
135+
readOpt(k1, Builtin.RawPointer.self)
100136
}
101-
102-
// IR-Osil: attributes [[ATTR]] = { alwaysinline

test/SILGen/builtins.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -384,8 +384,9 @@ func beginUnpairedModifyAccess<T1>(address: Builtin.RawPointer, scratch: Builtin
384384
// CHECK-LABEL: sil hidden @$S8builtins30performInstantaneousReadAccess{{[_0-9a-zA-Z]*}}F
385385
func performInstantaneousReadAccess<T1>(address: Builtin.RawPointer, scratch: Builtin.RawPointer, ty1: T1.Type) {
386386
// CHECK: [[P2A_ADDR:%.*]] = pointer_to_address %0
387-
// CHECK: [[ACCESS:%.*]] = begin_access [read] [dynamic] [no_nested_conflict] [[P2A_ADDR]] : $*T1
388-
// CHECK-NEXT: end_access [[ACCESS]] : $*T1
387+
// CHECK: [[SCRATCH:%.*]] = alloc_stack $Builtin.UnsafeValueBuffer
388+
// CHECK: begin_unpaired_access [read] [dynamic] [no_nested_conflict] [[P2A_ADDR]] : $*T1, [[SCRATCH]] : $*Builtin.UnsafeValueBuffer
389+
// CHECK-NOT: end_{{.*}}access
389390
// CHECK: [[RESULT:%.*]] = tuple ()
390391
// CHECK: [[RETURN:%.*]] = tuple ()
391392
// CHECK: return [[RETURN]] : $()

0 commit comments

Comments
 (0)