Skip to content

Commit d1757fc

Browse files
committed
[Exclusivity] Enforce exclusive access for class offsets in KeyPaths
Add dynamic enforcement of exclusive access when a KeyPath directly accesses a final stored property on an instance of a class. For read-only projections, this begins and ends the access immediately. For mutable projections, this uses the ClassHolder to perform a long-term access that lasts as long as the lifetime of the ClassHolder. rdar://problem/31972680
1 parent 6c42bcc commit d1757fc

File tree

9 files changed

+367
-20
lines changed

9 files changed

+367
-20
lines changed

include/swift/AST/Builtins.def

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,27 @@ BUILTIN_SIL_OPERATION(Gep, "gep", Integer)
294294
/// allocated element type 'E'.
295295
BUILTIN_SIL_OPERATION(GetTailAddr, "getTailAddr", Integer)
296296

297+
/// performInstantaneousReadAccess(Builtin.RawPointer, T.Type) -> ()
298+
/// Begin and then immediately end a read access to the given raw pointer,
299+
/// which will be treated as an address of type 'T'.
300+
BUILTIN_SIL_OPERATION(PerformInstantaneousReadAccess,
301+
"performInstantaneousReadAccess", Special)
302+
303+
/// beginUnpairedModifyAccess(Builtin.RawPointer, Builtin.RawPointer,
304+
/// T.Type) -> ()
305+
/// Begins but does not end a 'modify' access to the first raw pointer argument.
306+
/// The second raw pointer must be a pointer to an UnsafeValueBuffer, which
307+
/// will be used by the runtime to record the access. The lifetime of the
308+
/// value buffer must be longer than that of the access itself. The accessed
309+
/// address will be treated as having type 'T'.
310+
BUILTIN_SIL_OPERATION(BeginUnpairedModifyAccess, "beginUnpairedModifyAccess",
311+
Special)
312+
313+
/// endUnpairedAccess(Builtin.RawPointer) -> ()
314+
/// Ends an in-progress unpaired access. The raw pointer argument must be
315+
/// be a pointer to an UnsafeValueBuffer that records an in progress access.
316+
BUILTIN_SIL_OPERATION(EndUnpairedAccess, "endUnpairedAccess", Special)
317+
297318
/// condfail(Int1) -> ()
298319
/// Triggers a runtime failure if the condition is true.
299320
BUILTIN_SIL_OPERATION(CondFail, "condfail", Special)

lib/AST/Builtins.cpp

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -738,6 +738,31 @@ static ValueDecl *getGetTailAddrOperation(ASTContext &Context, Identifier Id,
738738
return builder.build(Id);
739739
}
740740

741+
static ValueDecl *getBeginUnpairedAccessOperation(ASTContext &Context,
742+
Identifier Id) {
743+
BuiltinGenericSignatureBuilder builder(Context);
744+
builder.addParameter(makeConcrete(Context.TheRawPointerType));
745+
builder.addParameter(makeConcrete(Context.TheRawPointerType));
746+
builder.addParameter(makeMetatype(makeGenericParam(0)));
747+
builder.setResult(makeConcrete(Context.TheEmptyTupleType));
748+
return builder.build(Id);
749+
}
750+
751+
static ValueDecl *
752+
getPerformInstantaneousReadAccessOperation(ASTContext &Context,
753+
Identifier Id) {
754+
BuiltinGenericSignatureBuilder builder(Context);
755+
builder.addParameter(makeConcrete(Context.TheRawPointerType));
756+
builder.addParameter(makeMetatype(makeGenericParam(0)));
757+
builder.setResult(makeConcrete(Context.TheEmptyTupleType));
758+
return builder.build(Id);
759+
}
760+
761+
static ValueDecl *getEndUnpairedAccessOperation(ASTContext &Context,
762+
Identifier Id) {
763+
return getBuiltinFunction(Id, { Context.TheRawPointerType },
764+
Context.TheEmptyTupleType);
765+
}
741766
static ValueDecl *getSizeOrAlignOfOperation(ASTContext &Context,
742767
Identifier Id) {
743768
BuiltinGenericSignatureBuilder builder(Context);
@@ -1644,6 +1669,18 @@ ValueDecl *swift::getBuiltinValueDecl(ASTContext &Context, Identifier Id) {
16441669
if (Types.size() != 1) return nullptr;
16451670
return getGetTailAddrOperation(Context, Id, Types[0]);
16461671

1672+
case BuiltinValueKind::PerformInstantaneousReadAccess:
1673+
if (!Types.empty()) return nullptr;
1674+
return getPerformInstantaneousReadAccessOperation(Context, Id);
1675+
1676+
case BuiltinValueKind::BeginUnpairedModifyAccess:
1677+
if (!Types.empty()) return nullptr;
1678+
return getBeginUnpairedAccessOperation(Context, Id);
1679+
1680+
case BuiltinValueKind::EndUnpairedAccess:
1681+
if (!Types.empty()) return nullptr;
1682+
return getEndUnpairedAccessOperation(Context, Id);
1683+
16471684
#define BUILTIN(id, name, Attrs)
16481685
#define BUILTIN_BINARY_OPERATION(id, name, attrs, overload) case BuiltinValueKind::id:
16491686
#include "swift/AST/Builtins.def"

lib/SIL/SILOwnershipVerifier.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1431,6 +1431,9 @@ BUILTINS_THAT_SHOULD_HAVE_BEEN_LOWERED_TO_SILINSTS(AddressOf)
14311431
BUILTINS_THAT_SHOULD_HAVE_BEEN_LOWERED_TO_SILINSTS(GepRaw)
14321432
BUILTINS_THAT_SHOULD_HAVE_BEEN_LOWERED_TO_SILINSTS(Gep)
14331433
BUILTINS_THAT_SHOULD_HAVE_BEEN_LOWERED_TO_SILINSTS(GetTailAddr)
1434+
BUILTINS_THAT_SHOULD_HAVE_BEEN_LOWERED_TO_SILINSTS(PerformInstantaneousReadAccess)
1435+
BUILTINS_THAT_SHOULD_HAVE_BEEN_LOWERED_TO_SILINSTS(BeginUnpairedModifyAccess)
1436+
BUILTINS_THAT_SHOULD_HAVE_BEEN_LOWERED_TO_SILINSTS(EndUnpairedAccess)
14341437
BUILTINS_THAT_SHOULD_HAVE_BEEN_LOWERED_TO_SILINSTS(CondFail)
14351438
BUILTINS_THAT_SHOULD_HAVE_BEEN_LOWERED_TO_SILINSTS(FixLifetime)
14361439
BUILTINS_THAT_SHOULD_HAVE_BEEN_LOWERED_TO_SILINSTS(IsUnique)

lib/SIL/ValueOwnershipKindClassifier.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,9 @@ CONSTANT_OWNERSHIP_BUILTIN(Trivial, AddressOf)
475475
CONSTANT_OWNERSHIP_BUILTIN(Trivial, GepRaw)
476476
CONSTANT_OWNERSHIP_BUILTIN(Trivial, Gep)
477477
CONSTANT_OWNERSHIP_BUILTIN(Trivial, GetTailAddr)
478+
CONSTANT_OWNERSHIP_BUILTIN(Trivial, PerformInstantaneousReadAccess)
479+
CONSTANT_OWNERSHIP_BUILTIN(Trivial, BeginUnpairedModifyAccess)
480+
CONSTANT_OWNERSHIP_BUILTIN(Trivial, EndUnpairedAccess)
478481
CONSTANT_OWNERSHIP_BUILTIN(Trivial, OnFastPath)
479482
CONSTANT_OWNERSHIP_BUILTIN(Trivial, IsUnique)
480483
CONSTANT_OWNERSHIP_BUILTIN(Trivial, IsUniqueOrPinned)

lib/SILGen/SILGenBuiltin.cpp

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,90 @@ static ManagedValue emitBuiltinGetTailAddr(SILGenFunction &SGF,
517517
return ManagedValue::forUnmanaged(addr);
518518
}
519519

520+
/// Specialized emitter for Builtin.beginUnpairedModifyAccess.
521+
static ManagedValue emitBuiltinBeginUnpairedModifyAccess(SILGenFunction &SGF,
522+
SILLocation loc,
523+
SubstitutionList substitutions,
524+
ArrayRef<ManagedValue> args,
525+
SGFContext C) {
526+
assert(substitutions.size() == 1 &&
527+
"Builtin.beginUnpairedModifyAccess should have one substitution");
528+
assert(args.size() == 3 &&
529+
"beginUnpairedModifyAccess should be given three arguments");
530+
531+
SILType elemTy = SGF.getLoweredType(substitutions[0].getReplacement());
532+
SILValue addr = SGF.B.createPointerToAddress(loc, args[0].getUnmanagedValue(),
533+
elemTy.getAddressType(),
534+
/*strict*/ true,
535+
/*invariant*/ false);
536+
537+
SILType valueBufferTy =
538+
SGF.getLoweredType(SGF.getASTContext().TheUnsafeValueBufferType);
539+
540+
SILValue buffer = SGF.B.createPointerToAddress(loc, args[1].getUnmanagedValue(),
541+
valueBufferTy.getAddressType(),
542+
/*strict*/ true,
543+
/*invariant*/ false);
544+
SGF.B.createBeginUnpairedAccess(loc, addr, buffer, SILAccessKind::Modify,
545+
SILAccessEnforcement::Dynamic,
546+
/*noNestedConflict*/ false);
547+
548+
return ManagedValue::forUnmanaged(SGF.emitEmptyTuple(loc));
549+
}
550+
551+
/// Specialized emitter for Builtin.performInstantaneousReadAccess
552+
static ManagedValue emitBuiltinPerformInstantaneousReadAccess(SILGenFunction &SGF,
553+
SILLocation loc,
554+
SubstitutionList substitutions,
555+
ArrayRef<ManagedValue> args,
556+
SGFContext C) {
557+
assert(substitutions.size() == 1 &&
558+
"Builtin.performInstantaneousReadAccess should have one substitution");
559+
assert(args.size() == 2 &&
560+
"Builtin.performInstantaneousReadAccess should be given "
561+
"two arguments");
562+
563+
SILType elemTy = SGF.getLoweredType(substitutions[0].getReplacement());
564+
SILValue addr = SGF.B.createPointerToAddress(loc, args[0].getUnmanagedValue(),
565+
elemTy.getAddressType(),
566+
/*strict*/ true,
567+
/*invariant*/ false);
568+
569+
// Begin and then immediately end a read access. No nested conflict is
570+
// possible because there are no instructions between the begin and the
571+
// end of the access.
572+
SILValue access = SGF.B.createBeginAccess(loc, addr, SILAccessKind::Read,
573+
SILAccessEnforcement::Dynamic,
574+
/*noNestedConflict*/ true);
575+
SGF.B.createEndAccess(loc, access, /*aborted*/ false);
576+
577+
return ManagedValue::forUnmanaged(SGF.emitEmptyTuple(loc));
578+
}
579+
580+
/// Specialized emitter for Builtin.endUnpairedAccessModifyAccess.
581+
static ManagedValue emitBuiltinEndUnpairedAccess(SILGenFunction &SGF,
582+
SILLocation loc,
583+
SubstitutionList substitutions,
584+
ArrayRef<ManagedValue> args,
585+
SGFContext C) {
586+
assert(substitutions.size() == 0 &&
587+
"Builtin.endUnpairedAccess should have no substitutions");
588+
assert(args.size() == 1 &&
589+
"endUnpairedAccess should be given one argument");
590+
591+
SILType valueBufferTy =
592+
SGF.getLoweredType(SGF.getASTContext().TheUnsafeValueBufferType);
593+
594+
SILValue buffer = SGF.B.createPointerToAddress(loc, args[0].getUnmanagedValue(),
595+
valueBufferTy.getAddressType(),
596+
/*strict*/ true,
597+
/*invariant*/ false);
598+
SGF.B.createEndUnpairedAccess(loc, buffer, SILAccessEnforcement::Dynamic,
599+
/*aborted*/ false);
600+
601+
return ManagedValue::forUnmanaged(SGF.emitEmptyTuple(loc));
602+
}
603+
520604
/// Specialized emitter for Builtin.condfail.
521605
static ManagedValue emitBuiltinCondFail(SILGenFunction &SGF,
522606
SILLocation loc,

stdlib/public/core/KeyPath.swift

Lines changed: 75 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -644,24 +644,73 @@ internal enum KeyPathComponent: Hashable {
644644
}
645645

646646
// A class that maintains ownership of another object while a mutable projection
647-
// into it is underway.
647+
// into it is underway. The lifetime of the instance of this class is also used
648+
// to begin and end exclusive 'modify' access to the projected address.
648649
@_fixed_layout // FIXME(sil-serialize-all)
649650
@usableFromInline // FIXME(sil-serialize-all)
650-
internal final class ClassHolder {
651+
internal final class ClassHolder<ProjectionType> {
652+
653+
/// The type of the scratch record passed to the runtime to record
654+
/// accesses to guarantee exlcusive access.
655+
internal typealias AccessRecord = Builtin.UnsafeValueBuffer
656+
651657
@usableFromInline // FIXME(sil-serialize-all)
652-
internal let previous: AnyObject?
658+
internal var previous: AnyObject?
653659
@usableFromInline // FIXME(sil-serialize-all)
654-
internal let instance: AnyObject
660+
internal var instance: AnyObject
655661

656662
@inlinable // FIXME(sil-serialize-all)
657663
@usableFromInline // FIXME(sil-serialize-all)
658-
internal init(previous: AnyObject?, instance: AnyObject) {
659-
self.previous = previous
660-
self.instance = instance
664+
internal init() {
665+
_sanityCheckFailure("use _create(...)")
661666
}
667+
662668
@inlinable // FIXME(sil-serialize-all)
663669
@usableFromInline // FIXME(sil-serialize-all)
664-
deinit {}
670+
internal final class func _create(
671+
previous: AnyObject?,
672+
instance: AnyObject,
673+
accessingAddress address: UnsafeRawPointer,
674+
type: ProjectionType.Type
675+
) -> ClassHolder {
676+
677+
// Tail allocate the UnsafeValueBuffer used as the AccessRecord.
678+
// This avoids a second heap allocation since there is no source-level way to
679+
// initialize a Builtin.UnsafeValueBuffer type and thus we cannot have a
680+
// stored property of that type.
681+
let holder: ClassHolder = Builtin.allocWithTailElems_1(self,
682+
1._builtinWordValue,
683+
AccessRecord.self)
684+
685+
// Initialize the ClassHolder's instance variables. This is done via
686+
// withUnsafeMutablePointer(to:) because the instance was just allocated with
687+
// allocWithTailElems_1 and so we need to make sure to use an initialization
688+
// rather than an assignment.
689+
withUnsafeMutablePointer(to: &holder.previous) {
690+
$0.initialize(to: previous)
691+
}
692+
693+
withUnsafeMutablePointer(to: &holder.instance) {
694+
$0.initialize(to: instance)
695+
}
696+
697+
let accessRecordPtr = Builtin.projectTailElems(holder, AccessRecord.self)
698+
699+
// Begin a 'modify' access to the address. This access is ended in
700+
// ClassHolder's deinitializer.
701+
Builtin.beginUnpairedModifyAccess(address._rawValue, accessRecordPtr, type)
702+
703+
return holder
704+
}
705+
706+
@inlinable // FIXME(sil-serialize-all)
707+
@usableFromInline // FIXME(sil-serialize-all)
708+
deinit {
709+
let accessRecordPtr = Builtin.projectTailElems(self, AccessRecord.self)
710+
711+
// Ends the access begun in _create().
712+
Builtin.endUnpairedAccess(accessRecordPtr)
713+
}
665714
}
666715

667716
// A class that triggers writeback to a pointer when destroyed.
@@ -1253,7 +1302,15 @@ internal struct RawKeyPathComponent {
12531302
let baseObj = unsafeBitCast(base, to: AnyObject.self)
12541303
let basePtr = UnsafeRawPointer(Builtin.bridgeToRawPointer(baseObj))
12551304
defer { _fixLifetime(baseObj) }
1256-
return .continue(basePtr.advanced(by: offset)
1305+
1306+
let offsetAddress = basePtr.advanced(by: offset)
1307+
1308+
// Perform an instaneous record access on the address in order to
1309+
// ensure that the read will not conflict with an already in-progress
1310+
// 'modify' access.
1311+
Builtin.performInstantaneousReadAccess(offsetAddress._rawValue,
1312+
NewValue.self)
1313+
return .continue(offsetAddress
12571314
.assumingMemoryBound(to: NewValue.self)
12581315
.pointee)
12591316

@@ -1313,12 +1370,16 @@ internal struct RawKeyPathComponent {
13131370
// AnyObject memory can alias any class reference memory, so we can
13141371
// assume type here
13151372
let object = base.assumingMemoryBound(to: AnyObject.self).pointee
1316-
// The base ought to be kept alive for the duration of the derived access
1317-
keepAlive = keepAlive == nil
1318-
? object
1319-
: ClassHolder(previous: keepAlive, instance: object)
1320-
return UnsafeRawPointer(Builtin.bridgeToRawPointer(object))
1373+
let offsetAddress = UnsafeRawPointer(Builtin.bridgeToRawPointer(object))
13211374
.advanced(by: offset)
1375+
1376+
// Keep the base alive for the duration of the derived access and also
1377+
// enforce exclusive access to the address.
1378+
keepAlive = ClassHolder._create(previous: keepAlive, instance: object,
1379+
accessingAddress: offsetAddress,
1380+
type: NewValue.self)
1381+
1382+
return offsetAddress
13221383

13231384
case .mutatingGetSet(id: _, get: let rawGet, set: let rawSet,
13241385
argument: let argument):

0 commit comments

Comments
 (0)