Skip to content

Commit 6a18a8f

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 9598e57 commit 6a18a8f

File tree

12 files changed

+287
-73
lines changed

12 files changed

+287
-73
lines changed

cmake/modules/AddSwiftUnittests.cmake

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

5959
string(TOUPPER "${SWIFT_SDK_${SWIFT_HOST_VARIANT_SDK}_THREADING_PACKAGE}" _threading_package)
6060
target_compile_definitions("${test_dirname}" PRIVATE
61-
"SWIFT_THREADING_${_threading_package}")
61+
"SWIFT_THREADING_${_threading_package}"
62+
"SWIFT_THREADING_STATIC")
6263

6364
if(NOT SWIFT_COMPILER_IS_MSVC_LIKE)
6465
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
@@ -120,7 +120,6 @@ add_swift_target_library(swift_Concurrency ${SWIFT_STDLIB_LIBRARY_BUILD_TYPES} I
120120
TaskLocal.cpp
121121
TaskLocal.swift
122122
TaskSleep.swift
123-
ThreadSanitizer.cpp
124123
ThreadingError.cpp
125124
TracingSignpost.cpp
126125
AsyncStreamBuffer.swift

stdlib/public/Concurrency/TaskPrivate.h

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include "swift/Runtime/Exclusivity.h"
3030
#include "swift/Runtime/HeapObject.h"
3131
#include "swift/Threading/Thread.h"
32+
#include "swift/Threading/ThreadSanitizer.h"
3233
#include <atomic>
3334
#include <new>
3435

@@ -99,15 +100,23 @@ void _swift_taskGroup_cancelAllChildren(TaskGroup *group);
99100
/// should generally use a higher-level function.
100101
void _swift_taskGroup_detachChild(TaskGroup *group, AsyncTask *child);
101102

102-
/// release() establishes a happens-before relation with a preceding acquire()
103-
/// on the same address.
104-
void _swift_tsan_acquire(void *addr);
105-
void _swift_tsan_release(void *addr);
106-
/// Technically, this consume relies on implicit HW address dependency ordering
107-
/// and is paired with a corresponding release. Since TSAN doesn't know how to
108-
/// reason about this, we tell TSAN it's an acquire instead. See also
109-
/// SWIFT_MEMORY_ORDER_CONSUME definition.
110-
#define _swift_tsan_consume _swift_tsan_acquire
103+
/// Tell TSan about an acquiring load
104+
inline void _swift_tsan_acquire(void *addr) {
105+
swift::tsan::acquire(addr);
106+
}
107+
/// Tell TSan about a releasing store
108+
inline void _swift_tsan_release(void *addr) {
109+
swift::tsan::release(addr);
110+
}
111+
/// Tell TSan about a consuming load
112+
inline void _swift_tsan_consume(void *addr) {
113+
// TSan doesn't support consume, so pretend it's an acquire.
114+
//
115+
// Note that that means that TSan won't generate errors for non-dependent
116+
// reads, so this isn't entirely safe if you're relying solely on TSan to
117+
// spot bugs.
118+
swift::tsan::acquire(addr);
119+
}
111120

112121
/// Special values used with DispatchQueueIndex to indicate the global and main
113122
/// 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)