Skip to content

Commit 785d41a

Browse files
committed
[TSan] Add option for emitting compound read-write instrumentation
This adds option -tsan-compound-read-before-write to emit different instrumentation for the write if the read before that write is omitted from instrumentation. The default TSan runtime currently does not support the different instrumentation, and the option is disabled by default. Alternative runtimes, such as the Kernel Concurrency Sanitizer (KCSAN) can make use of the feature. Indeed, the initial motivation is for use in KCSAN as it was determined that due to the Linux kernel having a large number of unaddressed data races, it makes sense to improve performance and reporting by distinguishing compounded operations. E.g. the compounded instrumentation is typically emitted for compound operations such as ++, +=, |=, etc. By emitting different reports, such data races can easily be noticed, and also automatically bucketed differently by CI systems. Reviewed By: dvyukov, glider Tags: #llvm Differential Revision: https://reviews.llvm.org/D83867
1 parent 0db3ac3 commit 785d41a

File tree

2 files changed

+177
-66
lines changed

2 files changed

+177
-66
lines changed

llvm/lib/Transforms/Instrumentation/ThreadSanitizer.cpp

Lines changed: 122 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
//===----------------------------------------------------------------------===//
2020

2121
#include "llvm/Transforms/Instrumentation/ThreadSanitizer.h"
22-
#include "llvm/ADT/SmallPtrSet.h"
22+
#include "llvm/ADT/DenseMap.h"
23+
#include "llvm/ADT/Optional.h"
2324
#include "llvm/ADT/SmallString.h"
2425
#include "llvm/ADT/SmallVector.h"
2526
#include "llvm/ADT/Statistic.h"
@@ -52,30 +53,36 @@ using namespace llvm;
5253

5354
#define DEBUG_TYPE "tsan"
5455

55-
static cl::opt<bool> ClInstrumentMemoryAccesses(
56+
static cl::opt<bool> ClInstrumentMemoryAccesses(
5657
"tsan-instrument-memory-accesses", cl::init(true),
5758
cl::desc("Instrument memory accesses"), cl::Hidden);
58-
static cl::opt<bool> ClInstrumentFuncEntryExit(
59-
"tsan-instrument-func-entry-exit", cl::init(true),
60-
cl::desc("Instrument function entry and exit"), cl::Hidden);
61-
static cl::opt<bool> ClHandleCxxExceptions(
59+
static cl::opt<bool>
60+
ClInstrumentFuncEntryExit("tsan-instrument-func-entry-exit", cl::init(true),
61+
cl::desc("Instrument function entry and exit"),
62+
cl::Hidden);
63+
static cl::opt<bool> ClHandleCxxExceptions(
6264
"tsan-handle-cxx-exceptions", cl::init(true),
6365
cl::desc("Handle C++ exceptions (insert cleanup blocks for unwinding)"),
6466
cl::Hidden);
65-
static cl::opt<bool> ClInstrumentAtomics(
66-
"tsan-instrument-atomics", cl::init(true),
67-
cl::desc("Instrument atomics"), cl::Hidden);
68-
static cl::opt<bool> ClInstrumentMemIntrinsics(
67+
static cl::opt<bool> ClInstrumentAtomics("tsan-instrument-atomics",
68+
cl::init(true),
69+
cl::desc("Instrument atomics"),
70+
cl::Hidden);
71+
static cl::opt<bool> ClInstrumentMemIntrinsics(
6972
"tsan-instrument-memintrinsics", cl::init(true),
7073
cl::desc("Instrument memintrinsics (memset/memcpy/memmove)"), cl::Hidden);
71-
static cl::opt<bool> ClDistinguishVolatile(
74+
static cl::opt<bool> ClDistinguishVolatile(
7275
"tsan-distinguish-volatile", cl::init(false),
7376
cl::desc("Emit special instrumentation for accesses to volatiles"),
7477
cl::Hidden);
75-
static cl::opt<bool> ClInstrumentReadBeforeWrite(
78+
static cl::opt<bool> ClInstrumentReadBeforeWrite(
7679
"tsan-instrument-read-before-write", cl::init(false),
7780
cl::desc("Do not eliminate read instrumentation for read-before-writes"),
7881
cl::Hidden);
82+
static cl::opt<bool> ClCompoundReadBeforeWrite(
83+
"tsan-compound-read-before-write", cl::init(false),
84+
cl::desc("Emit special compound instrumentation for reads-before-writes"),
85+
cl::Hidden);
7986

8087
STATISTIC(NumInstrumentedReads, "Number of instrumented reads");
8188
STATISTIC(NumInstrumentedWrites, "Number of instrumented writes");
@@ -101,15 +108,37 @@ namespace {
101108
/// ensures the __tsan_init function is in the list of global constructors for
102109
/// the module.
103110
struct ThreadSanitizer {
111+
ThreadSanitizer() {
112+
// Sanity check options and warn user.
113+
if (ClInstrumentReadBeforeWrite && ClCompoundReadBeforeWrite) {
114+
errs()
115+
<< "warning: Option -tsan-compound-read-before-write has no effect "
116+
"when -tsan-instrument-read-before-write is set.\n";
117+
}
118+
}
119+
104120
bool sanitizeFunction(Function &F, const TargetLibraryInfo &TLI);
105121

106122
private:
123+
// Internal Instruction wrapper that contains more information about the
124+
// Instruction from prior analysis.
125+
struct InstructionInfo {
126+
// Instrumentation emitted for this instruction is for a compounded set of
127+
// read and write operations in the same basic block.
128+
static constexpr unsigned kCompoundRW = (1U << 0);
129+
130+
explicit InstructionInfo(Instruction *Inst) : Inst(Inst) {}
131+
132+
Instruction *Inst;
133+
unsigned Flags = 0;
134+
};
135+
107136
void initialize(Module &M);
108-
bool instrumentLoadOrStore(Instruction *I, const DataLayout &DL);
137+
bool instrumentLoadOrStore(const InstructionInfo &II, const DataLayout &DL);
109138
bool instrumentAtomic(Instruction *I, const DataLayout &DL);
110139
bool instrumentMemIntrinsic(Instruction *I);
111140
void chooseInstructionsToInstrument(SmallVectorImpl<Instruction *> &Local,
112-
SmallVectorImpl<Instruction *> &All,
141+
SmallVectorImpl<InstructionInfo> &All,
113142
const DataLayout &DL);
114143
bool addrPointsToConstantData(Value *Addr);
115144
int getMemoryAccessFuncIndex(Value *Addr, const DataLayout &DL);
@@ -130,6 +159,8 @@ struct ThreadSanitizer {
130159
FunctionCallee TsanVolatileWrite[kNumberOfAccessSizes];
131160
FunctionCallee TsanUnalignedVolatileRead[kNumberOfAccessSizes];
132161
FunctionCallee TsanUnalignedVolatileWrite[kNumberOfAccessSizes];
162+
FunctionCallee TsanCompoundRW[kNumberOfAccessSizes];
163+
FunctionCallee TsanUnalignedCompoundRW[kNumberOfAccessSizes];
133164
FunctionCallee TsanAtomicLoad[kNumberOfAccessSizes];
134165
FunctionCallee TsanAtomicStore[kNumberOfAccessSizes];
135166
FunctionCallee TsanAtomicRMW[AtomicRMWInst::LAST_BINOP + 1]
@@ -268,6 +299,15 @@ void ThreadSanitizer::initialize(Module &M) {
268299
TsanUnalignedVolatileWrite[i] = M.getOrInsertFunction(
269300
UnalignedVolatileWriteName, Attr, IRB.getVoidTy(), IRB.getInt8PtrTy());
270301

302+
SmallString<64> CompoundRWName("__tsan_read_write" + ByteSizeStr);
303+
TsanCompoundRW[i] = M.getOrInsertFunction(
304+
CompoundRWName, Attr, IRB.getVoidTy(), IRB.getInt8PtrTy());
305+
306+
SmallString<64> UnalignedCompoundRWName("__tsan_unaligned_read_write" +
307+
ByteSizeStr);
308+
TsanUnalignedCompoundRW[i] = M.getOrInsertFunction(
309+
UnalignedCompoundRWName, Attr, IRB.getVoidTy(), IRB.getInt8PtrTy());
310+
271311
Type *Ty = Type::getIntNTy(M.getContext(), BitSize);
272312
Type *PtrTy = Ty->getPointerTo();
273313
SmallString<32> AtomicLoadName("__tsan_atomic" + BitSizeStr + "_load");
@@ -402,34 +442,42 @@ bool ThreadSanitizer::addrPointsToConstantData(Value *Addr) {
402442
// 'Local' is a vector of insns within the same BB (no calls between).
403443
// 'All' is a vector of insns that will be instrumented.
404444
void ThreadSanitizer::chooseInstructionsToInstrument(
405-
SmallVectorImpl<Instruction *> &Local, SmallVectorImpl<Instruction *> &All,
406-
const DataLayout &DL) {
407-
SmallPtrSet<Value*, 8> WriteTargets;
445+
SmallVectorImpl<Instruction *> &Local,
446+
SmallVectorImpl<InstructionInfo> &All, const DataLayout &DL) {
447+
DenseMap<Value *, size_t> WriteTargets; // Map of addresses to index in All
408448
// Iterate from the end.
409449
for (Instruction *I : reverse(Local)) {
410-
if (StoreInst *Store = dyn_cast<StoreInst>(I)) {
411-
Value *Addr = Store->getPointerOperand();
412-
if (!shouldInstrumentReadWriteFromAddress(I->getModule(), Addr))
413-
continue;
414-
WriteTargets.insert(Addr);
415-
} else {
416-
LoadInst *Load = cast<LoadInst>(I);
417-
Value *Addr = Load->getPointerOperand();
418-
if (!shouldInstrumentReadWriteFromAddress(I->getModule(), Addr))
419-
continue;
420-
if (!ClInstrumentReadBeforeWrite && WriteTargets.count(Addr)) {
421-
// We will write to this temp, so no reason to analyze the read.
422-
NumOmittedReadsBeforeWrite++;
423-
continue;
450+
const bool IsWrite = isa<StoreInst>(*I);
451+
Value *Addr = IsWrite ? cast<StoreInst>(I)->getPointerOperand()
452+
: cast<LoadInst>(I)->getPointerOperand();
453+
454+
if (!shouldInstrumentReadWriteFromAddress(I->getModule(), Addr))
455+
continue;
456+
457+
if (!IsWrite) {
458+
const auto WriteEntry = WriteTargets.find(Addr);
459+
if (!ClInstrumentReadBeforeWrite && WriteEntry != WriteTargets.end()) {
460+
auto &WI = All[WriteEntry->second];
461+
// If we distinguish volatile accesses and if either the read or write
462+
// is volatile, do not omit any instrumentation.
463+
const bool AnyVolatile =
464+
ClDistinguishVolatile && (cast<LoadInst>(I)->isVolatile() ||
465+
cast<StoreInst>(WI.Inst)->isVolatile());
466+
if (!AnyVolatile) {
467+
// We will write to this temp, so no reason to analyze the read.
468+
// Mark the write instruction as compound.
469+
WI.Flags |= InstructionInfo::kCompoundRW;
470+
NumOmittedReadsBeforeWrite++;
471+
continue;
472+
}
424473
}
474+
425475
if (addrPointsToConstantData(Addr)) {
426476
// Addr points to some constant data -- it can not race with any writes.
427477
continue;
428478
}
429479
}
430-
Value *Addr = isa<StoreInst>(*I)
431-
? cast<StoreInst>(I)->getPointerOperand()
432-
: cast<LoadInst>(I)->getPointerOperand();
480+
433481
if (isa<AllocaInst>(GetUnderlyingObject(Addr, DL)) &&
434482
!PointerMayBeCaptured(Addr, true, true)) {
435483
// The variable is addressable but not captured, so it cannot be
@@ -438,7 +486,14 @@ void ThreadSanitizer::chooseInstructionsToInstrument(
438486
NumOmittedNonCaptured++;
439487
continue;
440488
}
441-
All.push_back(I);
489+
490+
// Instrument this instruction.
491+
All.emplace_back(I);
492+
if (IsWrite) {
493+
// For read-before-write and compound instrumentation we only need one
494+
// write target, and we can override any previous entry if it exists.
495+
WriteTargets[Addr] = All.size() - 1;
496+
}
442497
}
443498
Local.clear();
444499
}
@@ -479,7 +534,7 @@ bool ThreadSanitizer::sanitizeFunction(Function &F,
479534
if (F.hasFnAttribute(Attribute::Naked))
480535
return false;
481536
initialize(*F.getParent());
482-
SmallVector<Instruction*, 8> AllLoadsAndStores;
537+
SmallVector<InstructionInfo, 8> AllLoadsAndStores;
483538
SmallVector<Instruction*, 8> LocalLoadsAndStores;
484539
SmallVector<Instruction*, 8> AtomicAccesses;
485540
SmallVector<Instruction*, 8> MemIntrinCalls;
@@ -514,8 +569,8 @@ bool ThreadSanitizer::sanitizeFunction(Function &F,
514569

515570
// Instrument memory accesses only if we want to report bugs in the function.
516571
if (ClInstrumentMemoryAccesses && SanitizeFunction)
517-
for (auto Inst : AllLoadsAndStores) {
518-
Res |= instrumentLoadOrStore(Inst, DL);
572+
for (const auto &II : AllLoadsAndStores) {
573+
Res |= instrumentLoadOrStore(II, DL);
519574
}
520575

521576
// Instrument atomic memory accesses in any case (they can be used to
@@ -553,13 +608,12 @@ bool ThreadSanitizer::sanitizeFunction(Function &F,
553608
return Res;
554609
}
555610

556-
bool ThreadSanitizer::instrumentLoadOrStore(Instruction *I,
611+
bool ThreadSanitizer::instrumentLoadOrStore(const InstructionInfo &II,
557612
const DataLayout &DL) {
558-
IRBuilder<> IRB(I);
559-
bool IsWrite = isa<StoreInst>(*I);
560-
Value *Addr = IsWrite
561-
? cast<StoreInst>(I)->getPointerOperand()
562-
: cast<LoadInst>(I)->getPointerOperand();
613+
IRBuilder<> IRB(II.Inst);
614+
const bool IsWrite = isa<StoreInst>(*II.Inst);
615+
Value *Addr = IsWrite ? cast<StoreInst>(II.Inst)->getPointerOperand()
616+
: cast<LoadInst>(II.Inst)->getPointerOperand();
563617

564618
// swifterror memory addresses are mem2reg promoted by instruction selection.
565619
// As such they cannot have regular uses like an instrumentation function and
@@ -570,9 +624,9 @@ bool ThreadSanitizer::instrumentLoadOrStore(Instruction *I,
570624
int Idx = getMemoryAccessFuncIndex(Addr, DL);
571625
if (Idx < 0)
572626
return false;
573-
if (IsWrite && isVtableAccess(I)) {
574-
LLVM_DEBUG(dbgs() << " VPTR : " << *I << "\n");
575-
Value *StoredValue = cast<StoreInst>(I)->getValueOperand();
627+
if (IsWrite && isVtableAccess(II.Inst)) {
628+
LLVM_DEBUG(dbgs() << " VPTR : " << *II.Inst << "\n");
629+
Value *StoredValue = cast<StoreInst>(II.Inst)->getValueOperand();
576630
// StoredValue may be a vector type if we are storing several vptrs at once.
577631
// In this case, just take the first element of the vector since this is
578632
// enough to find vptr races.
@@ -588,36 +642,46 @@ bool ThreadSanitizer::instrumentLoadOrStore(Instruction *I,
588642
NumInstrumentedVtableWrites++;
589643
return true;
590644
}
591-
if (!IsWrite && isVtableAccess(I)) {
645+
if (!IsWrite && isVtableAccess(II.Inst)) {
592646
IRB.CreateCall(TsanVptrLoad,
593647
IRB.CreatePointerCast(Addr, IRB.getInt8PtrTy()));
594648
NumInstrumentedVtableReads++;
595649
return true;
596650
}
597-
const unsigned Alignment = IsWrite
598-
? cast<StoreInst>(I)->getAlignment()
599-
: cast<LoadInst>(I)->getAlignment();
600-
const bool IsVolatile =
601-
ClDistinguishVolatile && (IsWrite ? cast<StoreInst>(I)->isVolatile()
602-
: cast<LoadInst>(I)->isVolatile());
651+
652+
const unsigned Alignment = IsWrite ? cast<StoreInst>(II.Inst)->getAlignment()
653+
: cast<LoadInst>(II.Inst)->getAlignment();
654+
const bool IsCompoundRW =
655+
ClCompoundReadBeforeWrite && (II.Flags & InstructionInfo::kCompoundRW);
656+
const bool IsVolatile = ClDistinguishVolatile &&
657+
(IsWrite ? cast<StoreInst>(II.Inst)->isVolatile()
658+
: cast<LoadInst>(II.Inst)->isVolatile());
659+
assert((!IsVolatile || !IsCompoundRW) && "Compound volatile invalid!");
660+
603661
Type *OrigTy = cast<PointerType>(Addr->getType())->getElementType();
604662
const uint32_t TypeSize = DL.getTypeStoreSizeInBits(OrigTy);
605663
FunctionCallee OnAccessFunc = nullptr;
606664
if (Alignment == 0 || Alignment >= 8 || (Alignment % (TypeSize / 8)) == 0) {
607-
if (IsVolatile)
665+
if (IsCompoundRW)
666+
OnAccessFunc = TsanCompoundRW[Idx];
667+
else if (IsVolatile)
608668
OnAccessFunc = IsWrite ? TsanVolatileWrite[Idx] : TsanVolatileRead[Idx];
609669
else
610670
OnAccessFunc = IsWrite ? TsanWrite[Idx] : TsanRead[Idx];
611671
} else {
612-
if (IsVolatile)
672+
if (IsCompoundRW)
673+
OnAccessFunc = TsanUnalignedCompoundRW[Idx];
674+
else if (IsVolatile)
613675
OnAccessFunc = IsWrite ? TsanUnalignedVolatileWrite[Idx]
614676
: TsanUnalignedVolatileRead[Idx];
615677
else
616678
OnAccessFunc = IsWrite ? TsanUnalignedWrite[Idx] : TsanUnalignedRead[Idx];
617679
}
618680
IRB.CreateCall(OnAccessFunc, IRB.CreatePointerCast(Addr, IRB.getInt8PtrTy()));
619-
if (IsWrite) NumInstrumentedWrites++;
620-
else NumInstrumentedReads++;
681+
if (IsCompoundRW || IsWrite)
682+
NumInstrumentedWrites++;
683+
if (IsCompoundRW || !IsWrite)
684+
NumInstrumentedReads++;
621685
return true;
622686
}
623687

Lines changed: 55 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
; RUN: opt < %s -tsan -S | FileCheck %s
1+
; RUN: opt < %s -tsan -S | FileCheck --check-prefixes=CHECK,CHECK-OPT %s
22
; RUN: opt < %s -tsan -tsan-instrument-read-before-write -S | FileCheck %s --check-prefixes=CHECK,CHECK-UNOPT
3+
; RUN: opt < %s -tsan -tsan-compound-read-before-write -S | FileCheck %s --check-prefixes=CHECK,CHECK-COMPOUND
4+
; RUN: opt < %s -tsan -tsan-distinguish-volatile -tsan-compound-read-before-write -S | FileCheck %s --check-prefixes=CHECK,CHECK-COMPOUND-VOLATILE
35

46
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-S128"
57

@@ -10,10 +12,13 @@ entry:
1012
store i32 %inc, i32* %ptr, align 4
1113
ret void
1214
}
13-
; CHECK: define void @IncrementMe
14-
; CHECK-NOT: __tsan_read
15-
; CHECK-UNOPT: __tsan_read
16-
; CHECK: __tsan_write
15+
; CHECK-LABEL: define void @IncrementMe
16+
; CHECK-OPT-NOT: __tsan_read4
17+
; CHECK-COMPOUND-NOT: __tsan_read4
18+
; CHECK-UNOPT: __tsan_read4
19+
; CHECK-OPT: __tsan_write4
20+
; CHECK-UNOPT: __tsan_write4
21+
; CHECK-COMPOUND: __tsan_read_write4
1722
; CHECK: ret void
1823

1924
define void @IncrementMeWithCallInBetween(i32* nocapture %ptr) nounwind uwtable sanitize_thread {
@@ -25,10 +30,52 @@ entry:
2530
ret void
2631
}
2732

28-
; CHECK: define void @IncrementMeWithCallInBetween
29-
; CHECK: __tsan_read
30-
; CHECK: __tsan_write
33+
; CHECK-LABEL: define void @IncrementMeWithCallInBetween
34+
; CHECK: __tsan_read4
35+
; CHECK: __tsan_write4
3136
; CHECK: ret void
3237

3338
declare void @foo()
3439

40+
define void @VolatileLoad(i32* nocapture %ptr) nounwind uwtable sanitize_thread {
41+
entry:
42+
%0 = load volatile i32, i32* %ptr, align 4
43+
%inc = add nsw i32 %0, 1
44+
store i32 %inc, i32* %ptr, align 4
45+
ret void
46+
}
47+
; CHECK-LABEL: define void @VolatileLoad
48+
; CHECK-COMPOUND-NOT: __tsan_read4
49+
; CHECK-COMPOUND-VOLATILE: __tsan_volatile_read4
50+
; CHECK-COMPOUND: __tsan_read_write4
51+
; CHECK-COMPOUND-VOLATILE: __tsan_write4
52+
; CHECK: ret void
53+
54+
define void @VolatileStore(i32* nocapture %ptr) nounwind uwtable sanitize_thread {
55+
entry:
56+
%0 = load i32, i32* %ptr, align 4
57+
%inc = add nsw i32 %0, 1
58+
store volatile i32 %inc, i32* %ptr, align 4
59+
ret void
60+
}
61+
; CHECK-LABEL: define void @VolatileStore
62+
; CHECK-COMPOUND-NOT: __tsan_read4
63+
; CHECK-COMPOUND-VOLATILE: __tsan_read4
64+
; CHECK-COMPOUND: __tsan_read_write4
65+
; CHECK-COMPOUND-VOLATILE: __tsan_volatile_write4
66+
; CHECK: ret void
67+
68+
define void @VolatileBoth(i32* nocapture %ptr) nounwind uwtable sanitize_thread {
69+
entry:
70+
%0 = load volatile i32, i32* %ptr, align 4
71+
%inc = add nsw i32 %0, 1
72+
store volatile i32 %inc, i32* %ptr, align 4
73+
ret void
74+
}
75+
; CHECK-LABEL: define void @VolatileBoth
76+
; CHECK-COMPOUND-NOT: __tsan_read4
77+
; CHECK-COMPOUND-VOLATILE: __tsan_volatile_read4
78+
; CHECK-COMPOUND: __tsan_read_write4
79+
; CHECK-COMPOUND-VOLATILE: __tsan_volatile_write4
80+
; CHECK: ret void
81+

0 commit comments

Comments
 (0)