Skip to content

Commit 08e6624

Browse files
authored
Merge pull request #16051 from atrick/exclusive-key-path-fix
[Exclusivity] Enforce exclusive access for class offsets in KeyPaths
2 parents 9a8a9d4 + abab49e commit 08e6624

File tree

14 files changed

+745
-59
lines changed

14 files changed

+745
-59
lines changed

include/swift/Runtime/Exclusivity.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,19 @@ void swift_endAccess(ValueBuffer *buffer);
5858
SWIFT_RUNTIME_EXPORT
5959
bool _swift_disableExclusivityChecking;
6060

61+
#ifndef NDEBUG
62+
63+
/// Dump all accesses currently tracked by the runtime.
64+
///
65+
/// This is a debug routine that is intended to be used from the debugger and is
66+
/// compiled out when asserts are disabled. The intention is that it allows one
67+
/// to dump the access state to easily see if/when exclusivity violations will
68+
/// happen. This eases debugging.
69+
SWIFT_RUNTIME_EXPORT
70+
void swift_dumpTrackedAccesses();
71+
72+
#endif
73+
6174
} // end namespace swift
6275

6376
#endif

include/swift/Strings.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
#ifndef SWIFT_STRINGS_H
1414
#define SWIFT_STRINGS_H
1515

16+
#include "swift/Basic/LLVM.h"
17+
#include "llvm/ADT/StringRef.h"
18+
1619
namespace swift {
1720

1821
/// The extension for serialized modules.
@@ -89,6 +92,10 @@ constexpr static const char BUILTIN_TYPE_NAME_VEC[] = "Builtin.Vec";
8992
constexpr static const char BUILTIN_TYPE_NAME_SILTOKEN[] = "Builtin.SILToken";
9093
/// The name of the Builtin type for Word
9194
constexpr static const char BUILTIN_TYPE_NAME_WORD[] = "Builtin.Word";
95+
96+
constexpr static StringLiteral OPTIMIZE_SIL_PRESERVE_EXCLUSIVITY =
97+
"optimize.sil.preserve_exclusivity";
98+
9299
} // end namespace swift
93100

94101
#endif // SWIFT_STRINGS_H

lib/IRGen/GenDecl.cpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2310,11 +2310,19 @@ llvm::Function *IRGenModule::getAddrOfSILFunction(SILFunction *f,
23102310

23112311
LinkInfo link = LinkInfo::get(*this, entity, forDefinition);
23122312

2313-
if (f->getInlineStrategy() == NoInline) {
2313+
switch (f->getInlineStrategy()) {
2314+
case NoInline:
23142315
attrs = attrs.addAttribute(signature.getType()->getContext(),
23152316
llvm::AttributeList::FunctionIndex,
23162317
llvm::Attribute::NoInline);
2318+
break;
2319+
case AlwaysInline:
2320+
// FIXME: We do not currently transfer AlwaysInline since doing so results
2321+
// in test failures, which must be investigated first.
2322+
case InlineDefault:
2323+
break;
23172324
}
2325+
23182326
if (isReadOnlyFunction(f)) {
23192327
attrs = attrs.addAttribute(signature.getType()->getContext(),
23202328
llvm::AttributeList::FunctionIndex,

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: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,19 @@
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"
2531
#include "swift/Basic/Range.h"
2632
#include "swift/SIL/SILFunction.h"
2733
#include "swift/SILOptimizer/PassManager/Transforms.h"
34+
#include "swift/Strings.h"
2835
#include "llvm/Support/CommandLine.h"
2936

3037
using namespace swift;
@@ -167,6 +174,12 @@ struct AccessMarkerEliminationPass : SILModuleTransform {
167174
void run() override {
168175
auto &M = *getModule();
169176
for (auto &F : M) {
177+
if (F.hasSemanticsAttr(OPTIMIZE_SIL_PRESERVE_EXCLUSIVITY)) {
178+
DEBUG(llvm::dbgs() << "Skipping " << F.getName() << ". Found "
179+
<< OPTIMIZE_SIL_PRESERVE_EXCLUSIVITY << " tag!\n");
180+
continue;
181+
}
182+
170183
bool removedAny = AccessMarkerElimination(&F).stripMarkers();
171184

172185
// Only invalidate analyses if we removed some markers.

lib/SILOptimizer/Mandatory/IRGenPrepare.cpp

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,14 @@
2020
///
2121
//===----------------------------------------------------------------------===//
2222

23-
#include "swift/SILOptimizer/PassManager/Passes.h"
23+
#define DEBUG_TYPE "sil-cleanup"
2424
#include "swift/SIL/SILFunction.h"
2525
#include "swift/SIL/SILInstruction.h"
2626
#include "swift/SIL/SILModule.h"
27-
#include "swift/SILOptimizer/Utils/Local.h"
27+
#include "swift/SILOptimizer/PassManager/Passes.h"
2828
#include "swift/SILOptimizer/PassManager/Transforms.h"
29+
#include "swift/SILOptimizer/Utils/Local.h"
30+
#include "swift/Strings.h"
2931

3032
using namespace swift;
3133

@@ -68,10 +70,12 @@ namespace {
6870

6971
class IRGenPrepare : public SILFunctionTransform {
7072
void run() override {
71-
bool shouldInvalidate = cleanFunction(*getFunction());
72-
if (!shouldInvalidate)
73-
return;
74-
invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions);
73+
SILFunction *F = getFunction();
74+
75+
bool shouldInvalidate = cleanFunction(*F);
76+
77+
if (shouldInvalidate)
78+
invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions);
7579
}
7680
};
7781

stdlib/public/core/KeyPath.swift

Lines changed: 106 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -625,23 +625,107 @@ 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
629-
// into it is underway.
664+
// into it is underway. The lifetime of the instance of this class is also used
665+
// to begin and end exclusive 'modify' access to the projected address.
630666
@_fixed_layout // FIXME(sil-serialize-all)
631667
@usableFromInline // FIXME(sil-serialize-all)
632-
internal final class ClassHolder {
668+
internal final class ClassHolder<ProjectionType> {
669+
670+
/// The type of the scratch record passed to the runtime to record
671+
/// accesses to guarantee exlcusive access.
672+
internal typealias AccessRecord = Builtin.UnsafeValueBuffer
673+
633674
@usableFromInline // FIXME(sil-serialize-all)
634-
internal let previous: AnyObject?
675+
internal var previous: AnyObject?
635676
@usableFromInline // FIXME(sil-serialize-all)
636-
internal let instance: AnyObject
677+
internal var instance: AnyObject
637678

638679
@inlinable // FIXME(sil-serialize-all)
639680
internal init(previous: AnyObject?, instance: AnyObject) {
640681
self.previous = previous
641682
self.instance = instance
642683
}
684+
643685
@inlinable // FIXME(sil-serialize-all)
644-
deinit {}
686+
internal final class func _create(
687+
previous: AnyObject?,
688+
instance: AnyObject,
689+
accessingAddress address: UnsafeRawPointer,
690+
type: ProjectionType.Type
691+
) -> ClassHolder {
692+
693+
// Tail allocate the UnsafeValueBuffer used as the AccessRecord.
694+
// This avoids a second heap allocation since there is no source-level way to
695+
// initialize a Builtin.UnsafeValueBuffer type and thus we cannot have a
696+
// stored property of that type.
697+
let holder: ClassHolder = Builtin.allocWithTailElems_1(self,
698+
1._builtinWordValue,
699+
AccessRecord.self)
700+
701+
// Initialize the ClassHolder's instance variables. This is done via
702+
// withUnsafeMutablePointer(to:) because the instance was just allocated with
703+
// allocWithTailElems_1 and so we need to make sure to use an initialization
704+
// rather than an assignment.
705+
withUnsafeMutablePointer(to: &holder.previous) {
706+
$0.initialize(to: previous)
707+
}
708+
709+
withUnsafeMutablePointer(to: &holder.instance) {
710+
$0.initialize(to: instance)
711+
}
712+
713+
let accessRecordPtr = Builtin.projectTailElems(holder, AccessRecord.self)
714+
715+
// Begin a 'modify' access to the address. This access is ended in
716+
// ClassHolder's deinitializer.
717+
beginAccessHelper(address._rawValue, accessRecordPtr, type)
718+
719+
return holder
720+
}
721+
722+
@inlinable // FIXME(sil-serialize-all)
723+
deinit {
724+
let accessRecordPtr = Builtin.projectTailElems(self, AccessRecord.self)
725+
726+
// Ends the access begun in _create().
727+
endAccessHelper(accessRecordPtr)
728+
}
645729
}
646730

647731
// A class that triggers writeback to a pointer when destroyed.
@@ -1185,7 +1269,14 @@ internal struct RawKeyPathComponent {
11851269
let baseObj = unsafeBitCast(base, to: AnyObject.self)
11861270
let basePtr = UnsafeRawPointer(Builtin.bridgeToRawPointer(baseObj))
11871271
defer { _fixLifetime(baseObj) }
1188-
return .continue(basePtr.advanced(by: offset)
1272+
1273+
let offsetAddress = basePtr.advanced(by: offset)
1274+
1275+
// Perform an instaneous record access on the address in order to
1276+
// ensure that the read will not conflict with an already in-progress
1277+
// 'modify' access.
1278+
instantaneousAccessHelper(offsetAddress._rawValue, NewValue.self)
1279+
return .continue(offsetAddress
11891280
.assumingMemoryBound(to: NewValue.self)
11901281
.pointee)
11911282

@@ -1244,12 +1335,16 @@ internal struct RawKeyPathComponent {
12441335
// AnyObject memory can alias any class reference memory, so we can
12451336
// assume type here
12461337
let object = base.assumingMemoryBound(to: AnyObject.self).pointee
1247-
// The base ought to be kept alive for the duration of the derived access
1248-
keepAlive = keepAlive == nil
1249-
? object
1250-
: ClassHolder(previous: keepAlive, instance: object)
1251-
return UnsafeRawPointer(Builtin.bridgeToRawPointer(object))
1338+
let offsetAddress = UnsafeRawPointer(Builtin.bridgeToRawPointer(object))
12521339
.advanced(by: offset)
1340+
1341+
// Keep the base alive for the duration of the derived access and also
1342+
// enforce exclusive access to the address.
1343+
keepAlive = ClassHolder._create(previous: keepAlive, instance: object,
1344+
accessingAddress: offsetAddress,
1345+
type: NewValue.self)
1346+
1347+
return offsetAddress
12531348

12541349
case .mutatingGetSet(id: _, get: let rawGet, set: let rawSet,
12551350
argument: let argument):

stdlib/public/runtime/Exclusivity.cpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,16 @@ class AccessSet {
260260

261261
swift_runtime_unreachable("access not found in set");
262262
}
263+
264+
#ifndef NDEBUG
265+
/// Only available with asserts. Intended to be used with
266+
/// swift_dumpTrackedAccess().
267+
void forEach(std::function<void (Access *)> action) {
268+
for (auto *iter = Head; iter != nullptr; iter = iter->getNext()) {
269+
action(iter);
270+
}
271+
}
272+
#endif
263273
};
264274

265275
} // end anonymous namespace
@@ -348,3 +358,17 @@ void swift::swift_endAccess(ValueBuffer *buffer) {
348358

349359
getAccessSet().remove(access);
350360
}
361+
362+
#ifndef NDEBUG
363+
364+
// Dump the accesses that are currently being tracked by the runtime.
365+
//
366+
// This is only intended to be used in the debugger.
367+
void swift::swift_dumpTrackedAccesses() {
368+
getAccessSet().forEach([](Access *a) {
369+
fprintf(stderr, "Access. Pointer: %p. PC: %p. AccessAction: %s",
370+
a->Pointer, a->PC, getAccessName(a->getAccessAction()));
371+
});
372+
}
373+
374+
#endif

0 commit comments

Comments
 (0)