Skip to content

Commit 1ba4128

Browse files
authoredAug 3, 2020
Copy LLVM's ThreadSanitizer (TSan) library to LDC's libraries like we do for ASan, and set the correct linker flags (#3522)
* Copy LLVM's ThreadSanitizer (TSan) library to LDC's libraries like we do for ASan, and set the correct linker flags. Requires at least LLVM 8 for executable TSan test cases and TSan execution is not supported on Windows.
1 parent e628a0a commit 1ba4128

File tree

7 files changed

+192
-17
lines changed

7 files changed

+192
-17
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

+17-17
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,9 @@ class ArgsBuilder {
5656

5757
private:
5858
virtual void addSanitizers(const llvm::Triple &triple);
59-
virtual void addASanLinkFlags(const llvm::Triple &triple);
59+
virtual void addSanitizerLinkFlags(const llvm::Triple &triple,
60+
const llvm::StringRef sanitizerName,
61+
const llvm::StringRef fallbackFlag);
6062
virtual void addFuzzLinkFlags(const llvm::Triple &triple);
6163
virtual void addCppStdlibLinkFlags(const llvm::Triple &triple);
6264
virtual void addProfileRuntimeLinkFlags(const llvm::Triple &triple);
@@ -279,27 +281,29 @@ getFullCompilerRTLibPathCandidates(llvm::StringRef baseName,
279281
return r;
280282
}
281283

282-
void ArgsBuilder::addASanLinkFlags(const llvm::Triple &triple) {
283-
// Examples: "libclang_rt.asan-x86_64.a" or "libclang_rt.asan-arm.a" and
284-
// "libclang_rt.asan-x86_64.so"
284+
void ArgsBuilder::addSanitizerLinkFlags(const llvm::Triple &triple,
285+
const llvm::StringRef sanitizerName,
286+
const llvm::StringRef fallbackFlag) {
287+
// Examples: "libclang_rt.tsan-x86_64.a" or "libclang_rt.tsan-arm.a" and
288+
// "libclang_rt.tsan-x86_64.so"
285289

286290
// TODO: let user choose to link with shared lib.
287291
// In case of shared ASan, I think we also need to statically link with
288292
// libclang_rt.asan-preinit-<arch>.a on Linux. On Darwin, the only option is
289293
// to use the shared library.
290-
bool linkSharedASan = triple.isOSDarwin();
294+
bool linkSharedLibrary = triple.isOSDarwin();
291295
const auto searchPaths =
292-
getFullCompilerRTLibPathCandidates("asan", triple, linkSharedASan);
296+
getFullCompilerRTLibPathCandidates(sanitizerName, triple, linkSharedLibrary);
293297

294298
for (const auto &filepath : searchPaths) {
295-
IF_LOG Logger::println("Searching ASan lib: %s", filepath.c_str());
299+
IF_LOG Logger::println("Searching sanitizer lib: %s", filepath.c_str());
296300

297301
if (llvm::sys::fs::exists(filepath) &&
298302
!llvm::sys::fs::is_directory(filepath)) {
299303
IF_LOG Logger::println("Found, linking with %s", filepath.c_str());
300304
args.push_back(filepath);
301305

302-
if (linkSharedASan) {
306+
if (linkSharedLibrary) {
303307
// Add @executable_path to rpath to support having the shared lib copied
304308
// with the executable.
305309
args.push_back("-rpath");
@@ -315,11 +319,9 @@ void ArgsBuilder::addASanLinkFlags(const llvm::Triple &triple) {
315319
}
316320
}
317321

318-
// When we reach here, we did not find the ASan library.
319-
// Fallback, requires Clang. The asan library contains a versioned symbol
320-
// name and a linker error will happen when the LDC-LLVM and Clang-LLVM
321-
// versions don't match.
322-
args.push_back("-fsanitize=address");
322+
// When we reach here, we did not find the sanitizer library.
323+
// Fallback, requires Clang.
324+
args.push_back(fallbackFlag);
323325
}
324326

325327
// Adds all required link flags for -fsanitize=fuzzer when libFuzzer library is
@@ -446,7 +448,7 @@ void ArgsBuilder::addProfileRuntimeLinkFlags(const llvm::Triple &triple) {
446448

447449
void ArgsBuilder::addSanitizers(const llvm::Triple &triple) {
448450
if (opts::isSanitizerEnabled(opts::AddressSanitizer)) {
449-
addASanLinkFlags(triple);
451+
addSanitizerLinkFlags(triple, "asan", "-fsanitize=address");
450452
}
451453

452454
if (opts::isSanitizerEnabled(opts::FuzzSanitizer)) {
@@ -459,10 +461,8 @@ void ArgsBuilder::addSanitizers(const llvm::Triple &triple) {
459461
args.push_back("-fsanitize=memory");
460462
}
461463

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.
464464
if (opts::isSanitizerEnabled(opts::ThreadSanitizer)) {
465-
args.push_back("-fsanitize=thread");
465+
addSanitizerLinkFlags(triple, "tsan", "-fsanitize=thread");
466466
}
467467
}
468468

‎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

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

‎tests/sanitizers/tsan_tiny_race_TLS.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+
// REQUIRES: atleast_llvm800
6+
7+
// RUN: %ldc -betterC -g -fsanitize=thread %s -of=%t%exe
8+
// RUN: %deflake 20 %t%exe | FileCheck %s
9+
10+
// CHECK: WARNING: ThreadSanitizer: data race
11+
12+
import core.sys.posix.pthread;
13+
14+
extern(C)
15+
void *thread1(void *x) {
16+
barrier_wait(&barrier);
17+
// CHECK-DAG: thread1{{.*}}[[@LINE+1]]
18+
*cast(int*)x = 42;
19+
return x;
20+
}
21+
22+
extern(C)
23+
int main() {
24+
int tls_variable;
25+
barrier_init(&barrier, 2);
26+
pthread_t t;
27+
pthread_create(&t, null, &thread1, &tls_variable);
28+
// CHECK-DAG: main{{.*}}[[@LINE+1]]
29+
tls_variable = 43;
30+
barrier_wait(&barrier);
31+
pthread_join(t, null);
32+
return 0;
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+
//----------------------------------------------------------------------------

0 commit comments

Comments
 (0)
Please sign in to comment.