Skip to content

Commit 6bfccef

Browse files
committed
[Runtime] Faster dynamic exclusivity checking implemented in Swift.
Replace C++ implementation of swift_beginAccess and swift_endAccess with (almost) pure Swift implementation. Helpers remain in C++ for TLS, getting return addresses, and raising a fatal error on violations. This change also moves the exclusivity access set head from the shared SwiftTLSContext structure to a dedicated TLS key. This improves performance, which is important for exclusivity checking. This is particularly the case where we can inline TLS access with a constant key, as on Darwin ARM64. The code that bridges exclusivity tracking into Concurrency remains in C++. The new Swift implementation exposes a few helpers for it to use as a replacement for directly manipulating the C++ implementation. rdar://161122309
1 parent e2287b6 commit 6bfccef

File tree

15 files changed

+389
-311
lines changed

15 files changed

+389
-311
lines changed

include/swift/Threading/Impl/Darwin.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,8 @@ inline tls_key_t tls_get_key(tls_key k) {
285285
return __PTK_FRAMEWORK_SWIFT_KEY5;
286286
case tls_key::observation_transaction:
287287
return __PTK_FRAMEWORK_SWIFT_KEY6;
288+
case tls_key::exclusivity:
289+
return __PTK_FRAMEWORK_SWIFT_KEY7;
288290
}
289291
}
290292

include/swift/Threading/TLSKeys.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ enum class tls_key {
2222
concurrency_task,
2323
concurrency_executor_tracking_info,
2424
concurrency_fallback,
25-
observation_transaction
25+
observation_transaction,
26+
exclusivity
2627
};
2728

2829
} // namespace swift

stdlib/public/SwiftShims/swift/shims/AssertionReporting.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,13 @@ void _swift_stdlib_reportUnimplementedInitializer(
6868
const unsigned char *initName, int initNameLength,
6969
__swift_uint32_t flags);
7070

71+
SWIFT_RUNTIME_STDLIB_SPI
72+
void _swift_reportExclusivityConflict(__swift_uintptr_t oldAction,
73+
const void *_Nullable oldPC,
74+
__swift_uintptr_t newFlags,
75+
const void *_Nullable newPC,
76+
const void *_Nullable pointer);
77+
7178
#ifdef __cplusplus
7279
} // extern "C"
7380
#endif

stdlib/public/SwiftShims/swift/shims/LibcShims.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ SWIFT_RUNTIME_STDLIB_INTERNAL
3838
__swift_size_t _swift_stdlib_fwrite_stdout(const void *ptr, __swift_size_t size,
3939
__swift_size_t nitems);
4040

41+
SWIFT_RUNTIME_STDLIB_INTERNAL
42+
void _swift_stdlib_fputs_stderr(const char *str);
43+
4144
// General utilities <stdlib.h>
4245
// Memory management functions
4346
static inline void _swift_stdlib_free(void *_Nullable ptr) {

stdlib/public/SwiftShims/swift/shims/RuntimeStubs.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#define SWIFT_STDLIB_SHIMS_RUNTIMESTUBS_H_
2121

2222
#include "LibcShims.h"
23+
#include "Visibility.h"
2324

2425
#ifdef __cplusplus
2526
extern "C" {
@@ -31,6 +32,25 @@ SWIFT_RUNTIME_STDLIB_API
3132
__swift_ssize_t
3233
swift_stdlib_readLine_stdin(unsigned char * _Nullable * _Nonnull LinePtr);
3334

35+
// Pick a return-address strategy
36+
#if defined(__wasm__)
37+
// Wasm can't access call frame for security purposes
38+
#define get_return_address() ((void*) 0)
39+
#elif __GNUC__
40+
#define get_return_address() __builtin_return_address(0)
41+
#elif _MSC_VER
42+
#include <intrin.h>
43+
#define get_return_address() _ReturnAddress()
44+
#else
45+
#error missing implementation for get_return_address
46+
#define get_return_address() ((void*) 0)
47+
#endif
48+
49+
SWIFT_ALWAYS_INLINE
50+
static inline const void *_Nullable _swift_stdlib_get_return_address() {
51+
return get_return_address();
52+
}
53+
3454
SWIFT_END_NULLABILITY_ANNOTATIONS
3555

3656
#ifdef __cplusplus

stdlib/public/SwiftShims/swift/shims/ThreadLocalStorage.h

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,48 @@
1818
SWIFT_RUNTIME_STDLIB_INTERNAL
1919
void * _Nonnull _swift_stdlib_threadLocalStorageGet(void);
2020

21+
SWIFT_RUNTIME_STDLIB_INTERNAL
22+
void * _Nullable _swift_getExclusivityTLSImpl();
23+
24+
SWIFT_RUNTIME_STDLIB_INTERNAL
25+
void _swift_setExclusivityTLSImpl(void * _Nullable newValue);
26+
27+
#if defined(__APPLE__) && __arm64__
28+
29+
// Use a fast path on Apple ARM64, where we have a dedicated TLS key and fast
30+
// access to read/write it.
31+
32+
#ifndef __PTK_FRAMEWORK_SWIFT_KEY7
33+
# define __PTK_FRAMEWORK_SWIFT_KEY7 107
34+
#endif
35+
36+
#define SWIFT_RUNTIME_EXCLUSIVITY_KEY __PTK_FRAMEWORK_SWIFT_KEY7
37+
38+
static inline void * _Nullable * _Nonnull _swift_getExclusivityTLSPointer() {
39+
unsigned long tsd;
40+
__asm__ ("mrs %0, TPIDRRO_EL0" : "=r" (tsd));
41+
void **base = (void **)tsd;
42+
return &base[SWIFT_RUNTIME_EXCLUSIVITY_KEY];
43+
}
44+
45+
static inline void * _Nullable _swift_getExclusivityTLS() {
46+
return *_swift_getExclusivityTLSPointer();
47+
}
48+
49+
static inline void _swift_setExclusivityTLS(void * _Nullable newValue) {
50+
*_swift_getExclusivityTLSPointer() = newValue;
51+
}
52+
53+
#else
54+
55+
static inline void * _Nullable _swift_getExclusivityTLS() {
56+
return _swift_getExclusivityTLSImpl();
57+
}
58+
59+
static inline void _swift_setExclusivityTLS(void * _Nullable newValue) {
60+
_swift_setExclusivityTLSImpl(newValue);
61+
}
62+
63+
#endif
64+
2165
#endif // SWIFT_STDLIB_SHIMS_THREADLOCALSTORAGE_H

stdlib/public/core/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ split_embedded_sources(
7878
EMBEDDED EnumeratedSequence.swift
7979
EMBEDDED Equatable.swift
8080
EMBEDDED ErrorType.swift
81+
NORMAL Exclusivity.swift
8182
EMBEDDED ExistentialCollection.swift
8283
EMBEDDED Filter.swift
8384
EMBEDDED FlatMap.swift
Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
import SwiftShims
2+
3+
fileprivate let ValueBufferSize = unsafe 3 * MemoryLayout<UnsafeRawPointer>.stride
4+
5+
fileprivate let TrackingFlag: UInt = 0x20
6+
fileprivate let ActionMask: UInt = 0x1
7+
8+
fileprivate typealias AccessPointer = UnsafeMutablePointer<Access>
9+
@unsafe fileprivate struct Access {
10+
11+
enum Action: UInt {
12+
case read
13+
case modify
14+
}
15+
16+
struct NextAndAction {
17+
private var rawValue: UInt
18+
19+
var action: Action {
20+
get {
21+
Action(rawValue: rawValue & ActionMask)!
22+
}
23+
set {
24+
rawValue = (rawValue & ~ActionMask) | newValue.rawValue
25+
}
26+
}
27+
28+
var next: AccessPointer? {
29+
get { unsafe UnsafeMutablePointer(bitPattern: rawValue & ~ActionMask) }
30+
set { rawValue = UInt(bitPattern: newValue) | (rawValue & ActionMask) }
31+
}
32+
}
33+
34+
var location: UnsafeRawPointer?
35+
var pc: UnsafeRawPointer?
36+
var nextAndAction: NextAndAction
37+
38+
var action: Action {
39+
get { unsafe nextAndAction.action }
40+
set { unsafe nextAndAction.action = newValue }
41+
}
42+
43+
var next: AccessPointer? {
44+
get { unsafe nextAndAction.next }
45+
set { unsafe nextAndAction.next = newValue }
46+
}
47+
48+
static func from(rawPointer: UnsafeMutableRawPointer?) -> AccessPointer? {
49+
guard let rawPointer = unsafe rawPointer else { return nil }
50+
return unsafe rawPointer.assumingMemoryBound(to: Access.self)
51+
}
52+
53+
@inline(__always)
54+
static func search(
55+
buffer: UnsafeMutableRawPointer,
56+
location: UnsafeRawPointer,
57+
pc: UnsafeRawPointer?,
58+
action: Action,
59+
inserting: Bool,
60+
head: inout AccessPointer?
61+
) {
62+
var cursor = unsafe head
63+
while let nextPtr = unsafe cursor {
64+
if unsafe nextPtr.pointee.location == location {
65+
if unsafe nextPtr.pointee.action == Action.modify
66+
|| action == Action.modify {
67+
unsafe _swift_reportExclusivityConflict(
68+
nextPtr.pointee.action.rawValue,
69+
nextPtr.pointee.pc,
70+
action.rawValue,
71+
pc,
72+
location)
73+
}
74+
}
75+
unsafe cursor = nextPtr.pointee.next
76+
}
77+
78+
if inserting {
79+
guard let access = unsafe Access.from(rawPointer: buffer) else {
80+
nullAccessBuffer()
81+
}
82+
83+
unsafe access.pointee.location = location
84+
unsafe access.pointee.pc = pc
85+
unsafe access.pointee.action = action
86+
87+
unsafe access.pointee.next = head
88+
unsafe head = access
89+
}
90+
}
91+
92+
@inline(__always)
93+
static func remove(access: AccessPointer, head: inout AccessPointer?) {
94+
var cursor = unsafe head
95+
var previous: AccessPointer? = nil
96+
while let nextPtr = unsafe cursor {
97+
if unsafe nextPtr == access {
98+
if let previous = unsafe previous {
99+
unsafe previous.pointee.next = access.pointee.next
100+
} else {
101+
unsafe head = access.pointee.next
102+
}
103+
return
104+
}
105+
unsafe previous = nextPtr
106+
unsafe cursor = nextPtr.pointee.next
107+
}
108+
109+
unsafe accessNotFound(access)
110+
}
111+
112+
@inline(__always)
113+
static func findParent(
114+
access: AccessPointer,
115+
child: AccessPointer?
116+
) -> AccessPointer? {
117+
var cursor = unsafe access
118+
while let next = unsafe cursor.pointee.next {
119+
if unsafe next == child {
120+
return unsafe cursor
121+
}
122+
unsafe cursor = next
123+
}
124+
125+
// If we were searching for nil, then we found it.
126+
if unsafe child == nil {
127+
return unsafe cursor
128+
}
129+
130+
// If we were searching for a non-nil node, we didn't find it.
131+
return nil
132+
}
133+
134+
static func forEach(_ head: AccessPointer?, _ action: (Access) -> Void) {
135+
var cursor = unsafe head
136+
while let nextPtr = unsafe cursor {
137+
unsafe action(nextPtr.pointee)
138+
unsafe cursor = nextPtr.pointee.next
139+
}
140+
}
141+
}
142+
143+
fileprivate var accessHead: AccessPointer? {
144+
get { unsafe Access.from(rawPointer: _swift_getExclusivityTLS()) }
145+
set { unsafe _swift_setExclusivityTLS(newValue) }
146+
}
147+
148+
@_cdecl("swift_beginAccess")
149+
@usableFromInline
150+
@unsafe
151+
internal func swift_beginAccess(
152+
pointer: UnsafeRawPointer,
153+
buffer: UnsafeMutableRawPointer,
154+
flags: UInt,
155+
pc: UnsafeRawPointer?) {
156+
precondition(unsafe MemoryLayout<Access>.size <= ValueBufferSize)
157+
158+
guard let action = Access.Action(rawValue: flags & ActionMask) else {
159+
invalidFlags(flags)
160+
}
161+
162+
let isTracking = (flags & TrackingFlag) != 0
163+
164+
unsafe Access.search(
165+
buffer: buffer,
166+
location: pointer,
167+
pc: pc ?? _swift_stdlib_get_return_address(),
168+
action: action,
169+
inserting: isTracking,
170+
head: &accessHead)
171+
}
172+
173+
@_cdecl("swift_endAccess")
174+
@usableFromInline
175+
@unsafe
176+
internal func swift_endAccess(buffer: UnsafeMutableRawPointer) {
177+
guard let access = unsafe Access.from(rawPointer: buffer) else {
178+
nullAccessBuffer()
179+
}
180+
unsafe Access.remove(access: access, head: &accessHead)
181+
}
182+
183+
@_cdecl("_swift_exclusivityAccessSetNext")
184+
@usableFromInline
185+
@unsafe
186+
internal func _swift_exclusivityAccessSetNext(
187+
access: UnsafeMutableRawPointer,
188+
next: UnsafeMutableRawPointer
189+
) {
190+
let access = unsafe Access.from(rawPointer: access)
191+
let next = unsafe Access.from(rawPointer: next)
192+
unsafe access?.pointee.next = next
193+
}
194+
195+
@_cdecl("swift_dumpTrackedAccesses")
196+
@usableFromInline
197+
@unsafe
198+
internal func swift_dumpTrackedAccesses() {
199+
if let head = unsafe accessHead {
200+
unsafe Access.forEach(head) {
201+
unsafe _swift_stdlib_fputs_stderr(" Access. " +
202+
"Pointer: \($0.location, default: "<null>")." +
203+
"PC: \($0.pc, default: "<null>"). " +
204+
"AccessAction: \($0.action)\n")
205+
}
206+
} else {
207+
unsafe _swift_stdlib_fputs_stderr(" No Accesses.\n")
208+
}
209+
}
210+
211+
/// Starting from `access`, find the access that is the parent node of `child`. If `child` is `nil`,
212+
/// find the last access in the list.
213+
@_cdecl("_swift_exclusivityAccessGetParent")
214+
@usableFromInline
215+
@unsafe
216+
internal func _swift_exclusivityAccessGetParent(
217+
access: UnsafeMutableRawPointer?,
218+
child: UnsafeMutableRawPointer?) -> UnsafeMutableRawPointer? {
219+
if let access = unsafe Access.from(rawPointer: access) {
220+
let result = unsafe Access.findParent(
221+
access: access, child: Access.from(rawPointer: child))
222+
return UnsafeMutableRawPointer(result)
223+
}
224+
return nil
225+
}
226+
227+
@inline(never)
228+
fileprivate func invalidFlags(_ flags: UInt) -> Never {
229+
reportExclusivityError("Internal exclusivity error",
230+
"unable to construct action from flags \(flags)")
231+
}
232+
233+
@inline(never)
234+
fileprivate func accessNotFound(_ access: AccessPointer) -> Never {
235+
unsafe reportExclusivityError("Internal exclusivity error",
236+
"didn't find exclusive access buffer \(access)")
237+
}
238+
239+
@inline(never)
240+
fileprivate func nullAccessBuffer() -> Never {
241+
reportExclusivityError("Internal exclusivity error", "NULL access buffer")
242+
}
243+
244+
fileprivate func reportExclusivityError(
245+
_ prefix: StaticString, _ message: String
246+
) -> Never {
247+
prefix.withUTF8Buffer { prefixBuffer in
248+
var message = message
249+
message.withUTF8 { messageBuffer in
250+
unsafe _swift_stdlib_reportFatalError(
251+
prefixBuffer.baseAddress!, CInt(prefixBuffer.count),
252+
messageBuffer.baseAddress!, CInt(messageBuffer.count),
253+
0)
254+
}
255+
}
256+
Builtin.int_trap()
257+
}

0 commit comments

Comments
 (0)