Skip to content

Commit ca054a8

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 ca054a8

File tree

15 files changed

+394
-311
lines changed

15 files changed

+394
-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: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
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+
) {
157+
// Make sure that Access fits in the value buffer. This should be a static
158+
// assert, but we'll settle for a precondition for now. The optimizer should
159+
// eliminate the check when it's true.
160+
precondition(unsafe MemoryLayout<Access>.size <= ValueBufferSize)
161+
162+
guard let action = Access.Action(rawValue: flags & ActionMask) else {
163+
invalidFlags(flags)
164+
}
165+
166+
let isTracking = (flags & TrackingFlag) != 0
167+
168+
unsafe Access.search(
169+
buffer: buffer,
170+
location: pointer,
171+
pc: pc ?? _swift_stdlib_get_return_address(),
172+
action: action,
173+
inserting: isTracking,
174+
head: &accessHead)
175+
}
176+
177+
@_cdecl("swift_endAccess")
178+
@usableFromInline
179+
@unsafe
180+
internal func swift_endAccess(buffer: UnsafeMutableRawPointer) {
181+
guard let access = unsafe Access.from(rawPointer: buffer) else {
182+
nullAccessBuffer()
183+
}
184+
unsafe Access.remove(access: access, head: &accessHead)
185+
}
186+
187+
@_cdecl("_swift_exclusivityAccessSetNext")
188+
@usableFromInline
189+
@unsafe
190+
internal func _swift_exclusivityAccessSetNext(
191+
access: UnsafeMutableRawPointer,
192+
next: UnsafeMutableRawPointer
193+
) {
194+
let access = unsafe Access.from(rawPointer: access)
195+
let next = unsafe Access.from(rawPointer: next)
196+
unsafe access?.pointee.next = next
197+
}
198+
199+
@_cdecl("swift_dumpTrackedAccesses")
200+
@usableFromInline
201+
@unsafe
202+
internal func swift_dumpTrackedAccesses() {
203+
if let head = unsafe accessHead {
204+
unsafe Access.forEach(head) {
205+
unsafe _swift_stdlib_fputs_stderr(" Access. " +
206+
"Pointer: \($0.location, default: "<null>")." +
207+
"PC: \($0.pc, default: "<null>"). " +
208+
"AccessAction: \($0.action)\n")
209+
}
210+
} else {
211+
unsafe _swift_stdlib_fputs_stderr(" No Accesses.\n")
212+
}
213+
}
214+
215+
/// Starting from `access`, find the access that is the parent node of `child`.
216+
/// If `child` is `nil`, find the last access in the list.
217+
@_cdecl("_swift_exclusivityAccessGetParent")
218+
@usableFromInline
219+
@unsafe
220+
internal func _swift_exclusivityAccessGetParent(
221+
access: UnsafeMutableRawPointer?,
222+
child: UnsafeMutableRawPointer?
223+
) -> UnsafeMutableRawPointer? {
224+
if let access = unsafe Access.from(rawPointer: access) {
225+
let result = unsafe Access.findParent(
226+
access: access, child: Access.from(rawPointer: child))
227+
return UnsafeMutableRawPointer(result)
228+
}
229+
return nil
230+
}
231+
232+
@inline(never)
233+
fileprivate func invalidFlags(_ flags: UInt) -> Never {
234+
reportExclusivityError("Internal exclusivity error",
235+
"unable to construct action from flags \(flags)")
236+
}
237+
238+
@inline(never)
239+
fileprivate func accessNotFound(_ access: AccessPointer) -> Never {
240+
unsafe reportExclusivityError("Internal exclusivity error",
241+
"didn't find exclusive access buffer \(access)")
242+
}
243+
244+
@inline(never)
245+
fileprivate func nullAccessBuffer() -> Never {
246+
reportExclusivityError("Internal exclusivity error", "NULL access buffer")
247+
}
248+
249+
fileprivate func reportExclusivityError(
250+
_ prefix: StaticString, _ message: String
251+
) -> Never {
252+
prefix.withUTF8Buffer { prefixBuffer in
253+
var message = message
254+
message.withUTF8 { messageBuffer in
255+
unsafe _swift_stdlib_reportFatalError(
256+
prefixBuffer.baseAddress!, CInt(prefixBuffer.count),
257+
messageBuffer.baseAddress!, CInt(messageBuffer.count),
258+
0)
259+
}
260+
}
261+
Builtin.int_trap()
262+
}

0 commit comments

Comments
 (0)