From 15821d04bde0fd749fdc690366c8e4599ea26e0d Mon Sep 17 00:00:00 2001 From: Chris Apple Date: Thu, 29 Aug 2024 07:55:15 -0700 Subject: [PATCH 1/5] First version --- compiler-rt/test/rtsan/bound_loop.cpp | 72 +++++++++++++++++ compiler-rt/test/rtsan/infinite_loop.cpp | 19 +++++ compiler-rt/test/rtsan/spinlock.cpp | 63 +++++++++++++++ .../Instrumentation/RealtimeSanitizer.cpp | 80 +++++++++++++++---- .../RealtimeSanitizer/rtsan_bound_loop.ll | 48 +++++++++++ .../RealtimeSanitizer/rtsan_cas_spinlock.ll | 30 +++++++ .../RealtimeSanitizer/rtsan_infinite_loop.ll | 15 ++++ 7 files changed, 312 insertions(+), 15 deletions(-) create mode 100644 compiler-rt/test/rtsan/bound_loop.cpp create mode 100644 compiler-rt/test/rtsan/infinite_loop.cpp create mode 100644 compiler-rt/test/rtsan/spinlock.cpp create mode 100644 llvm/test/Instrumentation/RealtimeSanitizer/rtsan_bound_loop.ll create mode 100644 llvm/test/Instrumentation/RealtimeSanitizer/rtsan_cas_spinlock.ll create mode 100644 llvm/test/Instrumentation/RealtimeSanitizer/rtsan_infinite_loop.ll diff --git a/compiler-rt/test/rtsan/bound_loop.cpp b/compiler-rt/test/rtsan/bound_loop.cpp new file mode 100644 index 0000000000000..4f9dc027c9ff8 --- /dev/null +++ b/compiler-rt/test/rtsan/bound_loop.cpp @@ -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 + +// TODO: this test is failing with O0, investigate why +// xxx: %clangxx -fsanitize=realtime %s -o %t -O0 +// xxx: %run %t 2>&1 | FileCheck %s + +// UNSUPPORTED: ios + +// Intent: Ensure basic bound audio loops don't trigger rtsan. + +#include +#include + +#include + + +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.*}} diff --git a/compiler-rt/test/rtsan/infinite_loop.cpp b/compiler-rt/test/rtsan/infinite_loop.cpp new file mode 100644 index 0000000000000..f45bff5862063 --- /dev/null +++ b/compiler-rt/test/rtsan/infinite_loop.cpp @@ -0,0 +1,19 @@ +// RUN: %clangxx -fsanitize=realtime %s -o %t +// RUN: not %run %t 2>&1 | FileCheck %s +// UNSUPPORTED: ios + +// Intent: Ensure we detect infinite loops + +#include +#include + +void infinity() [[clang::nonblocking]] { + while(true); +} + +int main() { + infinity(); + return 0; + // CHECK: {{.*Real-time violation.*}} + // CHECK: {{.*infinity*}} +} diff --git a/compiler-rt/test/rtsan/spinlock.cpp b/compiler-rt/test/rtsan/spinlock.cpp new file mode 100644 index 0000000000000..1fd4ba7aa2bef --- /dev/null +++ b/compiler-rt/test/rtsan/spinlock.cpp @@ -0,0 +1,63 @@ +// 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 + +#include + +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 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.*}} diff --git a/llvm/lib/Transforms/Instrumentation/RealtimeSanitizer.cpp b/llvm/lib/Transforms/Instrumentation/RealtimeSanitizer.cpp index 7854cf4f2c625..060a75f17b525 100644 --- a/llvm/lib/Transforms/Instrumentation/RealtimeSanitizer.cpp +++ b/llvm/lib/Transforms/Instrumentation/RealtimeSanitizer.cpp @@ -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 FunctionArgs) { + std::vector 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(&I)) - insertCallBeforeInstruction(Fn, I, InsertFnName); + if (isa(&I)) { + IRBuilder<> Builder{&I}; + insertCallBeforeInstruction(Fn, Builder, InsertFnName, std::nullopt); + } } RealtimeSanitizerPass::RealtimeSanitizerPass( @@ -50,14 +63,51 @@ RealtimeSanitizerPass::RealtimeSanitizerPass( PreservedAnalyses RealtimeSanitizerPass::run(Function &F, AnalysisManager &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(); - return PA; } - return PreservedAnalyses::all(); + const LoopInfo &LI = AM.getResult(F); + ScalarEvolution &SE = AM.getResult(F); + for (Loop *L : LI) { + + const bool HasNoExits = L->hasNoExitBlocks(); + const bool CannotPredictLoopCount = + isa(SE.getConstantMaxBackedgeTakenCount(L)) && + isa(SE.getBackedgeTakenCount(L)); + const bool LoopIsPotentiallyUnbound = HasNoExits || CannotPredictLoopCount; + + if (LoopIsPotentiallyUnbound) { + BasicBlock *Context = + L->getLoopPreheader() ? L->getLoopPreheader() : L->getHeader(); + assert(Context && "Loop has no preheader or header block"); + + 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(); + + // TODO: Tons of test cases + } + } + + return PA; } diff --git a/llvm/test/Instrumentation/RealtimeSanitizer/rtsan_bound_loop.ll b/llvm/test/Instrumentation/RealtimeSanitizer/rtsan_bound_loop.ll new file mode 100644 index 0000000000000..a6cdb3116d325 --- /dev/null +++ b/llvm/test/Instrumentation/RealtimeSanitizer/rtsan_bound_loop.ll @@ -0,0 +1,48 @@ +; RUN: opt < %s -passes='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: call{{.*}}@__rtsan_realtime_enter + +; TODO: This test fails when it shouldn't!! +; XXXXX--CHECK-NOT: call{{.*}}@__rtsan_expect_not_realtime + +; CHECK: call{{.*}}@__rtsan_realtime_exit diff --git a/llvm/test/Instrumentation/RealtimeSanitizer/rtsan_cas_spinlock.ll b/llvm/test/Instrumentation/RealtimeSanitizer/rtsan_cas_spinlock.ll new file mode 100644 index 0000000000000..b257a908a26a5 --- /dev/null +++ b/llvm/test/Instrumentation/RealtimeSanitizer/rtsan_cas_spinlock.ll @@ -0,0 +1,30 @@ +; RUN: opt < %s -passes=rtsan -S | FileCheck %s + +%class.SpinLockTestAndSet = type { %"struct.std::__1::atomic_flag" } +%"struct.std::__1::atomic_flag" = type { %"struct.std::__1::__cxx_atomic_impl" } +%"struct.std::__1::__cxx_atomic_impl" = type { %"struct.std::__1::__cxx_atomic_base_impl" } +%"struct.std::__1::__cxx_atomic_base_impl" = type { i8 } + +define noundef i32 @main() local_unnamed_addr #0 { +entry: + %spinlock = alloca %class.SpinLockTestAndSet, align 1 + call void @llvm.lifetime.start.p0(i64 1, ptr nonnull %spinlock) + store i8 0, ptr %spinlock, align 1 + br label %while.cond.i + +while.cond.i: ; preds = %while.cond.i, %entry + %0 = atomicrmw xchg ptr %spinlock, i8 1 acquire, align 1 + %extract.t2.i.i = trunc i8 %0 to i1 + br i1 %extract.t2.i.i, label %while.cond.i, label %SpinlockTestAndSet.exit + +SpinlockTestAndSet.exit: ; preds = %while.cond.i + store atomic i8 0, ptr %spinlock release, align 1 + call void @llvm.lifetime.end.p0(i64 1, ptr nonnull %spinlock) + ret i32 0 +} + +attributes #0 = { sanitize_realtime } + +; CHECK: call{{.*}}@__rtsan_realtime_enter +; CHECK: call{{.*}}@__rtsan_expect_not_realtime +; CHECK: call{{.*}}@__rtsan_realtime_exit diff --git a/llvm/test/Instrumentation/RealtimeSanitizer/rtsan_infinite_loop.ll b/llvm/test/Instrumentation/RealtimeSanitizer/rtsan_infinite_loop.ll new file mode 100644 index 0000000000000..35bf8214a965d --- /dev/null +++ b/llvm/test/Instrumentation/RealtimeSanitizer/rtsan_infinite_loop.ll @@ -0,0 +1,15 @@ +; RUN: opt < %s -passes=rtsan -S | FileCheck %s + +define void @process() #0 { +entry: + br label %while.body + +while.body: ; preds = %entry, %while.body + br label %while.body +} + +attributes #0 = { sanitize_realtime } + +; CHECK: call{{.*}}@__rtsan_realtime_enter +; CHECK: call{{.*}}@__rtsan_expect_not_realtime +; CHECK-NEXT: br label %while.body From 0a02dcd769e98a60da2a53b9d529f97cc694e111 Mon Sep 17 00:00:00 2001 From: Chris Apple Date: Wed, 4 Sep 2024 08:42:12 -0700 Subject: [PATCH 2/5] WIP: Loop pass --- clang/lib/CodeGen/BackendUtil.cpp | 8 ++- .../Instrumentation/RealtimeSanitizer.h | 9 +++ llvm/lib/Passes/PassRegistry.def | 1 + .../Instrumentation/RealtimeSanitizer.cpp | 58 +++++++++---------- .../RealtimeSanitizer/rtsan_bound_loop.ll | 9 +-- .../RealtimeSanitizer/rtsan_cas_spinlock.ll | 4 +- .../RealtimeSanitizer/rtsan_infinite_loop.ll | 3 +- 7 files changed, 50 insertions(+), 42 deletions(-) diff --git a/clang/lib/CodeGen/BackendUtil.cpp b/clang/lib/CodeGen/BackendUtil.cpp index 7fa6942029816..669c877a65914 100644 --- a/clang/lib/CodeGen/BackendUtil.cpp +++ b/clang/lib/CodeGen/BackendUtil.cpp @@ -991,12 +991,18 @@ void EmitAssemblyHelper::RunOptimizationPipeline( FPM.addPass(BoundsCheckingPass()); }); - if (LangOpts.Sanitize.has(SanitizerKind::Realtime)) + if (LangOpts.Sanitize.has(SanitizerKind::Realtime)) { + fprintf(stderr, "Adding RealtimeSanitizerPass\n"); + 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. diff --git a/llvm/include/llvm/Transforms/Instrumentation/RealtimeSanitizer.h b/llvm/include/llvm/Transforms/Instrumentation/RealtimeSanitizer.h index f2ce1636551ce..9238a6e1b6b59 100644 --- a/llvm/include/llvm/Transforms/Instrumentation/RealtimeSanitizer.h +++ b/llvm/include/llvm/Transforms/Instrumentation/RealtimeSanitizer.h @@ -20,6 +20,8 @@ #define LLVM_TRANSFORMS_INSTRUMENTATION_REALTIMESANITIZER_H #include "llvm/IR/PassManager.h" +#include "llvm/Transforms/Scalar/LoopPassManager.h" + namespace llvm { @@ -33,6 +35,13 @@ class RealtimeSanitizerPass : public PassInfoMixin { static bool isRequired() { return true; } }; +struct RealtimeSanitizerLoopPass : PassInfoMixin { + PreservedAnalyses run(Loop &L, LoopAnalysisManager &AM, + LoopStandardAnalysisResults &AR, LPMUpdater &U); + static bool isRequired() { return true; } +}; + + } // namespace llvm #endif // LLVM_TRANSFORMS_INSTRUMENTATION_REALTIMESANITIZER_H diff --git a/llvm/lib/Passes/PassRegistry.def b/llvm/lib/Passes/PassRegistry.def index 4f5f680a6e953..80427697d0158 100644 --- a/llvm/lib/Passes/PassRegistry.def +++ b/llvm/lib/Passes/PassRegistry.def @@ -659,6 +659,7 @@ LOOP_PASS("print", DDGAnalysisPrinterPass(dbgs())) LOOP_PASS("print", IVUsersPrinterPass(dbgs())) LOOP_PASS("print", LoopCachePrinterPass(dbgs())) LOOP_PASS("print", LoopNestPrinterPass(dbgs())) +LOOP_PASS("sanitize-unbound-loops", RealtimeSanitizerLoopPass()) #undef LOOP_PASS #ifndef LOOP_PASS_WITH_PARAMS diff --git a/llvm/lib/Transforms/Instrumentation/RealtimeSanitizer.cpp b/llvm/lib/Transforms/Instrumentation/RealtimeSanitizer.cpp index 060a75f17b525..8a78c61660a44 100644 --- a/llvm/lib/Transforms/Instrumentation/RealtimeSanitizer.cpp +++ b/llvm/lib/Transforms/Instrumentation/RealtimeSanitizer.cpp @@ -71,43 +71,43 @@ PreservedAnalyses RealtimeSanitizerPass::run(Function &F, PA.preserveSet(); } - const LoopInfo &LI = AM.getResult(F); - ScalarEvolution &SE = AM.getResult(F); - for (Loop *L : LI) { + return PA; +} - const bool HasNoExits = L->hasNoExitBlocks(); - const bool CannotPredictLoopCount = - isa(SE.getConstantMaxBackedgeTakenCount(L)) && - isa(SE.getBackedgeTakenCount(L)); - const bool LoopIsPotentiallyUnbound = HasNoExits || CannotPredictLoopCount; +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"); - if (LoopIsPotentiallyUnbound) { - 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"); - IRBuilder<> Builder{&Context->back()}; + const bool HasNoExits = L.hasNoExitBlocks(); + const bool CannotPredictLoopCount = isa(AR.SE.getConstantMaxBackedgeTakenCount(&L)) && + isa(AR.SE.getBackedgeTakenCount(&L)); + const bool LoopIsPotentiallyUnbound = HasNoExits || CannotPredictLoopCount; - std::string ReasonStr = - demangle(F.getName().str()) + " contains a possibly unbounded loop "; + if (LoopIsPotentiallyUnbound) { + IRBuilder<> Builder{&Context->back()}; - if (HasNoExits) - ReasonStr += "(reason: no exit blocks)."; - else if (CannotPredictLoopCount) - ReasonStr += "(reason: backedge taken count cannot be computed)."; - else - assert(false); + std::string ReasonStr = + demangle(F->getName().str()) + " contains a possibly unbounded loop "; - Value *Reason = Builder.CreateGlobalStringPtr(ReasonStr); - insertCallBeforeInstruction(F, Builder, "__rtsan_expect_not_realtime", - {Reason}); + if (HasNoExits) + ReasonStr += "(reason: no exit blocks)."; + else if (CannotPredictLoopCount) + ReasonStr += "(reason: backedge taken count cannot be computed)."; + else + assert(false); - // TODO: What is preserved here?? - PA = PreservedAnalyses::none(); + Value *Reason = Builder.CreateGlobalStringPtr(ReasonStr); + insertCallBeforeInstruction(*F, Builder, "__rtsan_expect_not_realtime", + {Reason}); - // TODO: Tons of test cases + // TODO: What is preserved here?? + return PreservedAnalyses::none(); } - } - return PA; + return PreservedAnalyses::all(); } + diff --git a/llvm/test/Instrumentation/RealtimeSanitizer/rtsan_bound_loop.ll b/llvm/test/Instrumentation/RealtimeSanitizer/rtsan_bound_loop.ll index a6cdb3116d325..ac2c8e3ffeed6 100644 --- a/llvm/test/Instrumentation/RealtimeSanitizer/rtsan_bound_loop.ll +++ b/llvm/test/Instrumentation/RealtimeSanitizer/rtsan_bound_loop.ll @@ -1,4 +1,4 @@ -; RUN: opt < %s -passes='rtsan' -S | FileCheck %s +; RUN: opt < %s -passes='sroa,sanitize-unbound-loops' -S | FileCheck %s define void @procces(ptr noundef %buffer, i32 noundef %size) #0 { @@ -40,9 +40,4 @@ for.end: ; preds = %for.cond attributes #0 = { sanitize_realtime } ; In this simple loop, we should not insert rtsan_expect_not_realtime -; CHECK: call{{.*}}@__rtsan_realtime_enter - -; TODO: This test fails when it shouldn't!! -; XXXXX--CHECK-NOT: call{{.*}}@__rtsan_expect_not_realtime - -; CHECK: call{{.*}}@__rtsan_realtime_exit +; CHECK-NOT: call{{.*}}@__rtsan_expect_not_realtime diff --git a/llvm/test/Instrumentation/RealtimeSanitizer/rtsan_cas_spinlock.ll b/llvm/test/Instrumentation/RealtimeSanitizer/rtsan_cas_spinlock.ll index b257a908a26a5..76058b7a57f73 100644 --- a/llvm/test/Instrumentation/RealtimeSanitizer/rtsan_cas_spinlock.ll +++ b/llvm/test/Instrumentation/RealtimeSanitizer/rtsan_cas_spinlock.ll @@ -1,4 +1,4 @@ -; RUN: opt < %s -passes=rtsan -S | FileCheck %s +; RUN: opt < %s -passes='sanitize-unbound-loops' -S | FileCheck %s %class.SpinLockTestAndSet = type { %"struct.std::__1::atomic_flag" } %"struct.std::__1::atomic_flag" = type { %"struct.std::__1::__cxx_atomic_impl" } @@ -25,6 +25,4 @@ SpinlockTestAndSet.exit: ; preds = %while.cond.i attributes #0 = { sanitize_realtime } -; CHECK: call{{.*}}@__rtsan_realtime_enter ; CHECK: call{{.*}}@__rtsan_expect_not_realtime -; CHECK: call{{.*}}@__rtsan_realtime_exit diff --git a/llvm/test/Instrumentation/RealtimeSanitizer/rtsan_infinite_loop.ll b/llvm/test/Instrumentation/RealtimeSanitizer/rtsan_infinite_loop.ll index 35bf8214a965d..e542e9f1c0d5f 100644 --- a/llvm/test/Instrumentation/RealtimeSanitizer/rtsan_infinite_loop.ll +++ b/llvm/test/Instrumentation/RealtimeSanitizer/rtsan_infinite_loop.ll @@ -1,4 +1,4 @@ -; RUN: opt < %s -passes=rtsan -S | FileCheck %s +; RUN: opt < %s -passes='sanitize-unbound-loops' -S | FileCheck %s define void @process() #0 { entry: @@ -10,6 +10,5 @@ while.body: ; preds = %entry, %while.body attributes #0 = { sanitize_realtime } -; CHECK: call{{.*}}@__rtsan_realtime_enter ; CHECK: call{{.*}}@__rtsan_expect_not_realtime ; CHECK-NEXT: br label %while.body From 5f4d0b04ef10d6012db273622b330c25cca46b69 Mon Sep 17 00:00:00 2001 From: Chris Apple Date: Wed, 4 Sep 2024 13:29:34 -0700 Subject: [PATCH 3/5] Cleanup --- clang/lib/CodeGen/BackendUtil.cpp | 1 - compiler-rt/test/rtsan/bound_loop.cpp | 5 ++--- .../llvm/Transforms/Instrumentation/RealtimeSanitizer.h | 2 -- llvm/lib/Transforms/Instrumentation/RealtimeSanitizer.cpp | 7 ++++--- 4 files changed, 6 insertions(+), 9 deletions(-) diff --git a/clang/lib/CodeGen/BackendUtil.cpp b/clang/lib/CodeGen/BackendUtil.cpp index 669c877a65914..68bf2044d21d2 100644 --- a/clang/lib/CodeGen/BackendUtil.cpp +++ b/clang/lib/CodeGen/BackendUtil.cpp @@ -992,7 +992,6 @@ void EmitAssemblyHelper::RunOptimizationPipeline( }); if (LangOpts.Sanitize.has(SanitizerKind::Realtime)) { - fprintf(stderr, "Adding RealtimeSanitizerPass\n"); PB.registerLoopOptimizerEndEPCallback( [](LoopPassManager &LPM, OptimizationLevel Level) { LPM.addPass(RealtimeSanitizerLoopPass()); diff --git a/compiler-rt/test/rtsan/bound_loop.cpp b/compiler-rt/test/rtsan/bound_loop.cpp index 4f9dc027c9ff8..4db6b9488a10c 100644 --- a/compiler-rt/test/rtsan/bound_loop.cpp +++ b/compiler-rt/test/rtsan/bound_loop.cpp @@ -5,9 +5,8 @@ // RUN: %clangxx -fsanitize=realtime %s -o %t -O1 // RUN: %run %t 2>&1 | FileCheck %s --allow-empty -// TODO: this test is failing with O0, investigate why -// xxx: %clangxx -fsanitize=realtime %s -o %t -O0 -// xxx: %run %t 2>&1 | FileCheck %s +// RUN: %clangxx -fsanitize=realtime %s -o %t -O0 +// RUN: %run %t 2>&1 | FileCheck %s --allow-empty // UNSUPPORTED: ios diff --git a/llvm/include/llvm/Transforms/Instrumentation/RealtimeSanitizer.h b/llvm/include/llvm/Transforms/Instrumentation/RealtimeSanitizer.h index 9238a6e1b6b59..04fdc40edbd28 100644 --- a/llvm/include/llvm/Transforms/Instrumentation/RealtimeSanitizer.h +++ b/llvm/include/llvm/Transforms/Instrumentation/RealtimeSanitizer.h @@ -22,7 +22,6 @@ #include "llvm/IR/PassManager.h" #include "llvm/Transforms/Scalar/LoopPassManager.h" - namespace llvm { struct RealtimeSanitizerOptions {}; @@ -41,7 +40,6 @@ struct RealtimeSanitizerLoopPass : PassInfoMixin { static bool isRequired() { return true; } }; - } // namespace llvm #endif // LLVM_TRANSFORMS_INSTRUMENTATION_REALTIMESANITIZER_H diff --git a/llvm/lib/Transforms/Instrumentation/RealtimeSanitizer.cpp b/llvm/lib/Transforms/Instrumentation/RealtimeSanitizer.cpp index 8a78c61660a44..536be7baa8be1 100644 --- a/llvm/lib/Transforms/Instrumentation/RealtimeSanitizer.cpp +++ b/llvm/lib/Transforms/Instrumentation/RealtimeSanitizer.cpp @@ -63,15 +63,16 @@ RealtimeSanitizerPass::RealtimeSanitizerPass( PreservedAnalyses RealtimeSanitizerPass::run(Function &F, AnalysisManager &AM) { - PreservedAnalyses PA = PreservedAnalyses::all(); if (F.hasFnAttribute(Attribute::SanitizeRealtime)) { insertCallAtFunctionEntryPoint(F, "__rtsan_realtime_enter"); insertCallAtAllFunctionExitPoints(F, "__rtsan_realtime_exit"); - PA = PreservedAnalyses::none(); + + PreservedAnalyses PA; PA.preserveSet(); + return PA; } - return PA; + return PreservedAnalyses::all(); } PreservedAnalyses RealtimeSanitizerLoopPass::run(Loop &L, LoopAnalysisManager &AM, LoopStandardAnalysisResults &AR, LPMUpdater &U) { From b86f26afa572ac2940723efebf806e20f7976049 Mon Sep 17 00:00:00 2001 From: Chris Apple Date: Sun, 8 Sep 2024 08:43:01 -0700 Subject: [PATCH 4/5] Fix incorrect conditional predicting loop count --- compiler-rt/test/rtsan/unbound_loop.cpp | 48 +++++++++++++++++++ .../Instrumentation/RealtimeSanitizer.cpp | 2 +- 2 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 compiler-rt/test/rtsan/unbound_loop.cpp diff --git a/compiler-rt/test/rtsan/unbound_loop.cpp b/compiler-rt/test/rtsan/unbound_loop.cpp new file mode 100644 index 0000000000000..2a23018692292 --- /dev/null +++ b/compiler-rt/test/rtsan/unbound_loop.cpp @@ -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 +#include + +#include + + +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.*}} diff --git a/llvm/lib/Transforms/Instrumentation/RealtimeSanitizer.cpp b/llvm/lib/Transforms/Instrumentation/RealtimeSanitizer.cpp index 536be7baa8be1..46612a72a9baa 100644 --- a/llvm/lib/Transforms/Instrumentation/RealtimeSanitizer.cpp +++ b/llvm/lib/Transforms/Instrumentation/RealtimeSanitizer.cpp @@ -84,7 +84,7 @@ PreservedAnalyses RealtimeSanitizerLoopPass::run(Loop &L, LoopAnalysisManager &A assert(F && "Loop has no parent function"); const bool HasNoExits = L.hasNoExitBlocks(); - const bool CannotPredictLoopCount = isa(AR.SE.getConstantMaxBackedgeTakenCount(&L)) && + const bool CannotPredictLoopCount = isa(AR.SE.getConstantMaxBackedgeTakenCount(&L)) || isa(AR.SE.getBackedgeTakenCount(&L)); const bool LoopIsPotentiallyUnbound = HasNoExits || CannotPredictLoopCount; From 2e8aef7b0bef97c6c95094e6b15f2e01a39a5bf2 Mon Sep 17 00:00:00 2001 From: Chris Apple Date: Sun, 8 Sep 2024 08:55:27 -0700 Subject: [PATCH 5/5] Fix clang format --- clang/lib/CodeGen/BackendUtil.cpp | 6 +-- compiler-rt/test/rtsan/bound_loop.cpp | 17 +++---- compiler-rt/test/rtsan/infinite_loop.cpp | 3 +- compiler-rt/test/rtsan/spinlock.cpp | 47 ++++++++----------- compiler-rt/test/rtsan/unbound_loop.cpp | 4 +- .../Instrumentation/RealtimeSanitizer.cpp | 14 +++--- 6 files changed, 44 insertions(+), 47 deletions(-) diff --git a/clang/lib/CodeGen/BackendUtil.cpp b/clang/lib/CodeGen/BackendUtil.cpp index 68bf2044d21d2..9aefcddb934cc 100644 --- a/clang/lib/CodeGen/BackendUtil.cpp +++ b/clang/lib/CodeGen/BackendUtil.cpp @@ -993,9 +993,9 @@ void EmitAssemblyHelper::RunOptimizationPipeline( if (LangOpts.Sanitize.has(SanitizerKind::Realtime)) { PB.registerLoopOptimizerEndEPCallback( - [](LoopPassManager &LPM, OptimizationLevel Level) { - LPM.addPass(RealtimeSanitizerLoopPass()); - }); + [](LoopPassManager &LPM, OptimizationLevel Level) { + LPM.addPass(RealtimeSanitizerLoopPass()); + }); PB.registerScalarOptimizerLateEPCallback( [](FunctionPassManager &FPM, OptimizationLevel Level) { RealtimeSanitizerOptions Opts; diff --git a/compiler-rt/test/rtsan/bound_loop.cpp b/compiler-rt/test/rtsan/bound_loop.cpp index 4db6b9488a10c..e57e0ef7eb628 100644 --- a/compiler-rt/test/rtsan/bound_loop.cpp +++ b/compiler-rt/test/rtsan/bound_loop.cpp @@ -17,23 +17,24 @@ #include - -void FillBufferInterleaved(int* buffer, int sample_count, int channel_count) [[clang::nonblocking]] { +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]] { +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]; - } + 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]; + buffer[i] = scratch_buffer[i]; } int main() { diff --git a/compiler-rt/test/rtsan/infinite_loop.cpp b/compiler-rt/test/rtsan/infinite_loop.cpp index f45bff5862063..8689533510150 100644 --- a/compiler-rt/test/rtsan/infinite_loop.cpp +++ b/compiler-rt/test/rtsan/infinite_loop.cpp @@ -8,7 +8,8 @@ #include void infinity() [[clang::nonblocking]] { - while(true); + while (true) + ; } int main() { diff --git a/compiler-rt/test/rtsan/spinlock.cpp b/compiler-rt/test/rtsan/spinlock.cpp index 1fd4ba7aa2bef..26451524ec42c 100644 --- a/compiler-rt/test/rtsan/spinlock.cpp +++ b/compiler-rt/test/rtsan/spinlock.cpp @@ -10,53 +10,46 @@ class SpinLockTestAndSet { public: - void lock() { - while (lock_flag.test_and_set(std::memory_order_acquire)) { - // Busy-wait (spin) until the lock is acquired - } + 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); - } + void unlock() { lock_flag.clear(std::memory_order_release); } private: - std::atomic_flag lock_flag = ATOMIC_FLAG_INIT; + 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 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); - } + void unlock() { lock_flag.store(false, std::memory_order_release); } private: - std::atomic lock_flag{false}; + std::atomic lock_flag{false}; }; -int lock_violation() [[clang::nonblocking]] -{ +int lock_violation() [[clang::nonblocking]] { #if defined(TEST_AND_SET_SPINLOCK) - SpinLockTestAndSet lock; + SpinLockTestAndSet lock; #elif defined(CAS_SPINLOCK) - SpinLockCompareExchange lock; + SpinLockCompareExchange lock; #else -#error "No spinlock defined" +# error "No spinlock defined" #endif - lock.lock(); - return 0; + lock.lock(); + return 0; } -int main() [[clang::nonblocking]] -{ - lock_violation(); -} +int main() [[clang::nonblocking]] { lock_violation(); } // CHECK-ALL: {{.*Real-time violation.*}} // CHECK-CAS: {{.*SpinLockCompareExchange::lock.*}} diff --git a/compiler-rt/test/rtsan/unbound_loop.cpp b/compiler-rt/test/rtsan/unbound_loop.cpp index 2a23018692292..49e84a98f45f9 100644 --- a/compiler-rt/test/rtsan/unbound_loop.cpp +++ b/compiler-rt/test/rtsan/unbound_loop.cpp @@ -17,8 +17,8 @@ #include - -void BadScalarEvolution(int* buffer, int sample_count, int channel_count) [[clang::nonblocking]] { +void BadScalarEvolution(int *buffer, int sample_count, int channel_count) + [[clang::nonblocking]] { int sample = 0; while (sample < sample_count) { diff --git a/llvm/lib/Transforms/Instrumentation/RealtimeSanitizer.cpp b/llvm/lib/Transforms/Instrumentation/RealtimeSanitizer.cpp index 46612a72a9baa..28efdfa77ec79 100644 --- a/llvm/lib/Transforms/Instrumentation/RealtimeSanitizer.cpp +++ b/llvm/lib/Transforms/Instrumentation/RealtimeSanitizer.cpp @@ -75,17 +75,20 @@ PreservedAnalyses RealtimeSanitizerPass::run(Function &F, return PreservedAnalyses::all(); } -PreservedAnalyses RealtimeSanitizerLoopPass::run(Loop &L, LoopAnalysisManager &AM, LoopStandardAnalysisResults &AR, LPMUpdater &U) { +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(); + Function *F = Context->getParent(); assert(F && "Loop has no parent function"); const bool HasNoExits = L.hasNoExitBlocks(); - const bool CannotPredictLoopCount = isa(AR.SE.getConstantMaxBackedgeTakenCount(&L)) || - isa(AR.SE.getBackedgeTakenCount(&L)); + const bool CannotPredictLoopCount = + isa(AR.SE.getConstantMaxBackedgeTakenCount(&L)) || + isa(AR.SE.getBackedgeTakenCount(&L)); const bool LoopIsPotentiallyUnbound = HasNoExits || CannotPredictLoopCount; if (LoopIsPotentiallyUnbound) { @@ -107,8 +110,7 @@ PreservedAnalyses RealtimeSanitizerLoopPass::run(Loop &L, LoopAnalysisManager &A // TODO: What is preserved here?? return PreservedAnalyses::none(); - } + } return PreservedAnalyses::all(); } -