Skip to content

Commit

Permalink
[asan] Skip promotable allocas to improve performance at -O0
Browse files Browse the repository at this point in the history
Currently, the ASan executables built with -O0 are unnecessarily slow.
The main reason is that ASan instrumentation pass inserts redundant
checks around promotable allocas. These allocas do not get instrumented
under -O1 because they get converted to virtual registered by mem2reg.
With this patch, ASan instrumentation pass will only instrument non
promotable allocas, giving us a speedup of 39% on a collection of
benchmarks with -O0. (There is no measurable speedup at -O1.)

llvm-svn: 230724
  • Loading branch information
AnnaZaks committed Feb 27, 2015
1 parent 2249049 commit 8ed1d81
Show file tree
Hide file tree
Showing 8 changed files with 105 additions and 36 deletions.
82 changes: 51 additions & 31 deletions llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
#include "llvm/Transforms/Utils/Cloning.h"
#include "llvm/Transforms/Utils/Local.h"
#include "llvm/Transforms/Utils/ModuleUtils.h"
#include "llvm/Transforms/Utils/PromoteMemToReg.h"
#include <algorithm>
#include <string>
#include <system_error>
Expand Down Expand Up @@ -165,6 +166,9 @@ static cl::opt<std::string> ClMemoryAccessCallbackPrefix(
cl::init("__asan_"));
static cl::opt<bool> ClInstrumentAllocas("asan-instrument-allocas",
cl::desc("instrument dynamic allocas"), cl::Hidden, cl::init(false));
static cl::opt<bool> ClSkipPromotableAllocas("asan-skip-promotable-allocas",
cl::desc("Do not instrument promotable allocas"),
cl::Hidden, cl::init(true));

// These flags allow to change the shadow mapping.
// The shadow mapping looks like
Expand Down Expand Up @@ -372,6 +376,17 @@ struct AddressSanitizer : public FunctionPass {
void getAnalysisUsage(AnalysisUsage &AU) const override {
AU.addRequired<DominatorTreeWrapperPass>();
}
uint64_t getAllocaSizeInBytes(AllocaInst *AI) const {
Type *Ty = AI->getAllocatedType();
uint64_t SizeInBytes = DL->getTypeAllocSize(Ty);
return SizeInBytes;
}
/// Check if we want (and can) handle this alloca.
bool isInterestingAlloca(AllocaInst &AI) const;
/// If it is an interesting memory access, return the PointerOperand
/// and set IsWrite/Alignment. Otherwise return nullptr.
Value *isInterestingMemoryAccess(Instruction *I, bool *IsWrite,
unsigned *Alignment) const;
void instrumentMop(Instruction *I, bool UseCalls);
void instrumentPointerComparisonOrSubtraction(Instruction *I);
void instrumentAddress(Instruction *OrigIns, Instruction *InsertBefore,
Expand Down Expand Up @@ -599,7 +614,7 @@ struct FunctionStackPoisoner : public InstVisitor<FunctionStackPoisoner> {

/// \brief Collect Alloca instructions we want (and can) handle.
void visitAllocaInst(AllocaInst &AI) {
if (!isInterestingAlloca(AI)) return;
if (!ASan.isInterestingAlloca(AI)) return;

StackAlignment = std::max(StackAlignment, AI.getAlignment());
if (isDynamicAlloca(AI))
Expand Down Expand Up @@ -653,19 +668,6 @@ struct FunctionStackPoisoner : public InstVisitor<FunctionStackPoisoner> {
bool isDynamicAlloca(AllocaInst &AI) const {
return AI.isArrayAllocation() || !AI.isStaticAlloca();
}

// Check if we want (and can) handle this alloca.
bool isInterestingAlloca(AllocaInst &AI) const {
return (AI.getAllocatedType()->isSized() &&
// alloca() may be called with 0 size, ignore it.
getAllocaSizeInBytes(&AI) > 0);
}

uint64_t getAllocaSizeInBytes(AllocaInst *AI) const {
Type *Ty = AI->getAllocatedType();
uint64_t SizeInBytes = ASan.DL->getTypeAllocSize(Ty);
return SizeInBytes;
}
/// Finds alloca where the value comes from.
AllocaInst *findAllocaForValue(Value *V);
void poisonRedZones(ArrayRef<uint8_t> ShadowBytes, IRBuilder<> &IRB,
Expand Down Expand Up @@ -775,38 +777,56 @@ void AddressSanitizer::instrumentMemIntrinsic(MemIntrinsic *MI) {
MI->eraseFromParent();
}

// If I is an interesting memory access, return the PointerOperand
// and set IsWrite/Alignment. Otherwise return nullptr.
static Value *isInterestingMemoryAccess(Instruction *I, bool *IsWrite,
unsigned *Alignment) {
/// Check if we want (and can) handle this alloca.
bool AddressSanitizer::isInterestingAlloca(AllocaInst &AI) const {
return (AI.getAllocatedType()->isSized() &&
// alloca() may be called with 0 size, ignore it.
getAllocaSizeInBytes(&AI) > 0 &&
// We are only interested in allocas not promotable to registers.
// Promotable allocas are common under -O0.
(!ClSkipPromotableAllocas || !isAllocaPromotable(&AI)));
}

/// If I is an interesting memory access, return the PointerOperand
/// and set IsWrite/Alignment. Otherwise return nullptr.
Value *AddressSanitizer::isInterestingMemoryAccess(Instruction *I,
bool *IsWrite,
unsigned *Alignment) const {
// Skip memory accesses inserted by another instrumentation.
if (I->getMetadata("nosanitize"))
return nullptr;

Value *PtrOperand = nullptr;
if (LoadInst *LI = dyn_cast<LoadInst>(I)) {
if (!ClInstrumentReads) return nullptr;
*IsWrite = false;
*Alignment = LI->getAlignment();
return LI->getPointerOperand();
}
if (StoreInst *SI = dyn_cast<StoreInst>(I)) {
PtrOperand = LI->getPointerOperand();
} else if (StoreInst *SI = dyn_cast<StoreInst>(I)) {
if (!ClInstrumentWrites) return nullptr;
*IsWrite = true;
*Alignment = SI->getAlignment();
return SI->getPointerOperand();
}
if (AtomicRMWInst *RMW = dyn_cast<AtomicRMWInst>(I)) {
PtrOperand = SI->getPointerOperand();
} else if (AtomicRMWInst *RMW = dyn_cast<AtomicRMWInst>(I)) {
if (!ClInstrumentAtomics) return nullptr;
*IsWrite = true;
*Alignment = 0;
return RMW->getPointerOperand();
}
if (AtomicCmpXchgInst *XCHG = dyn_cast<AtomicCmpXchgInst>(I)) {
PtrOperand = RMW->getPointerOperand();
} else if (AtomicCmpXchgInst *XCHG = dyn_cast<AtomicCmpXchgInst>(I)) {
if (!ClInstrumentAtomics) return nullptr;
*IsWrite = true;
*Alignment = 0;
return XCHG->getPointerOperand();
PtrOperand = XCHG->getPointerOperand();
}
return nullptr;

// Treat memory accesses to promotable allocas as non-interesting since they
// will not cause memory violations. This greatly speeds up the instrumented
// executable at -O0.
if (ClSkipPromotableAllocas)
if (auto AI = dyn_cast_or_null<AllocaInst>(PtrOperand))
return isInterestingAlloca(*AI) ? AI : nullptr;

return PtrOperand;
}

static bool isPointerOperand(Value *V) {
Expand Down Expand Up @@ -1665,7 +1685,7 @@ void FunctionStackPoisoner::poisonStack() {
SVD.reserve(AllocaVec.size());
for (AllocaInst *AI : AllocaVec) {
ASanStackVariableDescription D = { AI->getName().data(),
getAllocaSizeInBytes(AI),
ASan.getAllocaSizeInBytes(AI),
AI->getAlignment(), AI, 0};
SVD.push_back(D);
}
Expand Down Expand Up @@ -1856,7 +1876,7 @@ void FunctionStackPoisoner::poisonAlloca(Value *V, uint64_t Size,
AllocaInst *FunctionStackPoisoner::findAllocaForValue(Value *V) {
if (AllocaInst *AI = dyn_cast<AllocaInst>(V))
// We're intested only in allocas we can handle.
return isInterestingAlloca(*AI) ? AI : nullptr;
return ASan.isInterestingAlloca(*AI) ? AI : nullptr;
// See if we've already calculated (or started to calculate) alloca for a
// given value.
AllocaForValueMapTy::iterator I = AllocaForValue.find(V);
Expand Down
4 changes: 2 additions & 2 deletions llvm/test/Instrumentation/AddressSanitizer/debug_info.ll
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ define i32 @_Z3zzzi(i32 %p) nounwind uwtable sanitize_address {
entry:
%p.addr = alloca i32, align 4
%r = alloca i32, align 4
store i32 %p, i32* %p.addr, align 4
store volatile i32 %p, i32* %p.addr, align 4
call void @llvm.dbg.declare(metadata i32* %p.addr, metadata !10, metadata !{!"0x102"}), !dbg !11
call void @llvm.dbg.declare(metadata i32* %r, metadata !12, metadata !{!"0x102"}), !dbg !14
%0 = load i32* %p.addr, align 4, !dbg !14
%add = add nsw i32 %0, 1, !dbg !14
store i32 %add, i32* %r, align 4, !dbg !14
store volatile i32 %add, i32* %r, align 4, !dbg !14
%1 = load i32* %r, align 4, !dbg !15
ret i32 %1, !dbg !15
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
; RUN: opt < %s -asan -asan-module -asan-instrument-allocas=1 -S | FileCheck %s --check-prefix=CHECK

target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-apple-macosx10.10.0"

define i32 @test_promotable_allocas() sanitize_address {
entry:
; CHECK: %0 = alloca i32, align 4
; CHECK: store i32 0, i32* %0, align 4
; CHECK: %1 = load i32* %0, align 4
; CHECK: ret i32 %1

; CHECK-NOT: __asan_stack_malloc_0
; CHECK-NOT: icmp
; CHECK-NOT: call void @__asan_report_store4

%0 = alloca i32, align 4
store i32 0, i32* %0, align 4
%1 = load i32* %0, align 4
ret i32 %1
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@ entry:
; CHECK-NOALLOCA-NOT: store i32 -875836469
%0 = alloca i32, align 4
%1 = alloca i8*
store i32 %len, i32* %0, align 4
store volatile i32 %len, i32* %0, align 4
%2 = load i32* %0, align 4
%3 = zext i32 %2 to i64
%4 = alloca i8, i64 %3, align 32
store volatile i8 0, i8* %4
ret void
}

4 changes: 2 additions & 2 deletions llvm/test/Instrumentation/AddressSanitizer/lifetime-uar.ll
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ entry:
; Memory is unpoisoned at llvm.lifetime.start
; CHECK: call void @__asan_unpoison_stack_memory(i64 %{{[^ ]+}}, i64 1)

store i32 0, i32* %retval
store i8 0, i8* %c, align 1
store volatile i32 0, i32* %retval
store volatile i8 0, i8* %c, align 1

call void @llvm.lifetime.end(i64 1, i8* %c)
; Memory is poisoned at llvm.lifetime.end
Expand Down
5 changes: 5 additions & 0 deletions llvm/test/Instrumentation/AddressSanitizer/lifetime.ll
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ entry:
%i = alloca i32, align 4
%i.ptr = bitcast i32* %i to i8*
call void @llvm.lifetime.start(i64 -1, i8* %i.ptr)
store volatile i8 0, i8* %i.ptr
call void @llvm.lifetime.end(i64 -1, i8* %i.ptr)

; Check that lifetime with no size are ignored.
Expand All @@ -30,6 +31,7 @@ define void @lifetime() sanitize_address {
%i = alloca i32, align 4
%i.ptr = bitcast i32* %i to i8*
call void @llvm.lifetime.start(i64 3, i8* %i.ptr)
store volatile i8 0, i8* %i.ptr
; Memory is unpoisoned at llvm.lifetime.start
; CHECK: %[[VAR:[^ ]*]] = ptrtoint i32* %{{[^ ]+}} to i64
; CHECK-NEXT: call void @__asan_unpoison_stack_memory(i64 %[[VAR]], i64 3)
Expand All @@ -43,12 +45,14 @@ define void @lifetime() sanitize_address {
%arr = alloca [10 x i32], align 16
%arr.ptr = bitcast [10 x i32]* %arr to i8*
call void @llvm.lifetime.start(i64 40, i8* %arr.ptr)
store volatile i8 0, i8* %arr.ptr
; CHECK: call void @__asan_unpoison_stack_memory(i64 %{{[^ ]+}}, i64 40)
call void @llvm.lifetime.end(i64 40, i8* %arr.ptr)
; CHECK: call void @__asan_poison_stack_memory(i64 %{{[^ ]+}}, i64 40)

; One more lifetime start/end for the same variable %i.
call void @llvm.lifetime.start(i64 4, i8* %i.ptr)
store volatile i8 0, i8* %i.ptr
; CHECK: call void @__asan_unpoison_stack_memory(i64 %{{[^ ]+}}, i64 4)
call void @llvm.lifetime.end(i64 4, i8* %i.ptr)
; CHECK: call void @__asan_poison_stack_memory(i64 %{{[^ ]+}}, i64 4)
Expand All @@ -68,6 +72,7 @@ entry:
%i = alloca i64, align 4
%i.ptr = bitcast i64* %i to i8*
call void @llvm.lifetime.start(i64 8, i8* %i.ptr)
store volatile i8 0, i8* %i.ptr
; CHECK: __asan_unpoison_stack_memory
br i1 %x, label %bb0, label %bb1

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ entry:
; CHECK: ret void

%XXX = alloca [20 x i8], align 1
%arr.ptr = bitcast [20 x i8]* %XXX to i8*
store volatile i8 0, i8* %arr.ptr
ret void
}

Expand All @@ -37,6 +39,8 @@ entry:
; CHECK: ret void

%XXX = alloca [20 x i8], align 1
%arr.ptr = bitcast [20 x i8]* %XXX to i8*
store volatile i8 0, i8* %arr.ptr
call void asm sideeffect "mov %%rbx, %%rcx", "~{dirflag},~{fpsr},~{flags}"() nounwind
ret void
}
18 changes: 18 additions & 0 deletions llvm/test/Instrumentation/AddressSanitizer/stack_layout.ll
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ entry:
%XXX = alloca [10 x i8], align 1
%YYY = alloca [20 x i8], align 1
%ZZZ = alloca [30 x i8], align 1
%arr1.ptr = bitcast [10 x i8]* %XXX to i8*
store volatile i8 0, i8* %arr1.ptr
%arr2.ptr = bitcast [20 x i8]* %YYY to i8*
store volatile i8 0, i8* %arr2.ptr
%arr3.ptr = bitcast [30 x i8]* %ZZZ to i8*
store volatile i8 0, i8* %arr3.ptr
ret void
}

Expand All @@ -41,6 +47,12 @@ entry:
%AAA = alloca [5 x i8], align 1
%BBB = alloca [55 x i8], align 1
%CCC = alloca [555 x i8], align 1
%arr1.ptr = bitcast [5 x i8]* %AAA to i8*
store volatile i8 0, i8* %arr1.ptr
%arr2.ptr = bitcast [55 x i8]* %BBB to i8*
store volatile i8 0, i8* %arr2.ptr
%arr3.ptr = bitcast [555 x i8]* %CCC to i8*
store volatile i8 0, i8* %arr3.ptr
ret void
}

Expand All @@ -57,5 +69,11 @@ entry:
%AAA = alloca [128 x i8], align 16
%BBB = alloca [128 x i8], align 64
%CCC = alloca [128 x i8], align 256
%arr1.ptr = bitcast [128 x i8]* %AAA to i8*
store volatile i8 0, i8* %arr1.ptr
%arr2.ptr = bitcast [128 x i8]* %BBB to i8*
store volatile i8 0, i8* %arr2.ptr
%arr3.ptr = bitcast [128 x i8]* %CCC to i8*
store volatile i8 0, i8* %arr3.ptr
ret void
}

0 comments on commit 8ed1d81

Please sign in to comment.