Skip to content
Open
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
5 changes: 4 additions & 1 deletion clang/lib/CodeGen/BackendUtil.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
#include "llvm/Transforms/Scalar/EarlyCSE.h"
#include "llvm/Transforms/Scalar/GVN.h"
#include "llvm/Transforms/Scalar/JumpThreading.h"
#include "llvm/Transforms/Scalar/SROA.h"
#include "llvm/Transforms/Utils/Debugify.h"
#include "llvm/Transforms/Utils/ModuleUtils.h"
#include <memory>
Expand Down Expand Up @@ -991,12 +992,14 @@ void EmitAssemblyHelper::RunOptimizationPipeline(
FPM.addPass(BoundsCheckingPass());
});

if (LangOpts.Sanitize.has(SanitizerKind::Realtime))
if (LangOpts.Sanitize.has(SanitizerKind::Realtime)) {
PB.registerScalarOptimizerLateEPCallback(
[](FunctionPassManager &FPM, OptimizationLevel Level) {
RealtimeSanitizerOptions Opts;
FPM.addPass(SROAPass(SROAOptions::PreserveCFG));
FPM.addPass(RealtimeSanitizerPass(Opts));
});
}
Copy link
Owner Author

Choose a reason for hiding this comment

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

Here is where we add the SROA pass


// Don't add sanitizers if we are here from ThinLTO PostLink. That already
// done on PreLink stage.
Expand Down
72 changes: 72 additions & 0 deletions compiler-rt/test/rtsan/bound_loop.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// RUN: %clangxx -fsanitize=realtime %s -o %t -O3
// RUN: %run %t 2>&1 | FileCheck %s --allow-empty
// RUN: %clangxx -fsanitize=realtime %s -o %t -O2
// RUN: %run %t 2>&1 | FileCheck %s --allow-empty
// RUN: %clangxx -fsanitize=realtime %s -o %t -O1
// RUN: %run %t 2>&1 | FileCheck %s --allow-empty

// RUN: %clangxx -fsanitize=realtime %s -o %t -O0
// RUN: %run %t 2>&1 | FileCheck %s --allow-empty

// UNSUPPORTED: ios

// Intent: Ensure basic bound audio loops don't trigger rtsan.

#include <stdio.h>
#include <stdlib.h>

#include <assert.h>

void FillBufferInterleaved(int *buffer, int sample_count, int channel_count)
[[clang::nonblocking]] {
for (int sample = 0; sample < sample_count; sample++)
for (int channel = 0; channel < channel_count; channel++)
buffer[sample * channel_count + channel] = sample;
}

void Deinterleave(int *buffer, int sample_count, int channel_count,
int *scratch_buffer) [[clang::nonblocking]] {
for (int channel = 0; channel < channel_count; channel++)
for (int sample = 0; sample < sample_count; sample++) {
int interleaved_index = sample * channel_count + channel;
int deinterleaved_index = channel * sample_count + sample;
scratch_buffer[deinterleaved_index] = buffer[interleaved_index];
}

for (int i = 0; i < sample_count * channel_count; i++)
buffer[i] = scratch_buffer[i];
}

int main() {
const int sample_count = 10;
const int channel_count = 2;
int buffer[channel_count * sample_count];

FillBufferInterleaved(buffer, sample_count, channel_count);

assert(buffer[0] == 0);
assert(buffer[1] == 0);
assert(buffer[2] == 1);
assert(buffer[3] == 1);

assert(buffer[18] == 9);
assert(buffer[19] == 9);

int scratch_buffer[channel_count * sample_count];

Deinterleave(buffer, sample_count, channel_count, scratch_buffer);

assert(buffer[0] == 0);
assert(buffer[1] == 1);
assert(buffer[8] == 8);
assert(buffer[9] == 9);

assert(buffer[10] == 0);
assert(buffer[11] == 1);
assert(buffer[18] == 8);
assert(buffer[19] == 9);

return 0;
}

// CHECK-NOT: {{.*Real-time violation.*}}
20 changes: 20 additions & 0 deletions compiler-rt/test/rtsan/infinite_loop.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// RUN: %clangxx -fsanitize=realtime %s -o %t
// RUN: not %run %t 2>&1 | FileCheck %s
// UNSUPPORTED: ios

// Intent: Ensure we detect infinite loops

#include <stdio.h>
#include <stdlib.h>

void infinity() [[clang::nonblocking]] {
while (true)
;
}

int main() {
infinity();
return 0;
// CHECK: {{.*Real-time violation.*}}
// CHECK: {{.*infinity*}}
}
56 changes: 56 additions & 0 deletions compiler-rt/test/rtsan/spinlock.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// RUN: %clangxx -DCAS_SPINLOCK -fsanitize=realtime %s -o %t
// RUN: not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-ALL --check-prefix=CHECK-CAS
// RUN: %clangxx -DTEST_AND_SET_SPINLOCK -fsanitize=realtime %s -o %t
// RUN: not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-ALL --check-prefix=CHECK-TEST-AND-SET
// UNSUPPORTED: ios

#include <atomic>

#include <atomic>

class SpinLockTestAndSet {
public:
void lock() {
while (lock_flag.test_and_set(std::memory_order_acquire)) {
// Busy-wait (spin) until the lock is acquired
}
}

void unlock() { lock_flag.clear(std::memory_order_release); }

private:
std::atomic_flag lock_flag = ATOMIC_FLAG_INIT;
};

class SpinLockCompareExchange {
public:
void lock() {
bool expected = false;
while (!lock_flag.compare_exchange_weak(
expected, true, std::memory_order_acquire, std::memory_order_relaxed)) {
}
}

void unlock() { lock_flag.store(false, std::memory_order_release); }

private:
std::atomic<bool> lock_flag{false};
};

int lock_violation() [[clang::nonblocking]] {
#if defined(TEST_AND_SET_SPINLOCK)
SpinLockTestAndSet lock;
#elif defined(CAS_SPINLOCK)
SpinLockCompareExchange lock;
#else
# error "No spinlock defined"
#endif
lock.lock();
return 0;
}

int main() [[clang::nonblocking]] { lock_violation(); }

// CHECK-ALL: {{.*Real-time violation.*}}
// CHECK-CAS: {{.*SpinLockCompareExchange::lock.*}}
// CHECK-TEST-AND-SET: {{.*SpinLockTestAndSet::lock.*}}
48 changes: 48 additions & 0 deletions compiler-rt/test/rtsan/unbound_loop.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// RUN: %clangxx -fsanitize=realtime %s -o %t -O3
// RUN: not %run %t 2>&1 | FileCheck %s --allow-empty
// RUN: %clangxx -fsanitize=realtime %s -o %t -O2
// RUN: not %run %t 2>&1 | FileCheck %s --allow-empty
// RUN: %clangxx -fsanitize=realtime %s -o %t -O1
// RUN: not %run %t 2>&1 | FileCheck %s --allow-empty

// RUN: %clangxx -fsanitize=realtime %s -o %t -O0
// RUN: not %run %t 2>&1 | FileCheck %s --allow-empty

// UNSUPPORTED: ios

// Intent: Ensure basic bound audio loops don't trigger rtsan.

#include <stdio.h>
#include <stdlib.h>

#include <assert.h>

void BadScalarEvolution(int *buffer, int sample_count, int channel_count)
[[clang::nonblocking]] {

int sample = 0;
while (sample < sample_count) {
int channel = 0;
while (channel < channel_count) {
buffer[sample * channel_count + channel] = sample;
channel++;
}
sample++;

// NOTE! Here is the "bug" that causes the loop to be unbounded.
sample_count++;
}
}

int main() {
const int sample_count = 10;
const int channel_count = 2;
int buffer[channel_count * sample_count];

BadScalarEvolution(buffer, sample_count, channel_count);

return 0;
}

// CHECK: {{.*Real-time violation.*}}
// CHECK-NEXT {{.*BadScalarEvolution.*}}
77 changes: 62 additions & 15 deletions llvm/lib/Transforms/Instrumentation/RealtimeSanitizer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,51 +13,98 @@
//
//===----------------------------------------------------------------------===//

#include "llvm/IR/Analysis.h"
#include "llvm/Analysis/LoopInfo.h"
#include "llvm/Analysis/ScalarEvolution.h"
#include "llvm/Demangle/Demangle.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/Module.h"
#include "llvm/Support/raw_ostream.h"

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

using namespace llvm;

static void insertCallBeforeInstruction(Function &Fn, Instruction &Instruction,
const char *FunctionName) {
LLVMContext &Context = Fn.getContext();
FunctionType *FuncType = FunctionType::get(Type::getVoidTy(Context), false);
static void insertCallBeforeInstruction(Function &CallingFn,
IRBuilder<> &Builder,
const char *FunctionName,
ArrayRef<Value *> FunctionArgs) {
std::vector<Type *> FunctionArgTypes;
FunctionArgTypes.reserve(FunctionArgs.size());
for (Value *Arg : FunctionArgs)
FunctionArgTypes.push_back(Arg->getType());

FunctionType *FuncType = FunctionType::get(
Type::getVoidTy(CallingFn.getContext()), FunctionArgTypes, false);
FunctionCallee Func =
Fn.getParent()->getOrInsertFunction(FunctionName, FuncType);
IRBuilder<> Builder{&Instruction};
Builder.CreateCall(Func, {});
CallingFn.getParent()->getOrInsertFunction(FunctionName, FuncType);
Builder.CreateCall(Func, FunctionArgs);
}

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

insertCallBeforeInstruction(Fn, Fn.front().front(), InsertFnName);
IRBuilder<> Builder{&Fn.front().front()};
insertCallBeforeInstruction(Fn, Builder, InsertFnName, std::nullopt);
}

static void insertCallAtAllFunctionExitPoints(Function &Fn,
const char *InsertFnName) {
for (auto &BB : Fn)
for (auto &I : BB)
if (isa<ReturnInst>(&I))
insertCallBeforeInstruction(Fn, I, InsertFnName);
if (isa<ReturnInst>(&I)) {
IRBuilder<> Builder{&I};
insertCallBeforeInstruction(Fn, Builder, InsertFnName, std::nullopt);
}
}

RealtimeSanitizerPass::RealtimeSanitizerPass(
const RealtimeSanitizerOptions &Options) {}

PreservedAnalyses RealtimeSanitizerPass::run(Function &F,
AnalysisManager<Function> &AM) {
PreservedAnalyses PA = PreservedAnalyses::all();
if (F.hasFnAttribute(Attribute::SanitizeRealtime)) {
insertCallAtFunctionEntryPoint(F, "__rtsan_realtime_enter");
insertCallAtAllFunctionExitPoints(F, "__rtsan_realtime_exit");

PreservedAnalyses PA;
PA = PreservedAnalyses::none();
PA.preserveSet<CFGAnalyses>();
return PA;
}

return PreservedAnalyses::all();
const LoopInfo &LI = AM.getResult<LoopAnalysis>(F);
ScalarEvolution &SE = AM.getResult<ScalarEvolutionAnalysis>(F);
for (Loop *L : LI) {
BasicBlock *Context =
L->getLoopPreheader() ? L->getLoopPreheader() : L->getHeader();
assert(Context && "Loop has no preheader or header block");

const bool HasNoExits = L->hasNoExitBlocks();
const bool CannotPredictLoopCount =
isa<SCEVCouldNotCompute>(SE.getConstantMaxBackedgeTakenCount(L)) ||
isa<SCEVCouldNotCompute>(SE.getBackedgeTakenCount(L));
const bool LoopIsPotentiallyUnbound = HasNoExits || CannotPredictLoopCount;

if (LoopIsPotentiallyUnbound) {
IRBuilder<> Builder{&Context->back()};

std::string ReasonStr =
demangle(F.getName().str()) + " contains a possibly unbounded loop ";

if (HasNoExits)
ReasonStr += "(reason: no exit blocks).";
else if (CannotPredictLoopCount)
ReasonStr += "(reason: backedge taken count cannot be computed).";
else
assert(false);

Value *Reason = Builder.CreateGlobalStringPtr(ReasonStr);
insertCallBeforeInstruction(F, Builder, "__rtsan_expect_not_realtime",
{Reason});

// TODO: What is preserved here??
PA = PreservedAnalyses::none();
}
}

return PA;
}
43 changes: 43 additions & 0 deletions llvm/test/Instrumentation/RealtimeSanitizer/rtsan_bound_loop.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
; RUN: opt < %s -passes='sroa,rtsan' -S | FileCheck %s


define void @procces(ptr noundef %buffer, i32 noundef %size) #0 {
entry:
%buffer.addr = alloca ptr, align 8
%size.addr = alloca i32, align 4
%i = alloca i32, align 4
store ptr %buffer, ptr %buffer.addr, align 8
store i32 %size, ptr %size.addr, align 4
store i32 0, ptr %i, align 4
br label %for.cond

for.cond: ; preds = %for.inc, %entry
%0 = load i32, ptr %i, align 4
%1 = load i32, ptr %size.addr, align 4
%cmp = icmp slt i32 %0, %1
br i1 %cmp, label %for.body, label %for.end

for.body: ; preds = %for.cond
%2 = load i32, ptr %i, align 4
%conv = sitofp i32 %2 to float
%3 = load ptr, ptr %buffer.addr, align 8
%4 = load i32, ptr %i, align 4
%idxprom = sext i32 %4 to i64
%arrayidx = getelementptr inbounds float, ptr %3, i64 %idxprom
store float %conv, ptr %arrayidx, align 4
br label %for.inc

for.inc: ; preds = %for.body
%5 = load i32, ptr %i, align 4
%inc = add nsw i32 %5, 1
store i32 %inc, ptr %i, align 4
br label %for.cond

for.end: ; preds = %for.cond
ret void
}

attributes #0 = { sanitize_realtime }

; In this simple loop, we should not insert rtsan_expect_not_realtime
; CHECK-NOT: call{{.*}}@__rtsan_expect_not_realtime
Loading