Skip to content

Commit 64b19f7

Browse files
authored
Backdeploy task_wait_future fix to Swift 5.6 (#61254)
* Backdeploy swift_task_future_wait This patch adds the implementation for `swift_task_future_wait` entrypoint to the backdeploy library. This involves pulling in `AsyncTask::waitFuture`, which relies on a fair bit. Please note, this pulls in the `StaticMutex` implementation from Swift 5.6. There are some challenges here. The concurrency version of the `StaticMutex` involves a fairly nasty set of ODR violations in the normal setup. See `public/Concurrency/Mutex.cpp`, which includes the Mutex implementations cpp files directly, while defining a single macro to replace the implementation of swift::fatalError with swift_concurrency_fatalError. We only need the concurrency mutex (at least for now), so I have hard-coded the `swift_concurrency_fatalError` version into this library. If we should need the other implementation, we are forced to include ODR-related undefined behavior. We need symbols from C++, so I've added an implicit linker flag whenever the static library is used, namely, it passes `-lc++` to the linker. Since we only backdeploy on Apple platforms, this should be fine. Some of the platform runtimes we need to backdeploy to have the enter/exitThreadLocalContext functions defined, while others don't. We define our own backdeploy56 shim function that dlsym's the function pointer for these symbols if we have exclusivity checking available. Otherwise, it doesn't do anything. If concurrency exclusivity checking is available, we'll use it, otherwise we wont'. The same dlsym check is done for `swift_task_escalate`. Not all platforms we need to backdeploy to have a concurrency runtime. The symbols that do need to use pieces of the concurrency runtime should not be getting hit when deploying to systems that don't have concurrency. In the event that you've gotten around the language blocking you from calling these symbols and you've managed to call concurrency pieces without using concurrency, we'll abort because something is seriously wrong. * Backdeploy swift_task_future_wait_throwing Drop the remaining pieces in for adding `swift_task_future_wait_throwing`. * Apply task_wait_future fix Actually apply the fix from ef80a31. This deviates slightly from the original patch. AsyncTask::PrivateStorage::_Status() does not exist in the Swift 5.6 library. Instead I am using `AsyncTask::PrivateStorage::Status`. * Workaround missing compiler-rt linking Working around the missing link against compiler-rt in these test. They are a bit brittle as if anything in them uses compiler-rt, they will start failing. The backdeploy 5.6 library uses some symbols from compiler-rt, thus causes them to fail to link. Disabling the runtime compatibility version checking to avoid these symbols. This should be fine for the MicroStdlib test, but we should fix '%target-ld' to handle this better in the future. rdar://100868842
1 parent 6862bc3 commit 64b19f7

File tree

25 files changed

+2499
-30
lines changed

25 files changed

+2499
-30
lines changed

include/swift/Runtime/Exclusivity.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,18 @@ void swift_dumpTrackedAccesses();
7272

7373
#endif
7474

75+
#ifdef SWIFT_COMPATIBILITY56
76+
/// Backdeploy56 shim calls swift_task_enterThreadLocalContext if it is
77+
/// available in the underlying runtime, otherwise does nothing
78+
SWIFT_RUNTIME_EXPORT
79+
void swift_task_enterThreadLocalContextBackdeploy56(char *state);
80+
81+
/// Backdeploy56 shim calls swift_task_exitThreadLocalContext if it is available
82+
/// in the underlying runtime, otherwise does nothing
83+
SWIFT_RUNTIME_EXPORT
84+
void swift_task_exitThreadLocalContextBackdeploy56(char *state);
85+
#else
86+
7587
// When building the concurrency library for back deployment, we rename these
7688
// symbols uniformly so they don't conflict with the real concurrency library.
7789
#ifdef SWIFT_CONCURRENCY_BACK_DEPLOYMENT
@@ -103,6 +115,8 @@ void swift_task_enterThreadLocalContext(char *state);
103115
SWIFT_RUNTIME_EXPORT
104116
void swift_task_exitThreadLocalContext(char *state);
105117

118+
#endif
119+
106120
} // end namespace swift
107121

108122
#endif

stdlib/toolchain/Compatibility56/CMakeLists.txt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,23 @@ set(library_name "swiftCompatibility56")
22

33
include_directories("include/" "${SWIFT_STDLIB_SOURCE_DIR}")
44

5+
add_compile_definitions(SWIFT_COMPATIBILITY56)
56
add_swift_target_library("${library_name}" STATIC
67
Overrides.cpp
78
Concurrency/Task.cpp
9+
Concurrency/TaskLocal.cpp
10+
Concurrency/TaskStatus.cpp
811
Concurrency/Error.cpp
912
Concurrency/Actor.cpp
1013
Concurrency/AsyncLet.cpp
14+
Concurrency/ThreadSanitizer.cpp
15+
Concurrency/TaskAlloc.cpp
16+
Concurrency/MutexPThread.cpp
17+
Runtime/Exclusivity.cpp
1118

1219
TARGET_SDKS ${SWIFT_DARWIN_PLATFORMS}
1320

14-
C_COMPILE_FLAGS ${CXX_COMPILE_FLAGS}
21+
C_COMPILE_FLAGS ${CXX_COMPILE_FLAGS} "-D__STDC_WANT_LIB_EXT1__=1"
1522
LINK_FLAGS ${CXX_LINK_FLAGS}
1623
INCORPORATE_OBJECT_LIBRARIES swiftCompatibilityThreading
1724
SWIFT_COMPILE_FLAGS ${SWIFT_STANDARD_LIBRARY_SWIFT_FLAGS}

stdlib/toolchain/Compatibility56/Concurrency/Actor.cpp

Lines changed: 103 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
#include "Runtime/Concurrency.h"
22
#include "Concurrency/Actor.h"
33
#include "Concurrency/Task.h"
4+
#include "Concurrency/TaskPrivate.h"
5+
#include "Concurrency/VoucherSupport.h"
46

57
#include "swift/Runtime/Atomic.h"
68
#include "swift/Runtime/Casting.h"
7-
#include "Runtime/Threading/Mutex.h"
8-
#include "Runtime/Threading/Once.h"
99
#include "Runtime/Threading/ThreadLocal.h"
10-
#include "Runtime/Threading/ThreadLocalStorage.h"
1110

1211
#include <atomic>
1312
#include <new>
@@ -26,6 +25,84 @@ template <class T> struct Pointer {
2625
T *operator->() const { return Value; }
2726
};
2827

28+
/// A class which encapsulates the information we track about
29+
/// the current thread and active executor.
30+
class ExecutorTrackingInfo {
31+
/// A thread-local variable pointing to the active tracking
32+
/// information about the current thread, if any.
33+
///
34+
/// TODO: this is obviously runtime-internal and therefore not
35+
/// reasonable to make ABI. We might want to also provide a way
36+
/// for generated code to efficiently query the identity of the
37+
/// current executor, in order to do a cheap comparison to avoid
38+
/// doing all the work to suspend the task when we're already on
39+
/// the right executor. It would make sense for that to be a
40+
/// separate thread-local variable (or whatever is most efficient
41+
/// on the target platform).
42+
static SWIFT_RUNTIME_DECLARE_THREAD_LOCAL(
43+
Pointer<ExecutorTrackingInfo>, ActiveInfoInThread,
44+
SWIFT_CONCURRENCY_EXECUTOR_TRACKING_INFO_KEY);
45+
46+
/// The active executor.
47+
ExecutorRef ActiveExecutor = ExecutorRef::generic();
48+
49+
/// Whether this context allows switching. Some contexts do not;
50+
/// for example, we do not allow switching from swift_job_run
51+
/// unless the passed-in executor is generic.
52+
bool AllowsSwitching = true;
53+
54+
VoucherManager voucherManager;
55+
56+
/// The tracking info that was active when this one was entered.
57+
ExecutorTrackingInfo *SavedInfo;
58+
59+
public:
60+
ExecutorTrackingInfo() = default;
61+
62+
ExecutorTrackingInfo(const ExecutorTrackingInfo &) = delete;
63+
ExecutorTrackingInfo &operator=(const ExecutorTrackingInfo &) = delete;
64+
65+
/// Unconditionally initialize a fresh tracking state on the
66+
/// current state, shadowing any previous tracking state.
67+
/// leave() must be called beforet the object goes out of scope.
68+
void enterAndShadow(ExecutorRef currentExecutor) {
69+
ActiveExecutor = currentExecutor;
70+
SavedInfo = ActiveInfoInThread.get();
71+
ActiveInfoInThread.set(this);
72+
}
73+
74+
void swapToJob(Job *job) { voucherManager.swapToJob(job); }
75+
76+
void restoreVoucher(AsyncTask *task) { voucherManager.restoreVoucher(task); }
77+
78+
ExecutorRef getActiveExecutor() const {
79+
return ActiveExecutor;
80+
}
81+
82+
void setActiveExecutor(ExecutorRef newExecutor) {
83+
ActiveExecutor = newExecutor;
84+
}
85+
86+
87+
bool allowsSwitching() const {
88+
return AllowsSwitching;
89+
}
90+
91+
/// Disallow switching in this tracking context. This should only
92+
/// be set on a new tracking info, before any jobs are run in it.
93+
void disallowSwitching() {
94+
AllowsSwitching = false;
95+
}
96+
97+
static ExecutorTrackingInfo *current() {
98+
return ActiveInfoInThread.get();
99+
}
100+
101+
void leave() {
102+
voucherManager.leave();
103+
ActiveInfoInThread.set(SavedInfo);
104+
}
105+
};
29106

30107
class ActiveTask {
31108
/// A thread-local variable pointing to the active tracking
@@ -44,5 +121,28 @@ SWIFT_RUNTIME_DECLARE_THREAD_LOCAL(
44121
ActiveTask::Value,
45122
SWIFT_CONCURRENCY_TASK_KEY);
46123

124+
SWIFT_RUNTIME_DECLARE_THREAD_LOCAL(
125+
Pointer<ExecutorTrackingInfo>,
126+
ExecutorTrackingInfo::ActiveInfoInThread,
127+
SWIFT_CONCURRENCY_EXECUTOR_TRACKING_INFO_KEY);
47128

48129
} // namespace
130+
131+
SWIFT_CC(swift)
132+
AsyncTask *swift::swift_task_getCurrent() {
133+
return ActiveTask::get();
134+
}
135+
136+
AsyncTask *swift::_swift_task_clearCurrent() {
137+
auto task = ActiveTask::get();
138+
ActiveTask::set(nullptr);
139+
return task;
140+
}
141+
142+
void swift::adoptTaskVoucher(AsyncTask *task) {
143+
ExecutorTrackingInfo::current()->swapToJob(task);
144+
}
145+
146+
void swift::restoreTaskVoucher(AsyncTask *task) {
147+
ExecutorTrackingInfo::current()->restoreVoucher(task);
148+
}
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
//===--- MutexPThread.cpp - Supports Mutex.h using PThreads ---------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
//
13+
// Mutex, ConditionVariable, Read/Write lock, and Scoped lock implementations
14+
// using PThreads.
15+
//
16+
//===----------------------------------------------------------------------===//
17+
18+
#if __has_include(<unistd.h>)
19+
#include <unistd.h>
20+
#endif
21+
22+
#if defined(_POSIX_THREADS) && !defined(SWIFT_STDLIB_SINGLE_THREADED_RUNTIME)
23+
24+
// Notes: swift::fatalError is not shared between libswiftCore and libswift_Concurrency
25+
// and libswift_Concurrency uses swift_Concurrency_fatalError instead.
26+
/* #ifndef SWIFT_FATAL_ERROR */
27+
/* #define SWIFT_FATAL_ERROR swift::fatalError */
28+
/* #endif */
29+
30+
// This is the concurrency Mutex implementation. We're forcing the concurrency
31+
// fatalError here.
32+
#include "Concurrency/Error.h"
33+
#define SWIFT_FATAL_ERROR swift_Concurrency_fatalError
34+
35+
#include "Concurrency/Threading/Mutex.h"
36+
37+
#include "swift/Runtime/Debug.h"
38+
#include <errno.h>
39+
#include <stdlib.h>
40+
41+
using namespace swift;
42+
43+
#define reportError(PThreadFunction) \
44+
do { \
45+
int errorcode = PThreadFunction; \
46+
if (errorcode != 0) { \
47+
SWIFT_FATAL_ERROR(/* flags = */ 0, "'%s' failed with error '%s'(%d)\n", \
48+
#PThreadFunction, errorName(errorcode), errorcode); \
49+
} \
50+
} while (false)
51+
52+
#define returnTrueOrReportError(PThreadFunction, returnFalseOnEBUSY) \
53+
do { \
54+
int errorcode = PThreadFunction; \
55+
if (errorcode == 0) \
56+
return true; \
57+
if (returnFalseOnEBUSY && errorcode == EBUSY) \
58+
return false; \
59+
SWIFT_FATAL_ERROR(/* flags = */ 0, "'%s' failed with error '%s'(%d)\n", \
60+
#PThreadFunction, errorName(errorcode), errorcode); \
61+
} while (false)
62+
63+
static const char *errorName(int errorcode) {
64+
switch (errorcode) {
65+
case EINVAL:
66+
return "EINVAL";
67+
case EPERM:
68+
return "EPERM";
69+
case EDEADLK:
70+
return "EDEADLK";
71+
case ENOMEM:
72+
return "ENOMEM";
73+
case EAGAIN:
74+
return "EAGAIN";
75+
case EBUSY:
76+
return "EBUSY";
77+
default:
78+
return "<unknown>";
79+
}
80+
}
81+
82+
void ConditionPlatformHelper::init(pthread_cond_t &condition) {
83+
reportError(pthread_cond_init(&condition, nullptr));
84+
}
85+
86+
void ConditionPlatformHelper::destroy(pthread_cond_t &condition) {
87+
reportError(pthread_cond_destroy(&condition));
88+
}
89+
90+
void ConditionPlatformHelper::notifyOne(pthread_cond_t &condition) {
91+
reportError(pthread_cond_signal(&condition));
92+
}
93+
94+
void ConditionPlatformHelper::notifyAll(pthread_cond_t &condition) {
95+
reportError(pthread_cond_broadcast(&condition));
96+
}
97+
98+
void ConditionPlatformHelper::wait(pthread_cond_t &condition,
99+
pthread_mutex_t &mutex) {
100+
reportError(pthread_cond_wait(&condition, &mutex));
101+
}
102+
103+
void MutexPlatformHelper::init(pthread_mutex_t &mutex, bool checked) {
104+
pthread_mutexattr_t attr;
105+
int kind = (checked ? PTHREAD_MUTEX_ERRORCHECK : PTHREAD_MUTEX_NORMAL);
106+
reportError(pthread_mutexattr_init(&attr));
107+
reportError(pthread_mutexattr_settype(&attr, kind));
108+
reportError(pthread_mutex_init(&mutex, &attr));
109+
reportError(pthread_mutexattr_destroy(&attr));
110+
}
111+
112+
void MutexPlatformHelper::destroy(pthread_mutex_t &mutex) {
113+
reportError(pthread_mutex_destroy(&mutex));
114+
}
115+
116+
void MutexPlatformHelper::lock(pthread_mutex_t &mutex) {
117+
reportError(pthread_mutex_lock(&mutex));
118+
}
119+
120+
void MutexPlatformHelper::unlock(pthread_mutex_t &mutex) {
121+
reportError(pthread_mutex_unlock(&mutex));
122+
}
123+
124+
bool MutexPlatformHelper::try_lock(pthread_mutex_t &mutex) {
125+
returnTrueOrReportError(pthread_mutex_trylock(&mutex),
126+
/* returnFalseOnEBUSY = */ true);
127+
}
128+
129+
#if HAS_OS_UNFAIR_LOCK
130+
131+
void MutexPlatformHelper::init(os_unfair_lock &lock, bool checked) {
132+
(void)checked; // Unfair locks are always checked.
133+
lock = OS_UNFAIR_LOCK_INIT;
134+
}
135+
136+
void MutexPlatformHelper::destroy(os_unfair_lock &lock) {}
137+
138+
void MutexPlatformHelper::lock(os_unfair_lock &lock) {
139+
os_unfair_lock_lock(&lock);
140+
}
141+
142+
void MutexPlatformHelper::unlock(os_unfair_lock &lock) {
143+
os_unfair_lock_unlock(&lock);
144+
}
145+
146+
bool MutexPlatformHelper::try_lock(os_unfair_lock &lock) {
147+
return os_unfair_lock_trylock(&lock);
148+
}
149+
150+
#endif
151+
152+
void ReadWriteLockPlatformHelper::init(pthread_rwlock_t &rwlock) {
153+
reportError(pthread_rwlock_init(&rwlock, nullptr));
154+
}
155+
156+
void ReadWriteLockPlatformHelper::destroy(pthread_rwlock_t &rwlock) {
157+
reportError(pthread_rwlock_destroy(&rwlock));
158+
}
159+
160+
void ReadWriteLockPlatformHelper::readLock(pthread_rwlock_t &rwlock) {
161+
reportError(pthread_rwlock_rdlock(&rwlock));
162+
}
163+
164+
bool ReadWriteLockPlatformHelper::try_readLock(pthread_rwlock_t &rwlock) {
165+
returnTrueOrReportError(pthread_rwlock_tryrdlock(&rwlock),
166+
/* returnFalseOnEBUSY = */ true);
167+
}
168+
169+
void ReadWriteLockPlatformHelper::writeLock(pthread_rwlock_t &rwlock) {
170+
reportError(pthread_rwlock_wrlock(&rwlock));
171+
}
172+
173+
bool ReadWriteLockPlatformHelper::try_writeLock(pthread_rwlock_t &rwlock) {
174+
returnTrueOrReportError(pthread_rwlock_trywrlock(&rwlock),
175+
/* returnFalseOnEBUSY = */ true);
176+
}
177+
178+
void ReadWriteLockPlatformHelper::readUnlock(pthread_rwlock_t &rwlock) {
179+
reportError(pthread_rwlock_unlock(&rwlock));
180+
}
181+
182+
void ReadWriteLockPlatformHelper::writeUnlock(pthread_rwlock_t &rwlock) {
183+
reportError(pthread_rwlock_unlock(&rwlock));
184+
}
185+
#endif

0 commit comments

Comments
 (0)