From 1a26404f05fb562ed21ee755f44fc9d39743182d Mon Sep 17 00:00:00 2001 From: Anna Zaks Date: Fri, 11 Nov 2016 23:01:02 +0000 Subject: [PATCH] [tsan][llvm] Implement the function attribute to disable TSan checking at run time This implements a function annotation that disables TSan checking for the function at run time. The benefit over attribute((no_sanitize("thread"))) is that the accesses within the callees will also be suppressed. The motivation for this attribute is a guarantee given by the objective C language that the calls to the reference count decrement and object deallocation will be synchronized. To model this properly, we would need to intercept all ref count decrement calls (which are very common in ObjC due to use of ARC) and also every single message send. Instead, we propose to just ignore all accesses made from within dealloc at run time. The main downside is that this still does not introduce any synchronization, which means we might still report false positives if the code that relies on this synchronization is not executed from within dealloc. However, we have not seen this in practice so far and think these cases will be very rare. Differential Revision: https://reviews.llvm.org/D25858 git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@286663 91177308-0d34-0410-b5e6-96231b3b80d8 --- .../Instrumentation/ThreadSanitizer.cpp | 23 ++++++++++++ .../sanitize-thread-no-checking.ll | 35 +++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 test/Instrumentation/ThreadSanitizer/sanitize-thread-no-checking.ll diff --git a/lib/Transforms/Instrumentation/ThreadSanitizer.cpp b/lib/Transforms/Instrumentation/ThreadSanitizer.cpp index 355f69c9aae..2c8728945c3 100644 --- a/lib/Transforms/Instrumentation/ThreadSanitizer.cpp +++ b/lib/Transforms/Instrumentation/ThreadSanitizer.cpp @@ -99,12 +99,15 @@ struct ThreadSanitizer : public FunctionPass { const DataLayout &DL); bool addrPointsToConstantData(Value *Addr); int getMemoryAccessFuncIndex(Value *Addr, const DataLayout &DL); + void InsertRuntimeIgnores(Function &F, SmallVector &RetVec); Type *IntptrTy; IntegerType *OrdTy; // Callbacks to run-time library are computed in doInitialization. Function *TsanFuncEntry; Function *TsanFuncExit; + Function *TsanIgnoreBegin; + Function *TsanIgnoreEnd; // Accesses sizes are powers of two: 1, 2, 4, 8, 16. static const size_t kNumberOfAccessSizes = 5; Function *TsanRead[kNumberOfAccessSizes]; @@ -152,6 +155,10 @@ void ThreadSanitizer::initializeCallbacks(Module &M) { "__tsan_func_entry", IRB.getVoidTy(), IRB.getInt8PtrTy(), nullptr)); TsanFuncExit = checkSanitizerInterfaceFunction( M.getOrInsertFunction("__tsan_func_exit", IRB.getVoidTy(), nullptr)); + TsanIgnoreBegin = checkSanitizerInterfaceFunction(M.getOrInsertFunction( + "__tsan_ignore_thread_begin", IRB.getVoidTy(), nullptr)); + TsanIgnoreEnd = checkSanitizerInterfaceFunction(M.getOrInsertFunction( + "__tsan_ignore_thread_end", IRB.getVoidTy(), nullptr)); OrdTy = IRB.getInt32Ty(); for (size_t i = 0; i < kNumberOfAccessSizes; ++i) { const unsigned ByteSize = 1U << i; @@ -376,6 +383,16 @@ static bool isAtomic(Instruction *I) { return false; } +void ThreadSanitizer::InsertRuntimeIgnores(Function &F, + SmallVector &RetVec) { + IRBuilder<> IRB(F.getEntryBlock().getFirstNonPHI()); + IRB.CreateCall(TsanIgnoreBegin); + for (auto RetInst : RetVec) { + IRBuilder<> IRB(RetInst); + IRB.CreateCall(TsanIgnoreEnd); + } +} + bool ThreadSanitizer::runOnFunction(Function &F) { // This is required to prevent instrumenting call to __tsan_init from within // the module constructor. @@ -438,6 +455,12 @@ bool ThreadSanitizer::runOnFunction(Function &F) { Res |= instrumentMemIntrinsic(Inst); } + if (F.hasFnAttribute("sanitize_thread_no_checking_at_run_time")) { + assert(!F.hasFnAttribute(Attribute::SanitizeThread)); + if (HasCalls) + InsertRuntimeIgnores(F, RetVec); + } + // Instrument function entry/exit points if there were instrumented accesses. if ((Res || HasCalls) && ClInstrumentFuncEntryExit) { IRBuilder<> IRB(F.getEntryBlock().getFirstNonPHI()); diff --git a/test/Instrumentation/ThreadSanitizer/sanitize-thread-no-checking.ll b/test/Instrumentation/ThreadSanitizer/sanitize-thread-no-checking.ll new file mode 100644 index 00000000000..1bb3291f484 --- /dev/null +++ b/test/Instrumentation/ThreadSanitizer/sanitize-thread-no-checking.ll @@ -0,0 +1,35 @@ +; RUN: opt < %s -tsan -S | FileCheck %s + +target datalayout = "e-p:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v64:64:64-v128:128:128-a0:0:64-s0:64:64-f80:128:128-n8:16:32:64" +target triple = "x86_64-unknown-linux-gnu" + +define i32 @"\01-[NoCalls dealloc]"(i32* %a) "sanitize_thread_no_checking_at_run_time" { +entry: + %tmp1 = load i32, i32* %a, align 4 + ret i32 %tmp1 +} + +; CHECK: define i32 @"\01-[NoCalls dealloc]"(i32* %a) +; CHECK-NEXT: entry: +; CHECK-NEXT: %tmp1 = load i32, i32* %a, align 4 +; CHECK-NEXT: ret i32 %tmp1 + +declare void @"foo"() + +define i32 @"\01-[WithCalls dealloc]"(i32* %a) "sanitize_thread_no_checking_at_run_time" { +entry: + %tmp1 = load i32, i32* %a, align 4 + call void @foo() + ret i32 %tmp1 +} + +; CHECK: define i32 @"\01-[WithCalls dealloc]"(i32* %a) +; CHECK-NEXT: entry: +; CHECK-NEXT: %0 = call i8* @llvm.returnaddress(i32 0) +; CHECK-NEXT: call void @__tsan_func_entry(i8* %0) +; CHECK-NEXT: call void @__tsan_ignore_thread_begin() +; CHECK-NEXT: %tmp1 = load i32, i32* %a, align 4 +; CHECK-NEXT: call void @foo() +; CHECK-NEXT: call void @__tsan_ignore_thread_end() +; CHECK-NEXT: call void @__tsan_func_exit() +; CHECK-NEXT: ret i32 %tmp1 -- 2.50.1