Skip to content

Commit 15821d0

Browse files
committed
First version
1 parent d9a9960 commit 15821d0

File tree

7 files changed

+312
-15
lines changed

7 files changed

+312
-15
lines changed
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// RUN: %clangxx -fsanitize=realtime %s -o %t -O3
2+
// RUN: %run %t 2>&1 | FileCheck %s --allow-empty
3+
// RUN: %clangxx -fsanitize=realtime %s -o %t -O2
4+
// RUN: %run %t 2>&1 | FileCheck %s --allow-empty
5+
// RUN: %clangxx -fsanitize=realtime %s -o %t -O1
6+
// RUN: %run %t 2>&1 | FileCheck %s --allow-empty
7+
8+
// TODO: this test is failing with O0, investigate why
9+
// xxx: %clangxx -fsanitize=realtime %s -o %t -O0
10+
// xxx: %run %t 2>&1 | FileCheck %s
11+
12+
// UNSUPPORTED: ios
13+
14+
// Intent: Ensure basic bound audio loops don't trigger rtsan.
15+
16+
#include <stdio.h>
17+
#include <stdlib.h>
18+
19+
#include <assert.h>
20+
21+
22+
void FillBufferInterleaved(int* buffer, int sample_count, int channel_count) [[clang::nonblocking]] {
23+
for (int sample = 0; sample < sample_count; sample++)
24+
for (int channel = 0; channel < channel_count; channel++)
25+
buffer[sample * channel_count + channel] = sample;
26+
}
27+
28+
void Deinterleave(int* buffer, int sample_count, int channel_count, int* scratch_buffer) [[clang::nonblocking]] {
29+
for (int channel = 0; channel < channel_count; channel++)
30+
for (int sample = 0; sample < sample_count; sample++) {
31+
int interleaved_index = sample * channel_count + channel;
32+
int deinterleaved_index = channel * sample_count + sample;
33+
scratch_buffer[deinterleaved_index] = buffer[interleaved_index];
34+
}
35+
36+
for (int i = 0; i < sample_count * channel_count; i++)
37+
buffer[i] = scratch_buffer[i];
38+
}
39+
40+
int main() {
41+
const int sample_count = 10;
42+
const int channel_count = 2;
43+
int buffer[channel_count * sample_count];
44+
45+
FillBufferInterleaved(buffer, sample_count, channel_count);
46+
47+
assert(buffer[0] == 0);
48+
assert(buffer[1] == 0);
49+
assert(buffer[2] == 1);
50+
assert(buffer[3] == 1);
51+
52+
assert(buffer[18] == 9);
53+
assert(buffer[19] == 9);
54+
55+
int scratch_buffer[channel_count * sample_count];
56+
57+
Deinterleave(buffer, sample_count, channel_count, scratch_buffer);
58+
59+
assert(buffer[0] == 0);
60+
assert(buffer[1] == 1);
61+
assert(buffer[8] == 8);
62+
assert(buffer[9] == 9);
63+
64+
assert(buffer[10] == 0);
65+
assert(buffer[11] == 1);
66+
assert(buffer[18] == 8);
67+
assert(buffer[19] == 9);
68+
69+
return 0;
70+
}
71+
72+
// CHECK-NOT: {{.*Real-time violation.*}}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// RUN: %clangxx -fsanitize=realtime %s -o %t
2+
// RUN: not %run %t 2>&1 | FileCheck %s
3+
// UNSUPPORTED: ios
4+
5+
// Intent: Ensure we detect infinite loops
6+
7+
#include <stdio.h>
8+
#include <stdlib.h>
9+
10+
void infinity() [[clang::nonblocking]] {
11+
while(true);
12+
}
13+
14+
int main() {
15+
infinity();
16+
return 0;
17+
// CHECK: {{.*Real-time violation.*}}
18+
// CHECK: {{.*infinity*}}
19+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// RUN: %clangxx -DCAS_SPINLOCK -fsanitize=realtime %s -o %t
2+
// RUN: not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-ALL --check-prefix=CHECK-CAS
3+
// RUN: %clangxx -DTEST_AND_SET_SPINLOCK -fsanitize=realtime %s -o %t
4+
// RUN: not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-ALL --check-prefix=CHECK-TEST-AND-SET
5+
// UNSUPPORTED: ios
6+
7+
#include <atomic>
8+
9+
#include <atomic>
10+
11+
class SpinLockTestAndSet {
12+
public:
13+
void lock() {
14+
while (lock_flag.test_and_set(std::memory_order_acquire)) {
15+
// Busy-wait (spin) until the lock is acquired
16+
}
17+
}
18+
19+
void unlock() {
20+
lock_flag.clear(std::memory_order_release);
21+
}
22+
23+
private:
24+
std::atomic_flag lock_flag = ATOMIC_FLAG_INIT;
25+
};
26+
27+
class SpinLockCompareExchange {
28+
public:
29+
void lock() {
30+
bool expected = false;
31+
while (!lock_flag.compare_exchange_weak(expected, true, std::memory_order_acquire, std::memory_order_relaxed)) {
32+
}
33+
}
34+
35+
void unlock() {
36+
lock_flag.store(false, std::memory_order_release);
37+
}
38+
39+
private:
40+
std::atomic<bool> lock_flag{false};
41+
};
42+
43+
int lock_violation() [[clang::nonblocking]]
44+
{
45+
#if defined(TEST_AND_SET_SPINLOCK)
46+
SpinLockTestAndSet lock;
47+
#elif defined(CAS_SPINLOCK)
48+
SpinLockCompareExchange lock;
49+
#else
50+
#error "No spinlock defined"
51+
#endif
52+
lock.lock();
53+
return 0;
54+
}
55+
56+
int main() [[clang::nonblocking]]
57+
{
58+
lock_violation();
59+
}
60+
61+
// CHECK-ALL: {{.*Real-time violation.*}}
62+
// CHECK-CAS: {{.*SpinLockCompareExchange::lock.*}}
63+
// CHECK-TEST-AND-SET: {{.*SpinLockTestAndSet::lock.*}}

llvm/lib/Transforms/Instrumentation/RealtimeSanitizer.cpp

Lines changed: 65 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,51 +13,101 @@
1313
//
1414
//===----------------------------------------------------------------------===//
1515

16-
#include "llvm/IR/Analysis.h"
16+
#include "llvm/Analysis/LoopInfo.h"
17+
#include "llvm/Analysis/ScalarEvolution.h"
18+
#include "llvm/Demangle/Demangle.h"
19+
#include "llvm/IR/Function.h"
1720
#include "llvm/IR/IRBuilder.h"
1821
#include "llvm/IR/Module.h"
22+
#include "llvm/Support/raw_ostream.h"
1923

2024
#include "llvm/Transforms/Instrumentation/RealtimeSanitizer.h"
2125

2226
using namespace llvm;
2327

24-
static void insertCallBeforeInstruction(Function &Fn, Instruction &Instruction,
25-
const char *FunctionName) {
26-
LLVMContext &Context = Fn.getContext();
27-
FunctionType *FuncType = FunctionType::get(Type::getVoidTy(Context), false);
28+
static void insertCallBeforeInstruction(Function &CallingFn,
29+
IRBuilder<> &Builder,
30+
const char *FunctionName,
31+
ArrayRef<Value *> FunctionArgs) {
32+
std::vector<Type *> FunctionArgTypes;
33+
FunctionArgTypes.reserve(FunctionArgs.size());
34+
for (Value *Arg : FunctionArgs)
35+
FunctionArgTypes.push_back(Arg->getType());
36+
37+
FunctionType *FuncType = FunctionType::get(
38+
Type::getVoidTy(CallingFn.getContext()), FunctionArgTypes, false);
2839
FunctionCallee Func =
29-
Fn.getParent()->getOrInsertFunction(FunctionName, FuncType);
30-
IRBuilder<> Builder{&Instruction};
31-
Builder.CreateCall(Func, {});
40+
CallingFn.getParent()->getOrInsertFunction(FunctionName, FuncType);
41+
Builder.CreateCall(Func, FunctionArgs);
3242
}
3343

3444
static void insertCallAtFunctionEntryPoint(Function &Fn,
3545
const char *InsertFnName) {
3646

37-
insertCallBeforeInstruction(Fn, Fn.front().front(), InsertFnName);
47+
IRBuilder<> Builder{&Fn.front().front()};
48+
insertCallBeforeInstruction(Fn, Builder, InsertFnName, std::nullopt);
3849
}
3950

4051
static void insertCallAtAllFunctionExitPoints(Function &Fn,
4152
const char *InsertFnName) {
4253
for (auto &BB : Fn)
4354
for (auto &I : BB)
44-
if (isa<ReturnInst>(&I))
45-
insertCallBeforeInstruction(Fn, I, InsertFnName);
55+
if (isa<ReturnInst>(&I)) {
56+
IRBuilder<> Builder{&I};
57+
insertCallBeforeInstruction(Fn, Builder, InsertFnName, std::nullopt);
58+
}
4659
}
4760

4861
RealtimeSanitizerPass::RealtimeSanitizerPass(
4962
const RealtimeSanitizerOptions &Options) {}
5063

5164
PreservedAnalyses RealtimeSanitizerPass::run(Function &F,
5265
AnalysisManager<Function> &AM) {
66+
PreservedAnalyses PA = PreservedAnalyses::all();
5367
if (F.hasFnAttribute(Attribute::SanitizeRealtime)) {
5468
insertCallAtFunctionEntryPoint(F, "__rtsan_realtime_enter");
5569
insertCallAtAllFunctionExitPoints(F, "__rtsan_realtime_exit");
56-
57-
PreservedAnalyses PA;
70+
PA = PreservedAnalyses::none();
5871
PA.preserveSet<CFGAnalyses>();
59-
return PA;
6072
}
6173

62-
return PreservedAnalyses::all();
74+
const LoopInfo &LI = AM.getResult<LoopAnalysis>(F);
75+
ScalarEvolution &SE = AM.getResult<ScalarEvolutionAnalysis>(F);
76+
for (Loop *L : LI) {
77+
78+
const bool HasNoExits = L->hasNoExitBlocks();
79+
const bool CannotPredictLoopCount =
80+
isa<SCEVCouldNotCompute>(SE.getConstantMaxBackedgeTakenCount(L)) &&
81+
isa<SCEVCouldNotCompute>(SE.getBackedgeTakenCount(L));
82+
const bool LoopIsPotentiallyUnbound = HasNoExits || CannotPredictLoopCount;
83+
84+
if (LoopIsPotentiallyUnbound) {
85+
BasicBlock *Context =
86+
L->getLoopPreheader() ? L->getLoopPreheader() : L->getHeader();
87+
assert(Context && "Loop has no preheader or header block");
88+
89+
IRBuilder<> Builder{&Context->back()};
90+
91+
std::string ReasonStr =
92+
demangle(F.getName().str()) + " contains a possibly unbounded loop ";
93+
94+
if (HasNoExits)
95+
ReasonStr += "(reason: no exit blocks).";
96+
else if (CannotPredictLoopCount)
97+
ReasonStr += "(reason: backedge taken count cannot be computed).";
98+
else
99+
assert(false);
100+
101+
Value *Reason = Builder.CreateGlobalStringPtr(ReasonStr);
102+
insertCallBeforeInstruction(F, Builder, "__rtsan_expect_not_realtime",
103+
{Reason});
104+
105+
// TODO: What is preserved here??
106+
PA = PreservedAnalyses::none();
107+
108+
// TODO: Tons of test cases
109+
}
110+
}
111+
112+
return PA;
63113
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
; RUN: opt < %s -passes='rtsan' -S | FileCheck %s
2+
3+
4+
define void @procces(ptr noundef %buffer, i32 noundef %size) #0 {
5+
entry:
6+
%buffer.addr = alloca ptr, align 8
7+
%size.addr = alloca i32, align 4
8+
%i = alloca i32, align 4
9+
store ptr %buffer, ptr %buffer.addr, align 8
10+
store i32 %size, ptr %size.addr, align 4
11+
store i32 0, ptr %i, align 4
12+
br label %for.cond
13+
14+
for.cond: ; preds = %for.inc, %entry
15+
%0 = load i32, ptr %i, align 4
16+
%1 = load i32, ptr %size.addr, align 4
17+
%cmp = icmp slt i32 %0, %1
18+
br i1 %cmp, label %for.body, label %for.end
19+
20+
for.body: ; preds = %for.cond
21+
%2 = load i32, ptr %i, align 4
22+
%conv = sitofp i32 %2 to float
23+
%3 = load ptr, ptr %buffer.addr, align 8
24+
%4 = load i32, ptr %i, align 4
25+
%idxprom = sext i32 %4 to i64
26+
%arrayidx = getelementptr inbounds float, ptr %3, i64 %idxprom
27+
store float %conv, ptr %arrayidx, align 4
28+
br label %for.inc
29+
30+
for.inc: ; preds = %for.body
31+
%5 = load i32, ptr %i, align 4
32+
%inc = add nsw i32 %5, 1
33+
store i32 %inc, ptr %i, align 4
34+
br label %for.cond
35+
36+
for.end: ; preds = %for.cond
37+
ret void
38+
}
39+
40+
attributes #0 = { sanitize_realtime }
41+
42+
; In this simple loop, we should not insert rtsan_expect_not_realtime
43+
; CHECK: call{{.*}}@__rtsan_realtime_enter
44+
45+
; TODO: This test fails when it shouldn't!!
46+
; XXXXX--CHECK-NOT: call{{.*}}@__rtsan_expect_not_realtime
47+
48+
; CHECK: call{{.*}}@__rtsan_realtime_exit
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
; RUN: opt < %s -passes=rtsan -S | FileCheck %s
2+
3+
%class.SpinLockTestAndSet = type { %"struct.std::__1::atomic_flag" }
4+
%"struct.std::__1::atomic_flag" = type { %"struct.std::__1::__cxx_atomic_impl" }
5+
%"struct.std::__1::__cxx_atomic_impl" = type { %"struct.std::__1::__cxx_atomic_base_impl" }
6+
%"struct.std::__1::__cxx_atomic_base_impl" = type { i8 }
7+
8+
define noundef i32 @main() local_unnamed_addr #0 {
9+
entry:
10+
%spinlock = alloca %class.SpinLockTestAndSet, align 1
11+
call void @llvm.lifetime.start.p0(i64 1, ptr nonnull %spinlock)
12+
store i8 0, ptr %spinlock, align 1
13+
br label %while.cond.i
14+
15+
while.cond.i: ; preds = %while.cond.i, %entry
16+
%0 = atomicrmw xchg ptr %spinlock, i8 1 acquire, align 1
17+
%extract.t2.i.i = trunc i8 %0 to i1
18+
br i1 %extract.t2.i.i, label %while.cond.i, label %SpinlockTestAndSet.exit
19+
20+
SpinlockTestAndSet.exit: ; preds = %while.cond.i
21+
store atomic i8 0, ptr %spinlock release, align 1
22+
call void @llvm.lifetime.end.p0(i64 1, ptr nonnull %spinlock)
23+
ret i32 0
24+
}
25+
26+
attributes #0 = { sanitize_realtime }
27+
28+
; CHECK: call{{.*}}@__rtsan_realtime_enter
29+
; CHECK: call{{.*}}@__rtsan_expect_not_realtime
30+
; CHECK: call{{.*}}@__rtsan_realtime_exit
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
; RUN: opt < %s -passes=rtsan -S | FileCheck %s
2+
3+
define void @process() #0 {
4+
entry:
5+
br label %while.body
6+
7+
while.body: ; preds = %entry, %while.body
8+
br label %while.body
9+
}
10+
11+
attributes #0 = { sanitize_realtime }
12+
13+
; CHECK: call{{.*}}@__rtsan_realtime_enter
14+
; CHECK: call{{.*}}@__rtsan_expect_not_realtime
15+
; CHECK-NEXT: br label %while.body

0 commit comments

Comments
 (0)