Skip to content

Commit d9aed16

Browse files
committed
[Threading][TSan] Fix TSan errors from lazy init on Linux.
Move the TSan functionality from Concurrency into Threading. Use it in the Linux ulock implementation so that TSan knows about ulock and will tolerate the newer swift_once implementation that uses it. This should also slightly improve the performance of Concurrency, since the tsan tests will be inlined rather than it always doing a subroutine call. rdar://110665213
1 parent 32b31b9 commit d9aed16

File tree

12 files changed

+283
-68
lines changed

12 files changed

+283
-68
lines changed

cmake/modules/AddSwiftUnittests.cmake

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@ function(add_swift_unittest test_dirname)
5656

5757
string(TOUPPER "${SWIFT_SDK_${SWIFT_HOST_VARIANT_SDK}_THREADING_PACKAGE}" _threading_package)
5858
target_compile_definitions("${test_dirname}" PRIVATE
59-
"SWIFT_THREADING_${_threading_package}")
59+
"SWIFT_THREADING_${_threading_package}"
60+
"SWIFT_THREADING_STATIC")
6061

6162
if(NOT SWIFT_COMPILER_IS_MSVC_LIKE)
6263
if(SWIFT_USE_LINKER)

include/swift/Threading/Impl/Linux/ulock.h

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@
3434
#include <atomic>
3535
#include <cstdint>
3636

37+
#include "swift/Threading/ThreadSanitizer.h"
38+
3739
namespace swift {
3840
namespace threading_impl {
3941
namespace linux {
@@ -59,31 +61,38 @@ inline void ulock_lock(ulock_t *lock) {
5961
const ulock_t tid = ulock_get_tid();
6062
do {
6163
ulock_t zero = 0;
62-
if (ulock_fastpath(__atomic_compare_exchange_n(
63-
lock, &zero, tid, true, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED)))
64-
return;
65-
64+
if (ulock_fastpath(__atomic_compare_exchange_n(lock, &zero, tid,
65+
true, __ATOMIC_ACQUIRE,
66+
__ATOMIC_RELAXED)))
67+
break;
6668
} while (ulock_futex(lock, FUTEX_LOCK_PI) != 0);
69+
70+
tsan::acquire(lock);
6771
}
6872

6973
inline bool ulock_trylock(ulock_t *lock) {
7074
ulock_t zero = 0;
7175
if (ulock_fastpath(__atomic_compare_exchange_n(lock, &zero, ulock_get_tid(),
7276
true, __ATOMIC_ACQUIRE,
73-
__ATOMIC_RELAXED)))
77+
__ATOMIC_RELAXED))
78+
|| ulock_futex(lock, FUTEX_TRYLOCK_PI) == 0) {
79+
tsan::acquire(lock);
7480
return true;
81+
}
7582

76-
return ulock_futex(lock, FUTEX_TRYLOCK_PI) == 0;
83+
return false;
7784
}
7885

7986
inline void ulock_unlock(ulock_t *lock) {
87+
tsan::release(lock);
88+
8089
const ulock_t tid = ulock_get_tid();
8190
do {
8291
ulock_t expected = tid;
83-
if (ulock_fastpath(__atomic_compare_exchange_n(
84-
lock, &expected, 0, true, __ATOMIC_RELEASE, __ATOMIC_RELAXED)))
85-
return;
86-
92+
if (ulock_fastpath(__atomic_compare_exchange_n(lock, &expected, 0,
93+
true, __ATOMIC_RELEASE,
94+
__ATOMIC_RELAXED)))
95+
break;
8796
} while (ulock_futex(lock, FUTEX_UNLOCK_PI) != 0);
8897
}
8998

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
//===--- ThreadSanitizer.h - Thread Sanitizer support --------- -*- C++ -*-===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2023 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+
// Helper functions for code that needs to integrate with the thread
14+
// sanitizer. In particular, TSan can't see inside the runtime libraries,
15+
// so we occasionally need to give it a hint that we're doing synchronization
16+
// in order to avoid false positives.
17+
//
18+
//===----------------------------------------------------------------------===//
19+
20+
#ifndef SWIFT_THREADING_THREAD_SANITIZER_H
21+
#define SWIFT_THREADING_THREAD_SANITIZER_H
22+
23+
#include "swift/shims/Visibility.h"
24+
25+
namespace swift {
26+
27+
#if defined(_WIN32) || defined(__wasi__) || !__has_include(<dlfcn.h>)
28+
29+
#define SWIFT_THREADING_TSAN_SUPPORT 0
30+
31+
namespace tsan {
32+
33+
inline bool enabled() { return false; }
34+
template <typename T> T *acquire(T *ptr) { return ptr; }
35+
template <typename T> T *release(T *ptr) { return ptr; }
36+
37+
} // namespace tsan
38+
#else
39+
40+
#define SWIFT_THREADING_TSAN_SUPPORT 1
41+
42+
// If we're static linking to libswiftThreading.a, these symbols can come
43+
// from there. If, on the other hand, we're dynamically linked, we want
44+
// to get them from libswiftCore.dylib instead.
45+
#if SWIFT_THREADING_STATIC
46+
#define SWIFT_THREADING_EXPORT extern "C"
47+
#else
48+
#define SWIFT_THREADING_EXPORT SWIFT_RUNTIME_EXPORT
49+
#endif
50+
51+
namespace threading_impl {
52+
53+
SWIFT_THREADING_EXPORT bool _swift_tsan_enabled;
54+
SWIFT_THREADING_EXPORT void (*_swift_tsan_acquire)(const void *ptr);
55+
SWIFT_THREADING_EXPORT void (*_swift_tsan_release)(const void *ptr);
56+
57+
} // namespace threading_impl
58+
59+
namespace tsan {
60+
61+
/// Returns true if TSan is enabled
62+
inline bool enabled() {
63+
return threading_impl::_swift_tsan_enabled;
64+
}
65+
66+
/// Inform TSan about a synchronization operation.
67+
///
68+
/// This is used when TSan cannot see the synchronization operation, for
69+
/// example, if it is using a custom primitive for which TSan doesn't have
70+
/// a built-in interceptor. This does not necessarily mean a lock or a C(++)
71+
/// atomic operation - it could be any kind of synchronization mechanism.
72+
///
73+
/// An acquire-release pair using the same address establishes an ordering
74+
/// constraint in TSan's happens-before graph, which TSan uses to determine
75+
/// whether two memory accesses from different threads have a well-defined
76+
/// order.
77+
///
78+
/// For instance, in
79+
///
80+
/// Thread 1 Thread 2
81+
///
82+
/// access to y
83+
/// tsan::release(x)
84+
/// lock given away
85+
///
86+
/// --> sync point -->
87+
///
88+
/// lock taken
89+
/// tsan::acquire(x)
90+
/// access to y
91+
///
92+
/// the access to y from Thread 2 is safe relative to the preceding access to
93+
/// y on Thread 1 because it is preceded by an acquire of x that was itself
94+
/// preceded by a release of x.
95+
template <typename T>
96+
T *acquire(T *ptr) {
97+
if (threading_impl::_swift_tsan_acquire) {
98+
threading_impl::_swift_tsan_acquire(ptr);
99+
}
100+
return ptr;
101+
}
102+
103+
/// Inform TSan about a synchronization operation.
104+
///
105+
/// This is the counterpart to tsan::acquire.
106+
template <typename T>
107+
T *release(T *ptr) {
108+
if (threading_impl::_swift_tsan_release) {
109+
threading_impl::_swift_tsan_release(ptr);
110+
}
111+
return ptr;
112+
}
113+
114+
} // namespace tsan
115+
116+
#endif
117+
118+
} // namespace swift
119+
120+
#endif

lib/Threading/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@ add_swift_host_library(swiftThreading STATIC
1010
Linux.cpp
1111
Pthreads.cpp
1212
Win32.cpp
13-
Errors.cpp)
13+
Errors.cpp
14+
ThreadSanitizer.cpp)

lib/Threading/Linux.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
#include "swift/Threading/Impl.h"
2020
#include "swift/Threading/Errors.h"
21+
#include "swift/Threading/ThreadSanitizer.h"
2122

2223
namespace {
2324

@@ -61,7 +62,7 @@ void swift::threading_impl::once_slow(once_t &predicate, void (*fn)(void *),
6162
#endif
6263
if (predicate.flag.load(std::memory_order_acquire) == 0) {
6364
fn(context);
64-
predicate.flag.store(-1, std::memory_order_release);
65+
predicate.flag.store(tsan::enabled() ? 1 : -1, std::memory_order_release);
6566
}
6667
#if defined(__LP64__) || defined(_LP64)
6768
linux::ulock_unlock(&predicate.lock);

lib/Threading/ThreadSanitizer.cpp

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
//===--- ThreadSanitizer.cpp - Thread Sanitizer support -------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2023 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+
// Helper functions for code that needs to integrate with the thread
14+
// sanitizer. In particular, TSan can't see inside the runtime libraries,
15+
// so we occasionally need to give it a hint that we're doing synchronization
16+
// in order to avoid false positives.
17+
//
18+
//===----------------------------------------------------------------------===//
19+
20+
#include "swift/Threading/ThreadSanitizer.h"
21+
22+
#if SWIFT_THREADING_TSAN_SUPPORT
23+
24+
#include "swift/shims/Visibility.h"
25+
26+
#include <cstdio>
27+
28+
namespace swift {
29+
namespace threading_impl {
30+
31+
SWIFT_THREADING_EXPORT bool _swift_tsan_enabled = false;
32+
SWIFT_THREADING_EXPORT void (*_swift_tsan_acquire)(const void *) = nullptr;
33+
SWIFT_THREADING_EXPORT void (*_swift_tsan_release)(const void *) = nullptr;
34+
35+
#if __has_include(<dlfcn.h>)
36+
#include <dlfcn.h>
37+
38+
// The TSan library code will call this function when it starts up
39+
extern "C" SWIFT_ATTRIBUTE_FOR_EXPORTS
40+
void __tsan_on_initialize() {
41+
_swift_tsan_enabled = true;
42+
_swift_tsan_acquire = (void (*)(const void *))dlsym(RTLD_DEFAULT,
43+
"__tsan_acquire");
44+
_swift_tsan_release = (void (*)(const void *))dlsym(RTLD_DEFAULT,
45+
"__tsan_release");
46+
47+
// Always call through to the next image; this won't work on macOS, but it's
48+
// important on Linux to allow others to hook into the thread sanitizer if
49+
// they wish.
50+
void (*next_init)(void);
51+
next_init = (void (*)(void))dlsym(RTLD_NEXT, "__tsan_on_initialize");
52+
if (next_init) {
53+
next_init();
54+
}
55+
}
56+
#endif // __has_include(<dlfcn.h>)
57+
58+
} // namespace threading_impl
59+
} // namespace swift
60+
61+
#endif // SWIFT_THREADING_TSAN_SUPPORT

stdlib/public/Concurrency/CMakeLists.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,6 @@ add_swift_target_library(swift_Concurrency ${SWIFT_STDLIB_LIBRARY_BUILD_TYPES} I
117117
TaskLocal.cpp
118118
TaskLocal.swift
119119
TaskSleep.swift
120-
ThreadSanitizer.cpp
121120
ThreadingError.cpp
122121
TracingSignpost.cpp
123122
AsyncStreamBuffer.swift

stdlib/public/Concurrency/TaskPrivate.h

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include "swift/Runtime/Exclusivity.h"
2929
#include "swift/Runtime/HeapObject.h"
3030
#include "swift/Threading/Thread.h"
31+
#include "swift/Threading/ThreadSanitizer.h"
3132
#include <atomic>
3233
#include <new>
3334

@@ -97,10 +98,14 @@ void _swift_taskGroup_cancelAllChildren(TaskGroup *group);
9798
/// should generally use a higher-level function.
9899
void _swift_taskGroup_detachChild(TaskGroup *group, AsyncTask *child);
99100

100-
/// release() establishes a happens-before relation with a preceding acquire()
101-
/// on the same address.
102-
void _swift_tsan_acquire(void *addr);
103-
void _swift_tsan_release(void *addr);
101+
/// Tell TSan about an acquiring load
102+
inline void _swift_tsan_acquire(void *addr) {
103+
swift::tsan::acquire(addr);
104+
}
105+
/// Tell TSan about a releasing store
106+
inline void _swift_tsan_release(void *addr) {
107+
swift::tsan::release(addr);
108+
}
104109

105110
/// Special values used with DispatchQueueIndex to indicate the global and main
106111
/// executors.

stdlib/public/Concurrency/ThreadSanitizer.cpp

Lines changed: 0 additions & 50 deletions
This file was deleted.

stdlib/public/Threading/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../../cmake/modules")
55
include(AddSwiftStdlib)
66

7+
# This should *not* include ThreadSanitizer.cpp, as that is part of libswiftCore
78
add_swift_target_library(swiftThreading OBJECT_LIBRARY
89
"${SWIFT_SOURCE_DIR}/lib/Threading/C11.cpp"
910
"${SWIFT_SOURCE_DIR}/lib/Threading/Linux.cpp"

0 commit comments

Comments
 (0)