Skip to content

Minimal infrastructure support for ThreadSanitizer #27167

New issue

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

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

Already on GitHub? # to your account

Merged
merged 1 commit into from
Aug 5, 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
26 changes: 18 additions & 8 deletions Make.inc
Original file line number Diff line number Diff line change
Expand Up @@ -492,7 +492,7 @@ ifeq ($(USE_LIBCPP),1)
$(error USE_LIBCPP only supported with clang. Try setting USE_LIBCPP=0)
endif
ifeq ($(SANITIZE),1)
$(error Address Sanitizer only supported with clang. Try setting SANITIZE=0)
$(error Sanitizers are only supported with clang. Try setting SANITIZE=0)
endif
CC := $(CROSS_COMPILE)gcc
CXX := $(CROSS_COMPILE)g++
Expand Down Expand Up @@ -539,7 +539,7 @@ ifeq ($(USE_LIBCPP),1)
$(error USE_LIBCPP only supported with clang. Try setting USE_LIBCPP=0)
endif
ifeq ($(SANITIZE),1)
$(error Address Sanitizer only supported with clang. Try setting SANITIZE=0)
$(error Sanitizers only supported with clang. Try setting SANITIZE=0)
endif
CC := icc
CXX := icpc
Expand Down Expand Up @@ -670,17 +670,27 @@ endif
endif

ifeq ($(SANITIZE),1)
SANITIZE_OPTS :=
SANITIZE_LDFLAGS :=
ifeq ($(SANITIZE_MEMORY),1)
SANITIZE_OPTS := -fsanitize=memory -fsanitize-memory-track-origins -fno-omit-frame-pointer
SANITIZE_LDFLAGS := $(SANITIZE_OPTS)
else
SANITIZE_OPTS := -fsanitize=address -mllvm -asan-stack=0
SANITIZE_LDFLAGS := -fsanitize=address
SANITIZE_OPTS += -fsanitize=memory -fsanitize-memory-track-origins -fno-omit-frame-pointer
SANITIZE_LDFLAGS += $(SANITIZE_OPTS)
endif
ifeq ($(SANITIZE_ADDRESS),1)
SANITIZE_OPTS += -fsanitize=address -mllvm -asan-stack=0
SANITIZE_LDFLAGS += -fsanitize=address
endif
ifeq ($(SANITIZE_THREAD),1)
SANITIZE_OPTS += -fsanitize=thread
SANITIZE_LDFLAGS += -fsanitize=thread
endif
ifeq ($(SANITIZE_OPTS),)
$(error SANITIZE=1, but no sanitizer selected, set either SANITIZE_MEMORY, SANITIZE_THREAD, or SANITIZE_ADDRESS)
endif
JCXXFLAGS += $(SANITIZE_OPTS)
JCFLAGS += $(SANITIZE_OPTS)
JLDFLAGS += $(SANITIZE_LDFLAGS)
endif
endif # SANITIZE

TAR := $(shell which gtar 2>/dev/null || which tar 2>/dev/null)
TAR_TEST := $(shell $(TAR) --help 2>&1 | egrep 'bsdtar|strip-components')
Expand Down
3 changes: 3 additions & 0 deletions deps/llvm.mk
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,7 @@ $(eval $(call LLVM_PATCH,llvm-8.0-D66657-codegen-degenerate)) # remove for 10.0
$(eval $(call LLVM_PATCH,llvm-8.0-D71495-vectorize-freduce)) # remove for 10.0
$(eval $(call LLVM_PATCH,llvm-8.0-D75072-SCEV-add-type))
$(eval $(call LLVM_PATCH,llvm-8.0-D65174-limit-merge-stores)) # remove for 10.0
$(eval $(call LLVM_PATCH,llvm-julia-tsan-custom-as))
endif # LLVM_VER 8.0

ifeq ($(LLVM_VER_SHORT),9.0)
Expand All @@ -427,6 +428,7 @@ $(eval $(call LLVM_PATCH,llvm-D75072-SCEV-add-type))
$(eval $(call LLVM_PATCH,llvm-9.0-D65174-limit-merge-stores)) # remove for 10.0
$(eval $(call LLVM_PATCH,llvm9-D71443-PPC-MC-redef-symbol)) # remove for 10.0
$(eval $(call LLVM_PATCH,llvm-9.0-D78196)) # remove for 11.0
$(eval $(call LLVM_PATCH,llvm-julia-tsan-custom-as))
endif # LLVM_VER 9.0

ifeq ($(LLVM_VER_SHORT),10.0)
Expand All @@ -441,6 +443,7 @@ $(eval $(call LLVM_PATCH,llvm7-revert-D44485))
$(eval $(call LLVM_PATCH,llvm-D75072-SCEV-add-type))
$(eval $(call LLVM_PATCH,llvm-10.0-PPC_SELECT_CC)) # delete for LLVM 11
$(eval $(call LLVM_PATCH,llvm-10.0-PPC-LI-Elimination)) # delete for LLVM 11
$(eval $(call LLVM_PATCH,llvm-julia-tsan-custom-as))
endif # LLVM_VER 10.0

# Add a JL prefix to the version map. DO NOT REMOVE
Expand Down
28 changes: 28 additions & 0 deletions deps/patches/llvm-julia-tsan-custom-as.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
From bd41be423127b8946daea805290ad2eb19e66be4 Mon Sep 17 00:00:00 2001
From: Valentin Churavy <v.churavy@gmail.com>
Date: Sat, 19 May 2018 11:56:55 -0400
Subject: [PATCH] [TSAN] Allow for custom address spaces

Julia uses addressspaces for GC and we want these to be sanitized as well.
---
lib/Transforms/Instrumentation/ThreadSanitizer.cpp | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/lib/Transforms/Instrumentation/ThreadSanitizer.cpp b/lib/Transforms/Instrumentation/ThreadSanitizer.cpp
index ec6904486e1..9d673353f43 100644
--- a/lib/Transforms/Instrumentation/ThreadSanitizer.cpp
+++ b/lib/Transforms/Instrumentation/ThreadSanitizer.cpp
@@ -296,7 +296,9 @@ static bool shouldInstrumentReadWriteFromAddress(const Module *M, Value *Addr) {
// with them.
if (Addr) {
Type *PtrTy = cast<PointerType>(Addr->getType()->getScalarType());
- if (PtrTy->getPointerAddressSpace() != 0)
+ auto AS = PtrTy->getPointerAddressSpace();
+ // Allow for custom addresspaces
+ if (AS != 0 && AS < 10)
return false;
}

--
2.17.0

10 changes: 9 additions & 1 deletion doc/src/devdocs/sanitizers.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,13 @@ An easy solution is to have an dedicated build folder for providing a matching t
with `BUILD_LLVM_CLANG=1`. You can then refer to this toolchain from another build
folder by specifying `USECLANG=1` while overriding the `CC` and `CXX` variables.

To use one of of the sanitizers set `SANITIZE=1` and then the appropriate flag for the sanitizer you
want to use.

## Address Sanitizer (ASAN)

For detecting or debugging memory bugs, you can use Clang's [address sanitizer (ASAN)](http://clang.llvm.org/docs/AddressSanitizer.html).
By compiling with `SANITIZE=1` you enable ASAN for the Julia compiler and its generated code.
By compiling with `SANITIZE_ADDRESS=1` you enable ASAN for the Julia compiler and its generated code.
In addition, you can specify `LLVM_SANITIZE=1` to sanitize the LLVM library as well. Note that
these options incur a high performance and memory cost. For example, using ASAN for Julia and
LLVM makes `testall1` takes 8-10 times as long while using 20 times as much memory (this can be
Expand All @@ -31,3 +34,8 @@ the future.

For detecting use of uninitialized memory, you can use Clang's [memory sanitizer (MSAN)](http://clang.llvm.org/docs/MemorySanitizer.html)
by compiling with `SANITIZE_MEMORY=1`.

## Thread Sanitizer (TSAN)

For debugging data-races and other threading related issues you can use Clang's [thread sanitizer (TSAN)](https://clang.llvm.org/docs/ThreadSanitizer.html)
by compiling with `SANITIZE_THREAD=1`.
11 changes: 10 additions & 1 deletion src/aotcompile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

#include "llvm-version.h"
#include "platform.h"
#include "options.h"

// target support
#include <llvm/ADT/Triple.h>
Expand All @@ -24,6 +23,9 @@
#if defined(JL_ASAN_ENABLED)
#include <llvm/Transforms/Instrumentation/AddressSanitizer.h>
#endif
#if defined(JL_TSAN_ENABLED)
#include <llvm/Transforms/Instrumentation/ThreadSanitizer.h>
#endif
#include <llvm/Transforms/Scalar/GVN.h>
#include <llvm/Transforms/IPO/AlwaysInliner.h>
#include <llvm/Transforms/InstCombine/InstCombine.h>
Expand Down Expand Up @@ -624,6 +626,7 @@ void addOptimizationPasses(legacy::PassManagerBase *PM, int opt_level,
}
PM->add(createMemCpyOptPass());
PM->add(createAlwaysInlinerLegacyPass()); // Respect always_inline
PM->add(createLowerSimdLoopPass()); // Annotate loop marked with "loopinfo" as LLVM parallel loop
if (lower_intrinsics) {
PM->add(createBarrierNoopPass());
PM->add(createLowerExcHandlersPass());
Expand All @@ -641,6 +644,9 @@ void addOptimizationPasses(legacy::PassManagerBase *PM, int opt_level,
#endif
#if defined(JL_MSAN_ENABLED)
PM->add(createMemorySanitizerPass(true));
#endif
#if defined(JL_TSAN_ENABLED)
PM->add(createThreadSanitizerLegacyPassPass());
#endif
return;
}
Expand Down Expand Up @@ -764,6 +770,9 @@ void addOptimizationPasses(legacy::PassManagerBase *PM, int opt_level,
#if defined(JL_MSAN_ENABLED)
PM->add(createMemorySanitizerPass(true));
#endif
#if defined(JL_TSAN_ENABLED)
PM->add(createThreadSanitizerLegacyPassPass());
#endif
}

// An LLVM module pass that just runs all julia passes in order. Useful for
Expand Down
1 change: 0 additions & 1 deletion src/cgmemmgr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

#include "llvm-version.h"
#include "platform.h"
#include "options.h"

#include <llvm/ExecutionEngine/SectionMemoryManager.h>
#include "julia.h"
Expand Down
7 changes: 6 additions & 1 deletion src/codegen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

#include "llvm-version.h"
#include "platform.h"
#include "options.h"
#if defined(_OS_WINDOWS_)
// use ELF because RuntimeDyld COFF i686 support didn't exist
// use ELF because RuntimeDyld COFF X86_64 doesn't seem to work (fails to generate function pointers)?
Expand Down Expand Up @@ -5787,6 +5786,12 @@ static std::pair<std::unique_ptr<Module>, jl_llvm_functions_t>
f->addFnAttr(Attribute::StackProtectStrong);
#endif

#ifdef JL_TSAN_ENABLED
// TODO: enable this only when a argument like `-race` is passed to Julia
// add a macro for no_sanitize_thread
f->addFnAttr(llvm::Attribute::SanitizeThread);
#endif

// add the optimization level specified for this module, if any
int optlevel = jl_get_module_optlevel(ctx.module);
if (optlevel >= 0 && optlevel <= 3) {
Expand Down
2 changes: 1 addition & 1 deletion src/dlload.c
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ JL_DLLEXPORT void *jl_dlopen(const char *filename, unsigned flags)
#ifdef RTLD_NOLOAD
| JL_RTLD(flags, NOLOAD)
#endif
#if defined(RTLD_DEEPBIND) && !defined(JL_ASAN_ENABLED)
#if defined(RTLD_DEEPBIND) && !(defined(JL_ASAN_ENABLED) || defined(JL_TSAN_ENABLED) || defined(JL_MSAN_ENABLED))
| JL_RTLD(flags, DEEPBIND)
#endif
#ifdef RTLD_FIRST
Expand Down
1 change: 0 additions & 1 deletion src/jitlayers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

#include "llvm-version.h"
#include "platform.h"
#include "options.h"

#include <llvm/Transforms/Utils/Cloning.h>
#include <llvm/Transforms/Utils/ModuleUtils.h>
Expand Down
2 changes: 2 additions & 0 deletions src/julia.expmap
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
{
global:
__asan*;
__tsan*;
pthread*;
__stack_chk_guard;
asprintf;
bitvector_*;
Expand Down
21 changes: 21 additions & 0 deletions src/julia.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,23 @@
# define JL_THREAD_LOCAL
#endif

// Duplicated from options.h
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems oddly terrible. Can we delete it from there?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe put it in dtypes.h? We've seem to have accumulated a bunch of these kinds of things there.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, for the moment, there's probably no reason not to just put this here. Does raise the question of why jl_task_t is defined in the public header in the first place. We're never passing it by value, so it should probably move to the internals header, but we can do that separately.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, let me just do that first.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nevermind, we need the layout information in the public header because locks depend on it for ABI reasons. We should disentangle all of that at some point, but for now, I'll just leave this here and delete it elsewhere.

#if defined(__has_feature) // Clang flavor
#if __has_feature(address_sanitizer)
#define JL_ASAN_ENABLED
#endif
#if __has_feature(memory_sanitizer)
#define JL_MSAN_ENABLED
#endif
#if __has_feature(thread_sanitizer)
#define JL_TSAN_ENABLED
#endif
#else // GCC flavor
#if defined(__SANITIZE_ADDRESS__)
#define JL_ASAN_ENABLED
#endif
#endif // __has_feature

#define container_of(ptr, type, member) \
((type *) ((char *)(ptr) - offsetof(type, member)))

Expand Down Expand Up @@ -1788,6 +1805,10 @@ typedef struct _jl_task_t {
unsigned int copy_stack:31; // sizeof stack for copybuf
unsigned int started:1;

#if defined(JL_TSAN_ENABLED)
void *tsan_state;
#endif

// current exception handler
jl_handler_t *eh;
// saved gc stack top for context switches
Expand Down
26 changes: 9 additions & 17 deletions src/julia_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,29 +12,20 @@
#define sleep(x) Sleep(1000*x)
#endif

#if defined(__has_feature)
#if __has_feature(address_sanitizer)
#define JL_ASAN_ENABLED // Clang flavor
#endif
#elif defined(__SANITIZE_ADDRESS__)
#define JL_ASAN_ENABLED // GCC flavor
#endif

#ifdef JL_ASAN_ENABLED
#ifdef __cplusplus
extern "C" {
#endif
#ifdef JL_ASAN_ENABLED
void __sanitizer_start_switch_fiber(void**, const void*, size_t);
void __sanitizer_finish_switch_fiber(void*, const void**, size_t*);
#ifdef __cplusplus
}
#endif
#endif

#if defined(__has_feature)
#if __has_feature(memory_sanitizer)
#define JL_MSAN_ENABLED
#ifdef JL_TSAN_ENABLED
void *__tsan_create_fiber(unsigned flags);
// void __tsan_destroy_fiber(void *fiber);
void __tsan_switch_to_fiber(void *fiber, unsigned flags);
#endif
#ifdef __cplusplus
}
#endif

// Remove when C11 is required for C code.
Expand Down Expand Up @@ -321,7 +312,8 @@ jl_value_t *jl_permbox32(jl_datatype_t *t, int32_t x);
jl_value_t *jl_permbox64(jl_datatype_t *t, int64_t x);
jl_svec_t *jl_perm_symsvec(size_t n, ...);

#if !defined(__clang_analyzer__) && !defined(JL_ASAN_ENABLED) // this sizeof(__VA_ARGS__) trick can't be computed until C11, but that only matters to Clang in some situations
// this sizeof(__VA_ARGS__) trick can't be computed until C11, but that only matters to Clang in some situations
#if !defined(__clang_analyzer__) && !(defined(JL_ASAN_ENABLED) || defined(JL_TSAN_ENABLED))
#ifdef __GNUC__
#define jl_perm_symsvec(n, ...) \
(jl_perm_symsvec)(__extension__({ \
Expand Down
19 changes: 7 additions & 12 deletions src/options.h
Original file line number Diff line number Diff line change
Expand Up @@ -154,18 +154,8 @@

// sanitizer defaults ---------------------------------------------------------

// XXX: these macros are duplicated from julia_internal.h
#if defined(__has_feature)
#if __has_feature(address_sanitizer)
#define JL_ASAN_ENABLED
#endif
#elif defined(__SANITIZE_ADDRESS__)
#define JL_ASAN_ENABLED
#endif
#if defined(__has_feature)
#if __has_feature(memory_sanitizer)
#define JL_MSAN_ENABLED
#endif
#ifndef JULIA_H
#error "Must be included after julia.h"
#endif

// Automatically enable MEMDEBUG and KEEP_BODIES for the sanitizers
Expand All @@ -174,6 +164,11 @@
#define KEEP_BODIES
#endif

// TSAN doesn't like COPY_STACKS
#if defined(JL_TSAN_ENABLED) && defined(COPY_STACKS)
#undef COPY_STACKS
#endif

// Memory sanitizer needs TLS, which llvm only supports for the small memory model
#if defined(JL_MSAN_ENABLED)
// todo: fix the llvm MemoryManager to work with small memory model
Expand Down
Loading