Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Copy LLVM's ThreadSanitizer (TSan) library to LDC's libraries like we do for ASan, and set the correct linker flags #3522

Merged
merged 4 commits into from
Aug 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -763,6 +763,7 @@ if (LDC_INSTALL_LLVM_RUNTIME_LIBS)

if(APPLE)
copy_compilerrt_lib("darwin/libclang_rt.asan_osx_dynamic.dylib" "libldc_rt.asan.dylib" TRUE)
copy_compilerrt_lib("darwin/libclang_rt.tsan_osx_dynamic.dylib" "libldc_rt.tsan.dylib" TRUE)
copy_compilerrt_lib("darwin/libclang_rt.osx.a" "libldc_rt.builtins.a" FALSE)
copy_compilerrt_lib("darwin/libclang_rt.profile_osx.a" "libldc_rt.profile.a" FALSE)
copy_compilerrt_lib("darwin/libclang_rt.fuzzer_osx.a" "libldc_rt.fuzzer.a" FALSE)
Expand All @@ -777,6 +778,7 @@ if (LDC_INSTALL_LLVM_RUNTIME_LIBS)
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'.")

copy_compilerrt_lib("${LDC_INSTALL_LLVM_RUNTIME_LIBS_OS}/libclang_rt.asan-${LDC_INSTALL_LLVM_RUNTIME_LIBS_ARCH}.a" "libldc_rt.asan.a" FALSE)
copy_compilerrt_lib("${LDC_INSTALL_LLVM_RUNTIME_LIBS_OS}/libclang_rt.tsan-${LDC_INSTALL_LLVM_RUNTIME_LIBS_ARCH}.a" "libldc_rt.tsan.a" FALSE)
copy_compilerrt_lib("${LDC_INSTALL_LLVM_RUNTIME_LIBS_OS}/libclang_rt.builtins-${LDC_INSTALL_LLVM_RUNTIME_LIBS_ARCH}.a" "libldc_rt.builtins.a" FALSE)
copy_compilerrt_lib("${LDC_INSTALL_LLVM_RUNTIME_LIBS_OS}/libclang_rt.profile-${LDC_INSTALL_LLVM_RUNTIME_LIBS_ARCH}.a" "libldc_rt.profile.a" FALSE)
copy_compilerrt_lib("${LDC_INSTALL_LLVM_RUNTIME_LIBS_OS}/libclang_rt.xray-${LDC_INSTALL_LLVM_RUNTIME_LIBS_ARCH}.a" "libldc_rt.xray.a" FALSE)
Expand Down
34 changes: 17 additions & 17 deletions driver/linker-gcc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ class ArgsBuilder {

private:
virtual void addSanitizers(const llvm::Triple &triple);
virtual void addASanLinkFlags(const llvm::Triple &triple);
virtual void addSanitizerLinkFlags(const llvm::Triple &triple,
const llvm::StringRef sanitizerName,
const llvm::StringRef fallbackFlag);
virtual void addFuzzLinkFlags(const llvm::Triple &triple);
virtual void addCppStdlibLinkFlags(const llvm::Triple &triple);
virtual void addProfileRuntimeLinkFlags(const llvm::Triple &triple);
Expand Down Expand Up @@ -279,27 +281,29 @@ getFullCompilerRTLibPathCandidates(llvm::StringRef baseName,
return r;
}

void ArgsBuilder::addASanLinkFlags(const llvm::Triple &triple) {
// Examples: "libclang_rt.asan-x86_64.a" or "libclang_rt.asan-arm.a" and
// "libclang_rt.asan-x86_64.so"
void ArgsBuilder::addSanitizerLinkFlags(const llvm::Triple &triple,
const llvm::StringRef sanitizerName,
const llvm::StringRef fallbackFlag) {
// Examples: "libclang_rt.tsan-x86_64.a" or "libclang_rt.tsan-arm.a" and
// "libclang_rt.tsan-x86_64.so"

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

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

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

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

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

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

void ArgsBuilder::addSanitizers(const llvm::Triple &triple) {
if (opts::isSanitizerEnabled(opts::AddressSanitizer)) {
addASanLinkFlags(triple);
addSanitizerLinkFlags(triple, "asan", "-fsanitize=address");
}

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

// TODO: instead of this, we should link with our own sanitizer libraries
// because LDC's LLVM version could be different from the system clang.
if (opts::isSanitizerEnabled(opts::ThreadSanitizer)) {
args.push_back("-fsanitize=thread");
addSanitizerLinkFlags(triple, "tsan", "-fsanitize=thread");
}
}

Expand Down
29 changes: 29 additions & 0 deletions tests/sanitizers/deflake.bash
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#!/usr/bin/env bash

# Copied from LLVM's testsuite: compiler-rt/test/tsan

# This script is used to deflake inherently flaky tsan tests.
# It is invoked from lit tests as:
# %deflake $THRESHOLD mybinary
# which is then substituted by lit to:
# $(dirname %s)/deflake.bash $THRESHOLD mybinary
# - When TSAN_TEST_DEFLAKE_THRESHOLD is defined to a positive integer value,
# THRESHOLD will be the defined value.
# - When TSAN_TEST_DEFLAKE_THRESHOLD is not defined, THRESHOLD will be 10.
# The script runs the target program up to $THRESHOLD times,
# until it fails (i.e. produces a race report).

THRESHOLD="${1}"
shift

# Early exit if $THRESHOLD is not a non-negative integer
[[ "${THRESHOLD}" =~ ^[0-9]+$ ]] || exit 1

while (( THRESHOLD-- )); do
OUT=`$@ 2>&1`
if [[ $? != 0 ]]; then
echo "$OUT"
exit 0
fi
done
exit 1
7 changes: 7 additions & 0 deletions tests/sanitizers/lit.local.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ if (platform.system() == 'Darwin') or (platform.system() == 'Linux'):
if m is not None:
config.available_features.add('ASan')
continue
m = re.match('.*tsan.*', file)
if m is not None:
config.available_features.add('TSan')
continue
m = re.match('.*(F|f)uzzer.*', file)
if m is not None:
config.available_features.add('Fuzzer')
Expand All @@ -25,3 +29,6 @@ if 'ASan' in config.available_features:
config.substitutions.append(('%env_asan_opts=',
'env ASAN_OPTIONS=' + default_asan_options + ':'))

# Add the %deflake substitution, to help with flaky tests.
# Usage: "%deflake <count> <program>", runs <program> a maximum of <count> times until a failure occurs.
config.substitutions.append( ("%deflake", os.path.join(os.path.dirname(__file__), "deflake.bash")))
14 changes: 14 additions & 0 deletions tests/sanitizers/tsan_noerror.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Test that a simple program passes ThreadSanitizer without error

// REQUIRES: TSan

// XFAIL: *
// Druntime does not yet work with ThreadSanitizer.
// See Github issue 3519 (https://github.com/ldc-developers/ldc/issues/3519)

// RUN: %ldc -fsanitize=thread %s -of=%t%exe
// RUN: %t%exe

void main()
{
}
62 changes: 62 additions & 0 deletions tests/sanitizers/tsan_tiny_race.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Test that ThreadSanitizer+LDC works on a very basic testcase.
// Note that -betterC is used, to avoid relying on druntime for this test.

// REQUIRES: TSan
// REQUIRES: atleast_llvm800

// RUN: %ldc -betterC -g -fsanitize=thread %s -of=%t%exe
// RUN: %deflake 20 %t%exe | FileCheck %s

// CHECK: WARNING: ThreadSanitizer: data race

import core.sys.posix.pthread;

shared int global;

extern(C)
void *thread1(void *x) {
barrier_wait(&barrier);
// CHECK-DAG: thread1{{.*}}[[@LINE+1]]
global = 42;
return x;
}

extern(C)
int main() {
barrier_init(&barrier, 2);
pthread_t t;
pthread_create(&t, null, &thread1, null);
// CHECK-DAG: main{{.*}}[[@LINE+1]]
global = 43;
barrier_wait(&barrier);
pthread_join(t, null);
return global;
}

//----------------------------------------------------------------------------
// Code to facilitate thread synchronization to make this test deterministic.
// See LLVM: compiler-rt/test/tsan/test.h

// TSan-invisible barrier.
// Tests use it to establish necessary execution order in a way that does not
// interfere with tsan (does not establish synchronization between threads).
alias invisible_barrier_t = __c_ulonglong;
import core.stdc.config;
alias __c_unsigned = uint;
// Default instance of the barrier, but a test can declare more manually.
__gshared invisible_barrier_t barrier;

extern (C) {
// These functions reside inside the tsan library.
void __tsan_testonly_barrier_init(invisible_barrier_t *barrier, __c_unsigned count);
void __tsan_testonly_barrier_wait(invisible_barrier_t *barrier);
}

void barrier_init(invisible_barrier_t *barrier, __c_unsigned count) {
__tsan_testonly_barrier_init(barrier, count);
}

void barrier_wait(invisible_barrier_t *barrier) {
__tsan_testonly_barrier_wait(barrier);
}
//----------------------------------------------------------------------------
61 changes: 61 additions & 0 deletions tests/sanitizers/tsan_tiny_race_TLS.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Test that ThreadSanitizer+LDC works on a very basic testcase.
// Note that -betterC is used, to avoid relying on druntime for this test.

// REQUIRES: TSan
// REQUIRES: atleast_llvm800

// RUN: %ldc -betterC -g -fsanitize=thread %s -of=%t%exe
// RUN: %deflake 20 %t%exe | FileCheck %s

// CHECK: WARNING: ThreadSanitizer: data race

import core.sys.posix.pthread;

extern(C)
void *thread1(void *x) {
barrier_wait(&barrier);
// CHECK-DAG: thread1{{.*}}[[@LINE+1]]
*cast(int*)x = 42;
return x;
}

extern(C)
int main() {
int tls_variable;
barrier_init(&barrier, 2);
pthread_t t;
pthread_create(&t, null, &thread1, &tls_variable);
// CHECK-DAG: main{{.*}}[[@LINE+1]]
tls_variable = 43;
barrier_wait(&barrier);
pthread_join(t, null);
return 0;
}

//----------------------------------------------------------------------------
// Code to facilitate thread synchronization to make this test deterministic.
// See LLVM: compiler-rt/test/tsan/test.h

// TSan-invisible barrier.
// Tests use it to establish necessary execution order in a way that does not
// interfere with tsan (does not establish synchronization between threads).
alias invisible_barrier_t = __c_ulonglong;
import core.stdc.config;
alias __c_unsigned = uint;
// Default instance of the barrier, but a test can declare more manually.
__gshared invisible_barrier_t barrier;

extern (C) {
// These functions reside inside the tsan library.
void __tsan_testonly_barrier_init(invisible_barrier_t *barrier, __c_unsigned count);
void __tsan_testonly_barrier_wait(invisible_barrier_t *barrier);
}

void barrier_init(invisible_barrier_t *barrier, __c_unsigned count) {
__tsan_testonly_barrier_init(barrier, count);
}

void barrier_wait(invisible_barrier_t *barrier) {
__tsan_testonly_barrier_wait(barrier);
}
//----------------------------------------------------------------------------