Skip to content

Commit a8574e5

Browse files
committed
Copy LLVM's ThreadSanitizer (TSan) library to LDC's libraries like we do for ASan, and set the correct linker flags.
TSan is not supported on Windows.
1 parent 4240644 commit a8574e5

7 files changed

+216
-3
lines changed

CMakeLists.txt

+2
Original file line numberDiff line numberDiff line change
@@ -763,6 +763,7 @@ if (LDC_INSTALL_LLVM_RUNTIME_LIBS)
763763

764764
if(APPLE)
765765
copy_compilerrt_lib("darwin/libclang_rt.asan_osx_dynamic.dylib" "libldc_rt.asan.dylib" TRUE)
766+
copy_compilerrt_lib("darwin/libclang_rt.tsan_osx_dynamic.dylib" "libldc_rt.tsan.dylib" TRUE)
766767
copy_compilerrt_lib("darwin/libclang_rt.osx.a" "libldc_rt.builtins.a" FALSE)
767768
copy_compilerrt_lib("darwin/libclang_rt.profile_osx.a" "libldc_rt.profile.a" FALSE)
768769
copy_compilerrt_lib("darwin/libclang_rt.fuzzer_osx.a" "libldc_rt.fuzzer.a" FALSE)
@@ -777,6 +778,7 @@ if (LDC_INSTALL_LLVM_RUNTIME_LIBS)
777778
set(LDC_INSTALL_LLVM_RUNTIME_LIBS_ARCH "x86_64" CACHE STRING "Non-Mac Posix: architecture used as libname suffix for the compiler-rt source libraries, e.g., 'aarch64'.")
778779

779780
copy_compilerrt_lib("${LDC_INSTALL_LLVM_RUNTIME_LIBS_OS}/libclang_rt.asan-${LDC_INSTALL_LLVM_RUNTIME_LIBS_ARCH}.a" "libldc_rt.asan.a" FALSE)
781+
copy_compilerrt_lib("${LDC_INSTALL_LLVM_RUNTIME_LIBS_OS}/libclang_rt.tsan-${LDC_INSTALL_LLVM_RUNTIME_LIBS_ARCH}.a" "libldc_rt.tsan.a" FALSE)
780782
copy_compilerrt_lib("${LDC_INSTALL_LLVM_RUNTIME_LIBS_OS}/libclang_rt.builtins-${LDC_INSTALL_LLVM_RUNTIME_LIBS_ARCH}.a" "libldc_rt.builtins.a" FALSE)
781783
copy_compilerrt_lib("${LDC_INSTALL_LLVM_RUNTIME_LIBS_OS}/libclang_rt.profile-${LDC_INSTALL_LLVM_RUNTIME_LIBS_ARCH}.a" "libldc_rt.profile.a" FALSE)
782784
copy_compilerrt_lib("${LDC_INSTALL_LLVM_RUNTIME_LIBS_OS}/libclang_rt.xray-${LDC_INSTALL_LLVM_RUNTIME_LIBS_ARCH}.a" "libldc_rt.xray.a" FALSE)

driver/linker-gcc.cpp

+43-3
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ class ArgsBuilder {
5757
private:
5858
virtual void addSanitizers(const llvm::Triple &triple);
5959
virtual void addASanLinkFlags(const llvm::Triple &triple);
60+
virtual void addTSanLinkFlags(const llvm::Triple &triple);
6061
virtual void addFuzzLinkFlags(const llvm::Triple &triple);
6162
virtual void addCppStdlibLinkFlags(const llvm::Triple &triple);
6263
virtual void addProfileRuntimeLinkFlags(const llvm::Triple &triple);
@@ -322,6 +323,47 @@ void ArgsBuilder::addASanLinkFlags(const llvm::Triple &triple) {
322323
args.push_back("-fsanitize=address");
323324
}
324325

326+
void ArgsBuilder::addTSanLinkFlags(const llvm::Triple &triple) {
327+
// Examples: "libclang_rt.tsan-x86_64.a" or "libclang_rt.tsan-arm.a" and
328+
// "libclang_rt.tsan-x86_64.so"
329+
330+
// TODO: let user choose to link with shared lib.
331+
// In case of shared TSan, I think we also need to statically link with
332+
// libclang_rt.tsan-preinit-<arch>.a on Linux. On Darwin, the only option is
333+
// to use the shared library.
334+
bool linkSharedTSan = triple.isOSDarwin();
335+
const auto searchPaths =
336+
getFullCompilerRTLibPathCandidates("tsan", triple, linkSharedTSan);
337+
338+
for (const auto &filepath : searchPaths) {
339+
IF_LOG Logger::println("Searching TSan lib: %s", filepath.c_str());
340+
341+
if (llvm::sys::fs::exists(filepath) &&
342+
!llvm::sys::fs::is_directory(filepath)) {
343+
IF_LOG Logger::println("Found, linking with %s", filepath.c_str());
344+
args.push_back(filepath);
345+
346+
if (linkSharedTSan) {
347+
// Add @executable_path to rpath to support having the shared lib copied
348+
// with the executable.
349+
args.push_back("-rpath");
350+
args.push_back("@executable_path");
351+
352+
// Add the path to the resource dir to rpath to support using the shared
353+
// lib from the default location without copying.
354+
args.push_back("-rpath");
355+
args.push_back(std::string(llvm::sys::path::parent_path(filepath)));
356+
}
357+
358+
return;
359+
}
360+
}
361+
362+
// When we reach here, we did not find the TSan library.
363+
// Fallback, requires Clang.
364+
args.push_back("-fsanitize=thread");
365+
}
366+
325367
// Adds all required link flags for -fsanitize=fuzzer when libFuzzer library is
326368
// found.
327369
void ArgsBuilder::addFuzzLinkFlags(const llvm::Triple &triple) {
@@ -459,10 +501,8 @@ void ArgsBuilder::addSanitizers(const llvm::Triple &triple) {
459501
args.push_back("-fsanitize=memory");
460502
}
461503

462-
// TODO: instead of this, we should link with our own sanitizer libraries
463-
// because LDC's LLVM version could be different from the system clang.
464504
if (opts::isSanitizerEnabled(opts::ThreadSanitizer)) {
465-
args.push_back("-fsanitize=thread");
505+
addTSanLinkFlags(triple);
466506
}
467507
}
468508

tests/sanitizers/deflake.bash

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#!/usr/bin/env bash
2+
3+
# Copied from LLVM's testsuite: compiler-rt/test/tsan
4+
5+
# This script is used to deflake inherently flaky tsan tests.
6+
# It is invoked from lit tests as:
7+
# %deflake $THRESHOLD mybinary
8+
# which is then substituted by lit to:
9+
# $(dirname %s)/deflake.bash $THRESHOLD mybinary
10+
# - When TSAN_TEST_DEFLAKE_THRESHOLD is defined to a positive integer value,
11+
# THRESHOLD will be the defined value.
12+
# - When TSAN_TEST_DEFLAKE_THRESHOLD is not defined, THRESHOLD will be 10.
13+
# The script runs the target program up to $THRESHOLD times,
14+
# until it fails (i.e. produces a race report).
15+
16+
THRESHOLD="${1}"
17+
shift
18+
19+
# Early exit if $THRESHOLD is not a non-negative integer
20+
[[ "${THRESHOLD}" =~ ^[0-9]+$ ]] || exit 1
21+
22+
while (( THRESHOLD-- )); do
23+
OUT=`$@ 2>&1`
24+
if [[ $? != 0 ]]; then
25+
echo "$OUT"
26+
exit 0
27+
fi
28+
done
29+
exit 1

tests/sanitizers/lit.local.cfg

+7
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ if (platform.system() == 'Darwin') or (platform.system() == 'Linux'):
1111
if m is not None:
1212
config.available_features.add('ASan')
1313
continue
14+
m = re.match('.*tsan.*', file)
15+
if m is not None:
16+
config.available_features.add('TSan')
17+
continue
1418
m = re.match('.*(F|f)uzzer.*', file)
1519
if m is not None:
1620
config.available_features.add('Fuzzer')
@@ -25,3 +29,6 @@ if 'ASan' in config.available_features:
2529
config.substitutions.append(('%env_asan_opts=',
2630
'env ASAN_OPTIONS=' + default_asan_options + ':'))
2731

32+
# Add the %deflake substitution, to help with flaky tests.
33+
# Usage: "%deflake <count> <program>", runs <program> a maximum of <count> times until a failure occurs.
34+
config.substitutions.append( ("%deflake", os.path.join(os.path.dirname(__file__), "deflake.bash")))

tests/sanitizers/tsan_noerror.d

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Test that a simple program passes ThreadSanitizer without error
2+
3+
// REQUIRES: TSan
4+
5+
// XFAIL: *
6+
// Druntime does not yet work with ThreadSanitizer.
7+
// See Github issue 3519 (https://github.com/ldc-developers/ldc/issues/3519)
8+
9+
// RUN: %ldc -fsanitize=thread %s -of=%t%exe
10+
// RUN: %t%exe
11+
12+
void main()
13+
{
14+
}

tests/sanitizers/tsan_tiny_race.d

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Test that ThreadSanitizer+LDC works on a very basic testcase.
2+
// Note that -betterC is used, to avoid relying on druntime for this test.
3+
4+
// REQUIRES: TSan
5+
6+
// RUN: %ldc -betterC -g -fsanitize=thread %s -of=%t%exe
7+
// RUN: %deflake 10 %t%exe | FileCheck %s
8+
9+
// CHECK: WARNING: ThreadSanitizer: data race
10+
11+
import core.sys.posix.pthread;
12+
13+
shared int global;
14+
15+
extern(C)
16+
void *thread1(void *x) {
17+
barrier_wait(&barrier);
18+
// CHECK-DAG: thread1{{.*}}[[@LINE+1]]
19+
global = 42;
20+
return x;
21+
}
22+
23+
extern(C)
24+
int main() {
25+
barrier_init(&barrier, 2);
26+
pthread_t t;
27+
pthread_create(&t, null, &thread1, null);
28+
// CHECK-DAG: main{{.*}}[[@LINE+1]]
29+
global = 43;
30+
barrier_wait(&barrier);
31+
pthread_join(t, null);
32+
return global;
33+
}
34+
35+
//----------------------------------------------------------------------------
36+
// Code to facilitate thread synchronization to make this test deterministic.
37+
// See LLVM: compiler-rt/test/tsan/test.h
38+
39+
// TSan-invisible barrier.
40+
// Tests use it to establish necessary execution order in a way that does not
41+
// interfere with tsan (does not establish synchronization between threads).
42+
alias invisible_barrier_t = __c_ulonglong;
43+
import core.stdc.config;
44+
alias __c_unsigned = uint;
45+
// Default instance of the barrier, but a test can declare more manually.
46+
__gshared invisible_barrier_t barrier;
47+
48+
extern (C) {
49+
// These functions reside inside the tsan library.
50+
void __tsan_testonly_barrier_init(invisible_barrier_t *barrier, __c_unsigned count);
51+
void __tsan_testonly_barrier_wait(invisible_barrier_t *barrier);
52+
}
53+
54+
void barrier_init(invisible_barrier_t *barrier, __c_unsigned count) {
55+
__tsan_testonly_barrier_init(barrier, count);
56+
}
57+
58+
void barrier_wait(invisible_barrier_t *barrier) {
59+
__tsan_testonly_barrier_wait(barrier);
60+
}
61+
//----------------------------------------------------------------------------

tests/sanitizers/tsan_tiny_race_TLS.d

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Test that ThreadSanitizer+LDC works on a very basic testcase.
2+
// Note that -betterC is used, to avoid relying on druntime for this test.
3+
4+
// REQUIRES: TSan
5+
6+
// RUN: %ldc -betterC -g -fsanitize=thread %s -of=%t%exe
7+
// RUN: %deflake 10 %t%exe | FileCheck %s
8+
9+
// CHECK: WARNING: ThreadSanitizer: data race
10+
11+
import core.sys.posix.pthread;
12+
13+
extern(C)
14+
void *thread1(void *x) {
15+
barrier_wait(&barrier);
16+
// CHECK-DAG: thread1{{.*}}[[@LINE+1]]
17+
*cast(int*)x = 42;
18+
return x;
19+
}
20+
21+
extern(C)
22+
int main() {
23+
int tls_variable;
24+
barrier_init(&barrier, 2);
25+
pthread_t t;
26+
pthread_create(&t, null, &thread1, &tls_variable);
27+
// CHECK-DAG: main{{.*}}[[@LINE+1]]
28+
tls_variable = 43;
29+
barrier_wait(&barrier);
30+
pthread_join(t, null);
31+
return 0;
32+
}
33+
34+
//----------------------------------------------------------------------------
35+
// Code to facilitate thread synchronization to make this test deterministic.
36+
// See LLVM: compiler-rt/test/tsan/test.h
37+
38+
// TSan-invisible barrier.
39+
// Tests use it to establish necessary execution order in a way that does not
40+
// interfere with tsan (does not establish synchronization between threads).
41+
alias invisible_barrier_t = __c_ulonglong;
42+
import core.stdc.config;
43+
alias __c_unsigned = uint;
44+
// Default instance of the barrier, but a test can declare more manually.
45+
__gshared invisible_barrier_t barrier;
46+
47+
extern (C) {
48+
// These functions reside inside the tsan library.
49+
void __tsan_testonly_barrier_init(invisible_barrier_t *barrier, __c_unsigned count);
50+
void __tsan_testonly_barrier_wait(invisible_barrier_t *barrier);
51+
}
52+
53+
void barrier_init(invisible_barrier_t *barrier, __c_unsigned count) {
54+
__tsan_testonly_barrier_init(barrier, count);
55+
}
56+
57+
void barrier_wait(invisible_barrier_t *barrier) {
58+
__tsan_testonly_barrier_wait(barrier);
59+
}
60+
//----------------------------------------------------------------------------

0 commit comments

Comments
 (0)