Skip to content

Commit 4f4d014

Browse files
authored
Merge pull request #66721 from al45tair/eng/PR-110665213
[Threading][TSan] Fix TSan errors from lazy init on Linux.
2 parents 37220ed + f38b9a9 commit 4f4d014

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)