Skip to content

Loop exploration #6

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
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
7 changes: 6 additions & 1 deletion clang/lib/CodeGen/BackendUtil.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -991,12 +991,17 @@ void EmitAssemblyHelper::RunOptimizationPipeline(
FPM.addPass(BoundsCheckingPass());
});

if (LangOpts.Sanitize.has(SanitizerKind::Realtime))
if (LangOpts.Sanitize.has(SanitizerKind::Realtime)) {
PB.registerLoopOptimizerEndEPCallback(
[](LoopPassManager &LPM, OptimizationLevel Level) {
LPM.addPass(RealtimeSanitizerLoopPass());
});
PB.registerScalarOptimizerLateEPCallback(
[](FunctionPassManager &FPM, OptimizationLevel Level) {
RealtimeSanitizerOptions Opts;
FPM.addPass(RealtimeSanitizerPass(Opts));
});
}

// 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.*}}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#define LLVM_TRANSFORMS_INSTRUMENTATION_REALTIMESANITIZER_H

#include "llvm/IR/PassManager.h"
#include "llvm/Transforms/Scalar/LoopPassManager.h"

namespace llvm {

Expand All @@ -33,6 +34,12 @@ class RealtimeSanitizerPass : public PassInfoMixin<RealtimeSanitizerPass> {
static bool isRequired() { return true; }
};

struct RealtimeSanitizerLoopPass : PassInfoMixin<RealtimeSanitizerLoopPass> {
PreservedAnalyses run(Loop &L, LoopAnalysisManager &AM,
LoopStandardAnalysisResults &AR, LPMUpdater &U);
static bool isRequired() { return true; }
};

} // namespace llvm

#endif // LLVM_TRANSFORMS_INSTRUMENTATION_REALTIMESANITIZER_H
1 change: 1 addition & 0 deletions llvm/lib/Passes/PassRegistry.def
Original file line number Diff line number Diff line change
Expand Up @@ -659,6 +659,7 @@ LOOP_PASS("print<ddg>", DDGAnalysisPrinterPass(dbgs()))
LOOP_PASS("print<iv-users>", IVUsersPrinterPass(dbgs()))
LOOP_PASS("print<loop-cache-cost>", LoopCachePrinterPass(dbgs()))
LOOP_PASS("print<loopnest>", LoopNestPrinterPass(dbgs()))
LOOP_PASS("sanitize-unbound-loops", RealtimeSanitizerLoopPass())
#undef LOOP_PASS

#ifndef LOOP_PASS_WITH_PARAMS
Expand Down
75 changes: 64 additions & 11 deletions llvm/lib/Transforms/Instrumentation/RealtimeSanitizer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,36 +13,49 @@
//
//===----------------------------------------------------------------------===//

#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(
Expand All @@ -61,3 +74,43 @@ PreservedAnalyses RealtimeSanitizerPass::run(Function &F,

return PreservedAnalyses::all();
}

PreservedAnalyses
RealtimeSanitizerLoopPass::run(Loop &L, LoopAnalysisManager &AM,
LoopStandardAnalysisResults &AR, LPMUpdater &U) {
BasicBlock *Context =
L.getLoopPreheader() ? L.getLoopPreheader() : L.getHeader();
assert(Context && "Loop has no preheader or header block");

Function *F = Context->getParent();
assert(F && "Loop has no parent function");

const bool HasNoExits = L.hasNoExitBlocks();
const bool CannotPredictLoopCount =
isa<SCEVCouldNotCompute>(AR.SE.getConstantMaxBackedgeTakenCount(&L)) ||
isa<SCEVCouldNotCompute>(AR.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??
return PreservedAnalyses::none();
}

return PreservedAnalyses::all();
}
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,sanitize-unbound-loops' -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