mirror of
https://github.com/apple/swift.git
synced 2026-10-28 21:15:56 +01:00
Introduce a hook into the PAL that will be called to report error information for fatal errors, precondition failures, and assertions. Prior to this, we just printed them via standard output and trapped, and there was no way to customize this behavior. In the POSIX platform shim, implement these on top of vprintf and fflush. It does effectively extend how much of POSIX we need under the platform layer by default, but avoids having to do a bunch of manual printing of numbers (for example) in the shim. Implements rdar://138206068.
654 lines
24 KiB
Swift
654 lines
24 KiB
Swift
//===----------------------------------------------------------------------===//
|
||
//
|
||
// This source file is part of the Swift.org open source project
|
||
//
|
||
// Copyright (c) 2014 - 2025 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
|
||
//
|
||
//===----------------------------------------------------------------------===//
|
||
///
|
||
/// Dynamic exclusivity checking.
|
||
///
|
||
//===----------------------------------------------------------------------===//
|
||
|
||
import SwiftShims
|
||
|
||
fileprivate let ValueBufferSize = unsafe 3 * MemoryLayout<UnsafeRawPointer>.stride
|
||
|
||
fileprivate let TrackingFlag: UInt = 0x20
|
||
fileprivate let ActionMask: UInt = 0x1
|
||
|
||
typealias AccessPointer = UnsafeMutablePointer<Access>
|
||
@unsafe struct Access {
|
||
|
||
enum Action: UInt {
|
||
case read
|
||
case modify
|
||
}
|
||
|
||
struct NextAndAction {
|
||
private var rawValue: UInt
|
||
|
||
var action: Action {
|
||
get {
|
||
Action(rawValue: rawValue & ActionMask)!
|
||
}
|
||
set {
|
||
rawValue = (rawValue & ~ActionMask) | newValue.rawValue
|
||
}
|
||
}
|
||
|
||
var next: AccessPointer? {
|
||
get { unsafe UnsafeMutablePointer(bitPattern: rawValue & ~ActionMask) }
|
||
set { rawValue = UInt(bitPattern: newValue) | (rawValue & ActionMask) }
|
||
}
|
||
}
|
||
|
||
var location: UnsafeRawPointer?
|
||
var pc: UnsafeRawPointer?
|
||
var nextAndAction: NextAndAction
|
||
|
||
var action: Action {
|
||
get { unsafe nextAndAction.action }
|
||
set { unsafe nextAndAction.action = newValue }
|
||
}
|
||
|
||
var next: AccessPointer? {
|
||
get { unsafe nextAndAction.next }
|
||
set { unsafe nextAndAction.next = newValue }
|
||
}
|
||
|
||
static func from(rawPointer: UnsafeMutableRawPointer?) -> AccessPointer? {
|
||
guard let rawPointer = unsafe rawPointer else { return nil }
|
||
return unsafe rawPointer.assumingMemoryBound(to: Access.self)
|
||
}
|
||
|
||
@inline(__always)
|
||
static func search(
|
||
buffer: UnsafeMutableRawPointer,
|
||
location: UnsafeRawPointer,
|
||
pc: UnsafeRawPointer?,
|
||
action: Action,
|
||
inserting: Bool,
|
||
head: inout AccessPointer?
|
||
) {
|
||
var cursor = unsafe head
|
||
while let nextPtr = unsafe cursor {
|
||
if unsafe nextPtr.pointee.location == location {
|
||
if unsafe nextPtr.pointee.action == .modify || action == .modify {
|
||
#if !$Embedded
|
||
unsafe _swift_reportExclusivityConflict(
|
||
nextPtr.pointee.action.rawValue,
|
||
nextPtr.pointee.pc,
|
||
action.rawValue,
|
||
pc,
|
||
location)
|
||
#else
|
||
unsafe _embeddedReportExclusivityViolation(
|
||
oldAction: nextPtr.pointee.action,
|
||
oldPC: nextPtr.pointee.pc,
|
||
newAction: action,
|
||
newPC: pc,
|
||
pointer: location)
|
||
#endif
|
||
}
|
||
}
|
||
unsafe cursor = nextPtr.pointee.next
|
||
}
|
||
|
||
if inserting {
|
||
guard let access = unsafe Access.from(rawPointer: buffer) else {
|
||
nullAccessBuffer()
|
||
}
|
||
|
||
unsafe access.pointee.location = location
|
||
unsafe access.pointee.pc = pc
|
||
unsafe access.pointee.action = action
|
||
|
||
unsafe access.pointee.next = head
|
||
unsafe head = access
|
||
}
|
||
}
|
||
|
||
@inline(__always)
|
||
static func remove(access: AccessPointer, head: inout AccessPointer?) {
|
||
var cursor = unsafe head
|
||
var previous: AccessPointer? = nil
|
||
while let nextPtr = unsafe cursor {
|
||
if unsafe nextPtr == access {
|
||
if let previous = unsafe previous {
|
||
unsafe previous.pointee.next = access.pointee.next
|
||
} else {
|
||
unsafe head = access.pointee.next
|
||
}
|
||
return
|
||
}
|
||
unsafe previous = nextPtr
|
||
unsafe cursor = nextPtr.pointee.next
|
||
}
|
||
|
||
unsafe accessNotFound(access)
|
||
}
|
||
|
||
@inline(__always)
|
||
static func findParent(
|
||
access: AccessPointer,
|
||
child: AccessPointer?
|
||
) -> AccessPointer? {
|
||
var cursor = unsafe access
|
||
while let next = unsafe cursor.pointee.next {
|
||
if unsafe next == child {
|
||
return unsafe cursor
|
||
}
|
||
unsafe cursor = next
|
||
}
|
||
|
||
// If we were searching for nil, then we found it.
|
||
if unsafe child == nil {
|
||
return unsafe cursor
|
||
}
|
||
|
||
// If we were searching for a non-nil node, we didn't find it.
|
||
return nil
|
||
}
|
||
|
||
static func forEach(_ head: AccessPointer?, _ action: (Access) -> Void) {
|
||
var cursor = unsafe head
|
||
while let nextPtr = unsafe cursor {
|
||
unsafe action(nextPtr.pointee)
|
||
unsafe cursor = nextPtr.pointee.next
|
||
}
|
||
}
|
||
}
|
||
|
||
fileprivate var accessHead: AccessPointer? {
|
||
get { unsafe Access.from(rawPointer: _swift_getExclusivityTLS()) }
|
||
set { unsafe _swift_setExclusivityTLS(newValue) }
|
||
}
|
||
|
||
@c(swift_beginAccess)
|
||
@usableFromInline
|
||
@unsafe
|
||
internal func swift_beginAccess(
|
||
pointer: UnsafeRawPointer,
|
||
buffer: UnsafeMutableRawPointer,
|
||
flags: UInt,
|
||
pc: UnsafeRawPointer?
|
||
) {
|
||
// Make sure that Access fits in the value buffer. This should be a static
|
||
// assert, but we'll settle for a precondition for now. The optimizer should
|
||
// eliminate the check when it's true.
|
||
precondition(unsafe MemoryLayout<Access>.size <= ValueBufferSize)
|
||
|
||
#if !$Embedded
|
||
// If exclusivity checking is disabled, mark this access as untracked by
|
||
// putting nil into the access buffer's pointer field, and return.
|
||
if _swift_disableExclusivityChecking {
|
||
unsafe Access.from(rawPointer: buffer)?.pointee.location = nil
|
||
return
|
||
}
|
||
#endif
|
||
|
||
guard let action = Access.Action(rawValue: flags & ActionMask) else {
|
||
invalidFlags(flags)
|
||
}
|
||
|
||
let isTracking = (flags & TrackingFlag) != 0
|
||
|
||
unsafe Access.search(
|
||
buffer: buffer,
|
||
location: pointer,
|
||
pc: pc ?? _swift_stdlib_get_return_address(),
|
||
action: action,
|
||
inserting: isTracking,
|
||
head: &accessHead)
|
||
}
|
||
|
||
@c(swift_endAccess)
|
||
@usableFromInline
|
||
@unsafe
|
||
internal func swift_endAccess(buffer: UnsafeMutableRawPointer) {
|
||
guard let access = unsafe Access.from(rawPointer: buffer) else {
|
||
nullAccessBuffer()
|
||
}
|
||
|
||
// If the pointer field is nil then we're not tracking this access.
|
||
if unsafe access.pointee.location == nil {
|
||
return
|
||
}
|
||
unsafe Access.remove(access: access, head: &accessHead)
|
||
}
|
||
|
||
#if INTERNAL_CHECKS_ENABLED
|
||
@c(swift_dumpTrackedAccesses)
|
||
@usableFromInline
|
||
@unsafe
|
||
@_unavailableInEmbedded
|
||
internal func swift_dumpTrackedAccesses() {
|
||
if let head = unsafe accessHead {
|
||
unsafe Access.forEach(head) {
|
||
unsafe _swift_stdlib_fputs_stderr(" Access. " +
|
||
"Pointer: \($0.location, default: "<null>")." +
|
||
"PC: \($0.pc, default: "<null>"). " +
|
||
"AccessAction: \($0.action)\n")
|
||
}
|
||
} else {
|
||
unsafe _swift_stdlib_fputs_stderr(" No Accesses.\n")
|
||
}
|
||
}
|
||
#endif
|
||
|
||
//===----------------------------------------------------------------------===//
|
||
// Exclusivity tracking across task suspension
|
||
//===----------------------------------------------------------------------===//
|
||
//
|
||
// High Level Algorithm Description
|
||
// --------------------------------
|
||
//
|
||
// With the introduction of Concurrency, we add additional requirements to our
|
||
// exclusivity model:
|
||
//
|
||
// * We want tasks to have a consistent exclusivity access set across
|
||
// suspensions/resumptions. This ensures that any exclusive accesses began
|
||
// before a Task suspended are properly flagged after the Task is resumed
|
||
// even if the Task is resumed on a different thread.
|
||
//
|
||
// * If synchronous code calls a subroutine that creates a set of tasks to
|
||
// perform work and then blocks, we want the runtime to ensure that the tasks
|
||
// respect exclusivity accesses from the outside synchronous code.
|
||
//
|
||
// * We on purpose define exclusive access to the memory from multiple tasks as
|
||
// undefined behavior since that would be an additional feature that needs to
|
||
// be specifically designed in the future.
|
||
//
|
||
// * We assume that an access in synchronous code will never be ended in
|
||
// asynchronous code.
|
||
//
|
||
// * We additional require that our design leaves the exclusivity runtime
|
||
// unaware of any work we are doing here. All it should be aware of is the
|
||
// current thread local access set and adding/removing from that access set.
|
||
//
|
||
// We implement these requirements by reserving two pointers in each Task. The
|
||
// first pointer points at the head access of the linked list of accesses of
|
||
// the Task and the second pointer points at the end of the linked list of
|
||
// accesses of the task. We will for the discussion ahead call the first
|
||
// pointer TaskFirstAccess and the second TaskLastAccess. This allows us to
|
||
// modify the current TLV single linked list to include/remove the tasks’s
|
||
// access by updating a few nodes in the linked list when the task is running
|
||
// and serialize the task’s current access set and restoring to be head the
|
||
// original synchronous access set head when the task is running. This
|
||
// naturally fits a push/pop access set sort of schema where every time a task
|
||
// starts, we push its access set onto the local TLV and then pop it off when
|
||
// the task is suspended. This ensures that the task gets the current
|
||
// synchronous set of accesses and other Tasks do not see the accesses of the
|
||
// specific task providing task isolation.
|
||
//
|
||
// The cases can be described via the following table:
|
||
//
|
||
// +------+--------------------+--------------------+--------------------+
|
||
// | Case | Live Task Accesses | Live Sync Accesses | Live Task Accesses |
|
||
// | | When Push | When Push | When Pop |
|
||
// |------+--------------------+--------------------+--------------------|
|
||
// | 1 | F | F | F |
|
||
// | 2 | F | F | T |
|
||
// | 3 | F | T | F |
|
||
// | 4 | F | T | T |
|
||
// | 5 | T | F | F |
|
||
// | 6 | T | F | T |
|
||
// | 7 | T | T | F |
|
||
// | 8 | T | T | T |
|
||
// +------+--------------------+--------------------+--------------------+
|
||
//
|
||
// We mark the end of each title below introducing a case with 3 T/F to enable
|
||
// easy visual matching with the chart
|
||
//
|
||
// Case 1: Task/Sync do not have initial accesses and no Task accesses are
|
||
// created while running (F,F,F)
|
||
//
|
||
// In this case, TBegin and TEnd are both initially nil.
|
||
//
|
||
// When we push, we see that the current exclusivity TLV has a null head and
|
||
// leave it so. We set TBegin and TEnd as nil while running.
|
||
//
|
||
// When we pop, see that the exclusivity TLV is still nil, so we just leave
|
||
// TBegin and TEnd alone still as nil.
|
||
//
|
||
// This means that code that does not have any exclusive accesses do not have
|
||
// any runtime impact.
|
||
//
|
||
// Case 2: Task/Sync do not have initial access, but Task accesses are created
|
||
// while running (F, F, T)
|
||
//
|
||
// In this case, TBegin and TEnd are both initially nil.
|
||
//
|
||
// When we push, we see that the current exclusivity TLV has a null head. So, we
|
||
// leave TBegin and TEnd as nil while the task is running.
|
||
//
|
||
// When we pop, we see that the exclusivity TLV has a non-null head. In that
|
||
// case, we walk the list to find the last node and update TBegin to point at
|
||
// the current head, TEnd to point at that last node, and then set the TLV head
|
||
// to be nil.
|
||
//
|
||
// Case 3: Task does not have initial accesses, but Sync does, and new Task
|
||
// accesses are not created while running (F, T, F)
|
||
//
|
||
// In this case, TBegin and TEnd are both initially nil.
|
||
//
|
||
// When we push, we look at the TLV and see our initial synchronous thread was
|
||
// tracking accesses. In this case, we leave the TLV pointing at the
|
||
// SyncAccessHead and set TBegin to SyncAccessHead and leave TEnd as nil.
|
||
//
|
||
// When we pop, we see that TBegin (which we know has the old synchronous head
|
||
// in it) is equal to the TLV so we know that we did not create any new Task
|
||
// accesses. Then we set TBegin to nil and return. NOTE: TEnd is nil the entire
|
||
// time in this scenario.
|
||
//
|
||
// Case 4: Task does not have initial accesses, but Sync does, and new Task
|
||
// accesses are created while running (F, T, T)
|
||
//
|
||
// In this case, TBegin and TEnd are both initially nil. When we push, we look
|
||
// at the TLV and we see our initial synchronous thread was tracking accesses.
|
||
// In this case, we leave the TLV pointing at the SyncAccessHead and set TBegin
|
||
// to SyncAccessHead and leave TEnd as nil.
|
||
//
|
||
// When we pop, we see that the TLV and TBegin differ now. We know that this
|
||
// means that our task introduced new accesses. So, we search down from the head
|
||
// of the AccessSet TLV until we find TBegin . The node before TBegin is our new
|
||
// TEnd pointer. We set TBegin to then have the value of head, TEnd to be the
|
||
// new TEnd pointer, set TEnd’s next to be nil and make head the old value of
|
||
// TBegin.
|
||
//
|
||
// Case 5: Task has an initial access set, but Sync does not have initial
|
||
// accesses and no Task accesses exist after running (T,F,F)
|
||
//
|
||
// In this case, TBegin and TEnd are both initially set to non-null values. When
|
||
// we push, we look at the current TLV head and see that the TLV head is nil. We
|
||
// then set TLV head to be TBegin and set TBegin to be nil to signal the
|
||
// original synchronous TLV head was nil.
|
||
//
|
||
// When we pop, we see that TBegin is currently nil, so we know the synchronous
|
||
// access set was empty. We also know that despite us starting with a task
|
||
// access set, those accesses must have completed while the task was running
|
||
// since the access set is empty when we pop.
|
||
//
|
||
// Case 6: Task has initial accesses, sync does not have initial access, and
|
||
// Task access set is modified while running (T, F, T)
|
||
//
|
||
// In this case, TBegin and TEnd are both initially set to non-null values. When
|
||
// we push, we look at the current TLV head and see that the TLV head is nil. We
|
||
// then set TLV head to be TBegin and set TBegin to be nil to signal the
|
||
// original synchronous TLV head was nil. We have no requirement on TEnd now in
|
||
// this case but set it to nil, to track flags if we want to in the future in a
|
||
// different runtime.
|
||
//
|
||
// When we pop, we see that TBegin is currently nil, so we know the synchronous
|
||
// access set was empty. We do not have a way to know how/if we modified the
|
||
// Task AccessSet, so we walked the list to find the last node. We then make
|
||
// TBegin head, TEnd the last node, and set the TLV to be nil again.
|
||
//
|
||
// Case 7: Task has initial accesses, Sync has initial accesses, and new Task
|
||
// accesses are not created while running (T, T, F)
|
||
//
|
||
// In this case, TBegin and TEnd are both initially set to non-null values. When
|
||
// we push, we look at the current TLV head and see that the TLV head is a valid
|
||
// pointer. We then set TLV head to be the current value of TBegin, make
|
||
// TEnd->next the old head value and stash the old head value into TBegin. We
|
||
// have no requirement on TEnd now in this case.
|
||
//
|
||
// When we pop, we see that TBegin is not nil, so we know the synchronous access
|
||
// set had live accesses. We do not have a way to know how/if we modified the
|
||
// Task AccessSet, so we walked the list to find TBegin (which is old sync
|
||
// head). Noting that the predecessor node of old sync head’s node will be the
|
||
// end of the task’s current access set, we set TLV to point at the node we
|
||
// found in TBegin, set TBegin to the current TLV head, set TEnd to that
|
||
// predecessor node of the current TLV head and set TEnd->next to be nil.
|
||
//
|
||
// Case 8: Task has initial accesses, Sync does, and Task accesses is modified
|
||
// while running (T, T, T)
|
||
//
|
||
// In this case, TBegin and TEnd are both initially set to non-null values.
|
||
//
|
||
// When we push, we look at the current TLV head and see that the TLV head is a
|
||
// valid pointer. We then set TLV head to be the current value of TBegin, make
|
||
// TEnd->next the old head value and stash the old head value into TBegin. We
|
||
// have no requirement on TEnd now in this case.
|
||
//
|
||
// When we pop, we see that TBegin is not nil, so we know the synchronous access
|
||
// set had live accesses. We do not have a way to know how/if we modified the
|
||
// Task AccessSet, so we walked the list to find TBegin (which is old sync
|
||
// head). Noting that the predecessor node of old sync head’s node will be the
|
||
// end of the task’s current access set, we set TLV to point at the node we
|
||
// found in TBegin, set TBegin to the current TLV head, set TEnd to that
|
||
// predecessor node of the current TLV head and set TEnd->next to be nil.
|
||
|
||
#if !$Embedded
|
||
|
||
/// Wrapper around the reserved exclusivity space in a Task. The reserved space
|
||
/// is two pointers, which we use as two AccessPointer? values representing head
|
||
/// and tail.
|
||
@unsafe
|
||
private struct TaskExclusivityContext {
|
||
var state: UnsafeMutablePointer<AccessPointer?>
|
||
|
||
var head: AccessPointer? {
|
||
get { unsafe state[0] }
|
||
nonmutating set { unsafe state[0] = newValue }
|
||
}
|
||
|
||
var tail: AccessPointer? {
|
||
get { unsafe state[1] }
|
||
nonmutating set { unsafe state[1] = newValue }
|
||
}
|
||
|
||
init(_ ptr: UnsafeMutableRawPointer) {
|
||
unsafe state = ptr.assumingMemoryBound(to: AccessPointer?.self)
|
||
}
|
||
}
|
||
|
||
/// Push the task's exclusivity access set onto the thread-local list. Called
|
||
/// when a task initializes or resumes. `state` points at the task's two-word
|
||
/// `(head, tail)` buffer.
|
||
@c(swift_task_enterThreadLocalContext)
|
||
@usableFromInline
|
||
@unsafe
|
||
internal func swift_task_enterThreadLocalContext(
|
||
state: UnsafeMutableRawPointer
|
||
) {
|
||
let ctx = unsafe TaskExclusivityContext(state)
|
||
let tlsHead = unsafe accessHead
|
||
|
||
// First handle all of the cases where our task does not start without an
|
||
// initial access set.
|
||
//
|
||
// Handles push cases 1-4.
|
||
if unsafe ctx.head == nil {
|
||
// In this case, the current synchronous context is not tracking any
|
||
// accesses. So the tlsCtx and our initial access set are all nullptr, so we
|
||
// can just return early.
|
||
//
|
||
// Handles push cases 1-2.
|
||
guard let tlsHead = unsafe tlsHead else { return }
|
||
|
||
// Ok, our task isn't tracking any task specific accesses, but our tlsCtx
|
||
// was tracking accesses. Leave the tlsCtx alone at this point and set our
|
||
// state's begin access to be tlsCtx head. We leave our access set tail as
|
||
// nullptr.
|
||
//
|
||
// Handles push cases 3-4.
|
||
unsafe ctx.head = tlsHead
|
||
return
|
||
}
|
||
|
||
// The task has its own access set. Handles push cases 5-8.
|
||
|
||
// At this point, we know that we did have an initial access set. Both access
|
||
// set pointers are valid.
|
||
//
|
||
// Handles push cases 5-8.
|
||
|
||
// Now check if our synchronous code had any accesses. If not, we set head and
|
||
// tail to be nullptr and set the tlsCtx to point to head.
|
||
//
|
||
// Handles push cases 5-6.
|
||
guard let tlsHead = unsafe tlsHead else {
|
||
unsafe accessHead = ctx.head
|
||
unsafe ctx.head = nil
|
||
unsafe ctx.tail = nil
|
||
return
|
||
}
|
||
|
||
// In this final case, we found that our task had its own access set and our
|
||
// tlsCtx did as well. So we then set the Task's head to be the new TLV head,
|
||
// set tail->next to point at old head and stash oldhead into the task ctx.
|
||
//
|
||
// Handles push cases 7-8.
|
||
let oldHead = unsafe tlsHead
|
||
let tail = unsafe ctx.tail
|
||
unsafe accessHead = ctx.head
|
||
unsafe tail?.pointee.next = oldHead
|
||
unsafe ctx.head = oldHead
|
||
unsafe ctx.tail = nil
|
||
}
|
||
|
||
/// Pop the task's exclusivity access set back out of the thread-local list,
|
||
/// restoring the synchronous head. Called when a task suspends. `state` points
|
||
/// at the task's two-word `(head, tail)` buffer.
|
||
@c(swift_task_exitThreadLocalContext)
|
||
@usableFromInline
|
||
@unsafe
|
||
internal func swift_task_exitThreadLocalContext(
|
||
state: UnsafeMutableRawPointer
|
||
) {
|
||
let ctx = unsafe TaskExclusivityContext(state)
|
||
let tlsHead = unsafe accessHead
|
||
|
||
// First check our ctx to see if we were tracking a previous synchronous
|
||
// head. If we don't then we know that our synchronous thread was not
|
||
// initially tracking any accesses.
|
||
//
|
||
// Handles pop cases 1,2,5,6
|
||
guard let oldHead = unsafe ctx.head else {
|
||
// Then check if we are currently tracking an access set in the TLS. If we
|
||
// aren't, then we know that either we did not start with a task specific
|
||
// access set /or/ we did start but all of those accesses ended while the
|
||
// task was running. In either case, when we pushed initially, we set
|
||
// TBegin, TEnd to be nullptr already and since oldHead is already nullptr,
|
||
// we can just exit.
|
||
//
|
||
// Handles pop cases 1,5
|
||
guard let tlsHead = unsafe tlsHead else {
|
||
_internalInvariant(
|
||
unsafe ctx.tail == nil,
|
||
"tail should have been nil since push")
|
||
return
|
||
}
|
||
|
||
// In this case, we did find that we had live accesses. Since we know we
|
||
// did not start with any synchronous accesses, these accesses must all be
|
||
// from the given task. So, we first find the tail of the current TLS linked
|
||
// list, then set the Task access set head to accessSet, the Task accessSet
|
||
// tail to the TLS linked list tail and set tlsCtx.accessSet to nullptr.
|
||
//
|
||
// Handles pop cases 2,6
|
||
let newTail = unsafe Access.findParent(access: tlsHead, child: nil)
|
||
_internalInvariant(unsafe newTail != nil, "Failed to find tail?!")
|
||
unsafe accessHead = nil
|
||
unsafe ctx.head = tlsHead
|
||
unsafe ctx.tail = newTail
|
||
return
|
||
}
|
||
|
||
// Otherwise, we know that we /were/ tracking accesses from a previous
|
||
// synchronous context. So we need to unmerge our task specific state from the
|
||
// exclusivity access set.
|
||
//
|
||
// Handles pop cases 3,4,7,8.
|
||
|
||
// First check if the current head tlsAccess is the same as our oldHead. In
|
||
// such a case, we do not have new task accesses to update. So just set task
|
||
// access head/tail to nullptr. The end access should be nullptr.
|
||
//
|
||
// Handles pop case 3.
|
||
if unsafe tlsHead == oldHead {
|
||
unsafe ctx.head = nil
|
||
unsafe ctx.tail = nil
|
||
return
|
||
}
|
||
|
||
// Otherwise, we have task specific accesses that we need to serialize into
|
||
// the task's state. We currently can not tell if the Task actually modified
|
||
// the task list beyond if the task list is empty. So we have to handle case 7
|
||
// here (unfortunately).
|
||
//
|
||
// NOTE: If we could tell if the Task modified its access set while running,
|
||
// we could perhaps avoid the search for newEnd.
|
||
//
|
||
// Handles pop cases 4,7,8.
|
||
let newHead = unsafe tlsHead
|
||
var newEnd: AccessPointer? = nil
|
||
if let tlsHead = unsafe tlsHead {
|
||
unsafe newEnd = Access.findParent(access: tlsHead, child: oldHead)
|
||
}
|
||
unsafe accessHead = oldHead
|
||
unsafe newEnd?.pointee.next = nil
|
||
unsafe ctx.head = newHead
|
||
unsafe ctx.tail = newEnd
|
||
}
|
||
|
||
#endif
|
||
|
||
@inline(never)
|
||
fileprivate func invalidFlags(_ flags: UInt) -> Never {
|
||
#if !$Embedded
|
||
reportExclusivityError("Internal exclusivity error",
|
||
"unable to construct action from flags \(flags)")
|
||
#else
|
||
fatalError("Internal exclusivity error: unable to construct action from flags")
|
||
#endif
|
||
}
|
||
|
||
@inline(never)
|
||
fileprivate func accessNotFound(_ access: AccessPointer) -> Never {
|
||
#if !$Embedded
|
||
unsafe reportExclusivityError("Internal exclusivity error",
|
||
"didn't find exclusive access buffer \(access)")
|
||
#else
|
||
fatalError("Internal exclusivity error: didn't find exclusive access buffer")
|
||
#endif
|
||
}
|
||
|
||
@inline(never)
|
||
fileprivate func nullAccessBuffer() -> Never {
|
||
#if !$Embedded
|
||
reportExclusivityError("Internal exclusivity error", "NULL access buffer")
|
||
#else
|
||
fatalError("Internal exclusivity error: NULL access buffer")
|
||
#endif
|
||
}
|
||
|
||
@_unavailableInEmbedded
|
||
fileprivate func reportExclusivityError(
|
||
_ prefix: StaticString, _ message: String
|
||
) -> Never {
|
||
prefix.withUTF8Buffer { prefixBuffer in
|
||
var message = message
|
||
message.withUTF8 { messageBuffer in
|
||
#if !$Embedded
|
||
unsafe _swift_stdlib_reportFatalError(
|
||
prefixBuffer.baseAddress!, CInt(prefixBuffer.count),
|
||
messageBuffer.baseAddress!, CInt(messageBuffer.count),
|
||
0)
|
||
#else
|
||
unsafe _embeddedReportFatalError(
|
||
kind: .fatal(),
|
||
message: messageBuffer
|
||
)
|
||
#endif
|
||
}
|
||
}
|
||
Builtin.int_trap()
|
||
}
|