File: LinuxImpl.swift

package info (click to toggle)
swiftlang 6.0.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,519,992 kB
  • sloc: cpp: 9,107,863; ansic: 2,040,022; asm: 1,135,751; python: 296,500; objc: 82,456; f90: 60,502; lisp: 34,951; pascal: 19,946; sh: 18,133; perl: 7,482; ml: 4,937; javascript: 4,117; makefile: 3,840; awk: 3,535; xml: 914; fortran: 619; cs: 573; ruby: 573
file content (368 lines) | stat: -rw-r--r-- 14,873 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift Atomics open source project
//
// Copyright (c) 2024 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

import _SynchronizationShims
#if canImport(Android)
import Android
#elseif canImport(Musl)
import Musl
#else
import Glibc
#endif

extension Atomic where Value == UInt32 {
  // This returns 'false' on success and 'true' on error. Check 'errno' for the
  // specifc error value.
  internal borrowing func _futexLock() -> UInt32 {
    _swift_stdlib_futex_lock(.init(_rawAddress))
  }

  // This returns 'false' on success and 'true' on error. Check 'errno' for the
  // specifc error value.
  internal borrowing func _futexTryLock() -> UInt32 {
    _swift_stdlib_futex_trylock(.init(_rawAddress))
  }

  // This returns 'false' on success and 'true' on error. Check 'errno' for the
  // specific error value.
  internal borrowing func _futexUnlock() -> UInt32 {
    _swift_stdlib_futex_unlock(.init(_rawAddress))
  }
}

@available(SwiftStdlib 6.0, *)
@frozen
@_staticExclusiveOnly
public struct _MutexHandle: ~Copyable {
  // There are only 3 different values that storage can hold at a single time.
  // 0: unlocked
  // TID: locked, current thread's id (uncontended)
  // (TID | FUTEX_WAITERS): locked, current thread's id (contended)
  @usableFromInline
  let storage: Atomic<UInt32>

  @available(SwiftStdlib 6.0, *)
  @_alwaysEmitIntoClient
  @_transparent
  public init() {
    storage = Atomic(0)
  }
}

@available(SwiftStdlib 6.0, *)
extension _MutexHandle {
  @available(SwiftStdlib 6.0, *)
  @_alwaysEmitIntoClient
  @_transparent
  internal borrowing func _lock() {
    // Note: This is being TLS cached.
    let selfId = _swift_stdlib_gettid()

    let (exchanged, _) = storage.compareExchange(
      expected: 0,
      desired: selfId,
      successOrdering: .acquiring,
      failureOrdering: .relaxed
    )

    if _fastPath(exchanged) {
      // Locked!
      return
    }

    _lockSlow(selfId)
  }

  @available(SwiftStdlib 6.0, *)
  @usableFromInline
  internal borrowing func _lockSlow(_ selfId: UInt32) {
    // Before relinquishing control to the kernel to block this particular
    // thread, run a little spin lock to keep this thread busy in the scenario
    // where the current owner thread's critical section is somewhat quick. We
    // avoid a lot of the syscall overhead in these cases which allow both the
    // owner thread and this current thread to do the user-space atomic for
    // releasing and acquiring (assuming no existing waiters). The waiter bit is
    // typically unset when a call to 'FUTEX_UNLOCK_PI' has no other pi state,
    // meaning there is no one else waiting to acquire the lock.
    do {
      // This value is controlled on a per architecture bases defined in
      // 'SpinLoopHint.swift'.
      var tries = _tries

      repeat {
        // Do a relaxed load of the futex value to prevent introducing a memory
        // barrier on each iteration of this loop. We're already informing the
        // CPU that this is a spin loop via the '_spinLoopHint' call which
        // should hopefully slow down the loop a considerable amount to view an
        // actually change in the value potentially. An extra memory barrier
        // would make it even slower on top of the fact that we may not even be
        // able to attempt to acquire the lock.
        let state = storage.load(ordering: .relaxed)

        if state == 0, storage.compareExchange(
          expected: 0,
          desired: selfId,
          successOrdering: .acquiring,
          failureOrdering: .relaxed
        ).exchanged {
          // Locked!
          return
        }

        tries &-= 1

        // Inform the CPU that we're doing a spin loop which should have the
        // effect of slowing down this loop if only by a little to preserve
        // energy.
        _spinLoopHint()
      } while tries != 0
    }

    // We've exhausted our spins. Ask the kernel to block for us until the owner
    // releases the lock.
    //
    // Note: The kernel will attempt to acquire the lock for us as well which
    // could succeed if the owner releases in between finishing spinning the
    // futex syscall.
    while true {
      // Block until an equivalent '_futexUnlock' has been called by the owner.
      // This returns '0' on success which means the kernel has acquired the
      // lock for us.
      switch storage._futexLock() {
      case 0:
        // Locked!
        return

      // EINTR  - "A FUTEX_WAIT or FUTEX_WAIT_BITSET operation was interrupted
      //           by a signal (see signal(7)). Before Linux 2.6.22, this error
      //           could also be returned for a spurious wakeup; since Linux
      //           2.6.22, this no longer happens."
      // EAGAIN - "The futex owner thread ID of uaddr is about to exit, but has
      //           not yet handled the internal state cleanup. Try again."
      case 4, 11:
        continue

      // EDEADLK - "The futex word at uaddr is already locked by the caller."
      case 35:
        // TODO: Replace with a colder function / one that takes a StaticString
        fatalError("Recursive call to lock Mutex")

      // This handles all of the following errors which generally aren't
      // applicable to this implementation:
      //
      // EACCES - "No read access to the memory of a futex word."
      // EFAULT - "A required pointer argument did not point to a valid
      //           user-space address."
      // EINVAL - "The operation in futex_op is one of those that employs a
      //           timeout, but the supplied timeout argument was invalid
      //           (tv_sec was less than zero, or tv_nsec was not less than
      //           1,000,000,000)."
      //          OR
      //          "The operation specified in futex_op employs one or both of
      //           the pointers uaddr and uaddr2, but one of these does not
      //           point to a valid object—that is, the address is not four-
      //           byte-aligned."
      //          OR
      //          "The kernel detected an inconsistency between the user-space
      //           state at uaddr and the kernel state. This indicates either
      //           state corruption or that the kernel found a waiter on uaddr
      //           which is waiting via FUTEX_WAIT or FUTEX_WAIT_BITSET."
      //          OR
      //          "Invalid argument."
      // ENOMEM - "The kernel could not allocate memory to hold state
      //           information."
      // ENOSYS - "Invalid operation specified in futex_op."
      //          OR
      //          "A run-time check determined that the operation is not
      //           available. The PI-futex operations are not implemented on all
      //           architectures and are not supported on some CPU variants."
      // EPERM  - "The caller is not allowed to attach itself to the futex at
      //           uaddr (This may be caused by a state corruption in user
      //           space.)"
      // ESRCH  - "The thread ID in the futex word at uaddr does not exist."
      default:
        // TODO: Replace with a colder function / one that takes a StaticString
        fatalError("Unknown error occured while attempting to acquire a Mutex")
      }
    }
  }

  @available(SwiftStdlib 6.0, *)
  @_alwaysEmitIntoClient
  @_transparent
  internal borrowing func _tryLock() -> Bool {
    // Do a user space cmpxchg to see if we can easily acquire the lock.
    if storage.compareExchange(
      expected: 0,

      // Note: This is being TLS cached.
      desired: _swift_stdlib_gettid(),
      successOrdering: .acquiring,
      failureOrdering: .relaxed
    ).exchanged {
      // Locked!
      return true
    }

    // The quick atomic op failed, ask the kernel to see if it can acquire the
    // lock for us.
    return _tryLockSlow()
  }

  @available(SwiftStdlib 6.0, *)
  @usableFromInline
  internal borrowing func _tryLockSlow() -> Bool {
    // Note: "Because the kernel has access to more state information than user
    //        space, acquisition of the lock might succeed if performed by the
    //        kernel in cases where the futex word (i.e., the state information
    //        accessible to use-space) contains stale state (FUTEX_WAITERS
    //        and/or FUTEX_OWNER_DIED). This can happen when the owner of the
    //        futex died. User space cannot handle this condition in a race-free
    //        manner, but the kernel can fix this up and acquire the futex."
    switch storage._futexTryLock() {
    case 0:
      // Locked!
      return true

    // EDEADLK - "The futex word at uaddr is already locked by the caller."
    case 35:
      // TODO: Replace with a colder function / one that takes a StaticString
      fatalError("Attempt to try to lock Mutex in already acquired thread")

    // This handles all of the following errors which generally aren't
    // applicable to this implementation:
    //
    // EACCES - "No read access to the memory of a futex word."
    // EAGAIN - "The futex owner thread ID of uaddr is about to exit, but has
    //           not yet handled the internal state cleanup. Try again."
    // EFAULT - "A required pointer argument did not point to a valid
    //           user-space address."
    // EINVAL - "The operation in futex_op is one of those that employs a
    //           timeout, but the supplied timeout argument was invalid
    //           (tv_sec was less than zero, or tv_nsec was not less than
    //           1,000,000,000)."
    //          OR
    //          "The operation specified in futex_op employs one or both of
    //           the pointers uaddr and uaddr2, but one of these does not
    //           point to a valid object—that is, the address is not four-
    //           byte-aligned."
    //          OR
    //          "The kernel detected an inconsistency between the user-space
    //           state at uaddr and the kernel state. This indicates either
    //           state corruption or that the kernel found a waiter on uaddr
    //           which is waiting via FUTEX_WAIT or FUTEX_WAIT_BITSET."
    //          OR
    //          "Invalid argument."
    // ENOMEM - "The kernel could not allocate memory to hold state
    //           information."
    // ENOSYS - "Invalid operation specified in futex_op."
    //          OR
    //          "A run-time check determined that the operation is not
    //           available. The PI-futex operations are not implemented on all
    //           architectures and are not supported on some CPU variants."
    // EPERM  - "The caller is not allowed to attach itself to the futex at
    //           uaddr (This may be caused by a state corruption in user
    //           space.)"
    // ESRCH  - "The thread ID in the futex word at uaddr does not exist."
    default:
      // Note: We could maybe retry this operation when given EAGAIN, but this
      //       is more or less supposed to be a quick yes/no.
      return false
    }
  }

  @available(SwiftStdlib 6.0, *)
  @_alwaysEmitIntoClient
  @_transparent
  internal borrowing func _unlock() {
    // Note: This is being TLS cached.
    let selfId = _swift_stdlib_gettid()

    // Attempt to release the lock. We can only atomically release the lock in
    // user-space when there are no other waiters. If there are waiters, the
    // waiter bit is set and we need to inform the kernel that we're unlocking.
    let (exchanged, _) = storage.compareExchange(
      expected: selfId,
      desired: 0,
      successOrdering: .releasing,
      failureOrdering: .relaxed
    )

    if _fastPath(exchanged) {
      // No waiters, unlocked!
      return
    }

    _unlockSlow()
  }

  @available(SwiftStdlib 6.0, *)
  @usableFromInline
  internal borrowing func _unlockSlow() {
    while true {
      switch storage._futexUnlock() {
      case 0:
        // Unlocked!
        return

      // EINTR  - "A FUTEX_WAIT or FUTEX_WAIT_BITSET operation was interrupted
      //           by a signal (see signal(7)). Before Linux 2.6.22, this error
      //           could also be returned for a spurious wakeup; since Linux
      //           2.6.22, this no longer happens."
      case 4:
        continue

      // EPERM  - "The caller does not own the lock represented by the futex
      //           word."
      case 1:
        // TODO: Replace with a colder function / one that takes a StaticString
        fatalError(
          "Call to unlock Mutex on a thread which hasn't acquired the lock"
        )

      // This handles all of the following errors which generally aren't
      // applicable to this implementation:
      //
      // EACCES - "No read access to the memory of a futex word."
      // EFAULT - "A required pointer argument did not point to a valid
      //           user-space address."
      // EINVAL - "The operation in futex_op is one of those that employs a
      //           timeout, but the supplied timeout argument was invalid
      //           (tv_sec was less than zero, or tv_nsec was not less than
      //           1,000,000,000)."
      //          OR
      //          "The operation specified in futex_op employs one or both of
      //           the pointers uaddr and uaddr2, but one of these does not
      //           point to a valid object—that is, the address is not four-
      //           byte-aligned."
      //          OR
      //          "The kernel detected an inconsistency between the user-space
      //           state at uaddr and the kernel state. This indicates either
      //           state corruption or that the kernel found a waiter on uaddr
      //           which is waiting via FUTEX_WAIT or FUTEX_WAIT_BITSET."
      //          OR
      //          "Invalid argument."
      // ENOSYS - "Invalid operation specified in futex_op."
      //          OR
      //          "A run-time check determined that the operation is not
      //           available. The PI-futex operations are not implemented on all
      //           architectures and are not supported on some CPU variants."
      // EPERM  - "The caller is not allowed to attach itself to the futex at
      //           uaddr (This may be caused by a state corruption in user
      //           space.)"
      default:
        // TODO: Replace with a colder function / one that takes a StaticString
        fatalError("Unknown error occured while attempting to release a Mutex")
      }
    }
  }
}