diff --git a/Make.inc b/Make.inc index 21a0299318b51..c79b229ddebfc 100644 --- a/Make.inc +++ b/Make.inc @@ -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++ @@ -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 @@ -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') diff --git a/deps/llvm.mk b/deps/llvm.mk index 5a6faaccf0e0e..a0cb21e7f13f0 100644 --- a/deps/llvm.mk +++ b/deps/llvm.mk @@ -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) @@ -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) @@ -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 diff --git a/deps/patches/llvm-julia-tsan-custom-as.patch b/deps/patches/llvm-julia-tsan-custom-as.patch new file mode 100644 index 0000000000000..a6f8a42ad2e32 --- /dev/null +++ b/deps/patches/llvm-julia-tsan-custom-as.patch @@ -0,0 +1,28 @@ +From bd41be423127b8946daea805290ad2eb19e66be4 Mon Sep 17 00:00:00 2001 +From: Valentin Churavy +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(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 + diff --git a/doc/src/devdocs/sanitizers.md b/doc/src/devdocs/sanitizers.md index 7b065181a87c1..ce32592bf01a0 100644 --- a/doc/src/devdocs/sanitizers.md +++ b/doc/src/devdocs/sanitizers.md @@ -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 @@ -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`. diff --git a/src/aotcompile.cpp b/src/aotcompile.cpp index 9d90a69155701..45e91ea0ecb57 100644 --- a/src/aotcompile.cpp +++ b/src/aotcompile.cpp @@ -24,6 +24,9 @@ #if defined(JL_ASAN_ENABLED) #include #endif +#if defined(JL_TSAN_ENABLED) +#include +#endif #include #include #include @@ -624,6 +627,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()); @@ -641,6 +645,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; } @@ -764,6 +771,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 diff --git a/src/codegen.cpp b/src/codegen.cpp index ff9ded7dae850..8ed34fdda5eaf 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -5787,6 +5787,12 @@ static std::pair, 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) { diff --git a/src/dlload.c b/src/dlload.c index cd0acdab8854c..ca60e9557fee5 100644 --- a/src/dlload.c +++ b/src/dlload.c @@ -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 diff --git a/src/julia.expmap b/src/julia.expmap index f0b808d8db1f6..daf3a01749ed9 100644 --- a/src/julia.expmap +++ b/src/julia.expmap @@ -1,6 +1,8 @@ { global: __asan*; + __tsan*; + pthread*; __stack_chk_guard; asprintf; bitvector_*; diff --git a/src/julia.h b/src/julia.h index eae7fbc335bb0..ba16b165e792e 100644 --- a/src/julia.h +++ b/src/julia.h @@ -65,6 +65,23 @@ # define JL_THREAD_LOCAL #endif +// Duplicated from options.h +#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))) @@ -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 diff --git a/src/julia_internal.h b/src/julia_internal.h index 930ec96252f8e..d7c857a78439d 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -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. @@ -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__({ \ diff --git a/src/options.h b/src/options.h index 00e61d7b38fb7..dd90c8dda5881 100644 --- a/src/options.h +++ b/src/options.h @@ -154,19 +154,21 @@ // sanitizer defaults --------------------------------------------------------- -// XXX: these macros are duplicated from julia_internal.h -#if defined(__has_feature) +#if defined(__has_feature) // Clang flavor #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 +#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 // Automatically enable MEMDEBUG and KEEP_BODIES for the sanitizers #if defined(JL_ASAN_ENABLED) || defined(JL_MSAN_ENABLED) @@ -174,6 +176,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 diff --git a/src/task.c b/src/task.c index fd93695b9791f..8fa20462bb52a 100644 --- a/src/task.c +++ b/src/task.c @@ -40,7 +40,7 @@ extern "C" { #endif -#ifdef JL_ASAN_ENABLED +#if defined(JL_ASAN_ENABLED) static inline void sanitizer_start_switch_fiber(const void* bottom, size_t size) { __sanitizer_start_switch_fiber(NULL, bottom, size); } @@ -52,6 +52,22 @@ static inline void sanitizer_start_switch_fiber(const void* bottom, size_t size) static inline void sanitizer_finish_switch_fiber(void) {} #endif +#if defined(JL_TSAN_ENABLED) +static inline void *tsan_create_fiber() { + return __tsan_create_fiber(0); +} +// static inline void tsan_destroy_fiber(jl_task_t *fiber) { +// __tsan_destroy_fiber(fiber->tsan_state); +// } +static inline void tsan_switch_to_fiber(jl_task_t *fiber) { + __tsan_switch_to_fiber(fiber->tsan_state, 0); +} +#else +static inline void *tsan_create_fiber() {} +// static inline void tsan_destroy_fiber(void *fiber) {} +static inline void tsan_switch_to_fiber(jl_task_t *fiber) {} +#endif + #if defined(_OS_WINDOWS_) volatile int jl_in_stackwalk = 0; #else @@ -151,6 +167,7 @@ static void NOINLINE JL_NORETURN restore_stack(jl_task_t *t, jl_ptls_t ptls, cha assert(_x != NULL && _y != NULL); memcpy_a16((uint64_t*)_x, (uint64_t*)_y, nb); // destroys all but the current stackframe + tsan_switch_to_fiber(t); sanitizer_start_switch_fiber(t->stkbuf, t->bufsz); jl_set_fiber(&t->ctx); abort(); // unreachable @@ -162,6 +179,7 @@ static void restore_stack2(jl_task_t *t, jl_ptls_t ptls, jl_task_t *lastt) void *_y = t->stkbuf; assert(_x != NULL && _y != NULL); memcpy_a16((uint64_t*)_x, (uint64_t*)_y, nb); // destroys all but the current stackframe + tsan_switch_to_fiber(t); sanitizer_start_switch_fiber(t->stkbuf, t->bufsz); jl_swap_fiber(&lastt->ctx, &t->ctx); sanitizer_finish_switch_fiber(); @@ -352,16 +370,19 @@ static void ctx_switch(jl_ptls_t ptls) else #endif if (!lastt_ctx) { + tsan_switch_to_fiber(t); sanitizer_start_switch_fiber(t->stkbuf, t->bufsz); jl_set_fiber(&t->ctx); // (doesn't return) abort(); // unreachable } else { + tsan_switch_to_fiber(t); sanitizer_start_switch_fiber(t->stkbuf, t->bufsz); jl_swap_fiber(lastt_ctx, &t->ctx); sanitizer_finish_switch_fiber(); } } else { + tsan_switch_to_fiber(t); sanitizer_start_switch_fiber(t->stkbuf, t->bufsz); if (always_copy_stacks) { #ifdef COPY_STACKS @@ -592,6 +613,9 @@ JL_DLLEXPORT jl_task_t *jl_new_task(jl_function_t *start, jl_value_t *completion t->tid = -1; #ifdef ENABLE_TIMINGS t->timing_stack = jl_root_timing; +#endif +#ifdef JL_TSAN_ENABLED + t->tsan_state = tsan_create_fiber(); #endif arraylist_new(&t->locks, 0); @@ -792,6 +816,7 @@ static void start_basefiber(void) jl_ptls_t ptls = jl_get_ptls_states(); if (jl_setjmp(ptls->base_ctx.uc_mcontext, 0)) start_task(); // sanitizer_finish_switch_fiber is part of start_task + tsan_switch_to_fiber(jl_root_task); sanitizer_start_switch_fiber(jl_root_task->stkbuf, jl_root_task->bufsz); jl_longjmp(jl_root_task->ctx.uc_mcontext, 1); abort(); // unreachable @@ -860,6 +885,7 @@ static void jl_init_basefiber(size_t ssize) char *stkbuf = jl_alloc_fiber(&ptls->base_ctx, &ssize, NULL); ptls->stackbase = stkbuf + ssize; ptls->stacksize = ssize; + // TSAN doesn't like COPY_STACKS sanitizer_start_switch_fiber(stkbuf, sksize); jl_start_fiber(jl_root_task, &ptls->base_ctx); // finishes initializing jl_basectx sanitizer_finish_switch_fiber(); @@ -1122,6 +1148,10 @@ void jl_init_root_task(void *stack_lo, void *stack_hi) ptls->current_task->sticky = 1; arraylist_new(&ptls->current_task->locks, 0); +#ifdef JL_TSAN_ENABLED + ptls->current_task->tsan_state = tsan_create_fiber(); +#endif + #ifdef COPY_STACKS if (always_copy_stacks) { ptls->stackbase = stack_hi;