Skip to content

Commit

Permalink
[Attributor] Add "free"-based heap2stack deduction
Browse files Browse the repository at this point in the history
Summary:
If there is a unique free of the allocated that has to be reached from
the malloc, we can apply the heap-2-stack transformation even if the
pointer escapes.

Reviewers: hfinkel, sstefan1, uenoku

Subscribers: hiraditya, bollu, llvm-commits

Tags: #llvm

Differential Revision: https://reviews.llvm.org/D68958
  • Loading branch information
jdoerfert committed Oct 31, 2019
1 parent ed7bcb2 commit 0be9cf2
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 32 deletions.
24 changes: 24 additions & 0 deletions llvm/include/llvm/Analysis/MustExecute.h
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,30 @@ struct MustBeExecutedContextExplorer {
}
///}

/// Helper to look for \p I in the context of \p PP.
///
/// The context is expanded until \p I was found or no more expansion is
/// possible.
///
/// \returns True, iff \p I was found.
bool findInContextOf(const Instruction *I, const Instruction *PP) {
auto EIt = begin(PP), EEnd = end(PP);
return findInContextOf(I, EIt, EEnd);
}

/// Helper to look for \p I in the context defined by \p EIt and \p EEnd.
///
/// The context is expanded until \p I was found or no more expansion is
/// possible.
///
/// \returns True, iff \p I was found.
bool findInContextOf(const Instruction *I, iterator &EIt, iterator &EEnd) {
bool Found = EIt.count(I);
while (!Found && EIt != EEnd)
Found = (++EIt).getCurrentInst() == I;
return Found;
}

/// Return the next instruction that is guaranteed to be executed after \p PP.
///
/// \param It The iterator that is used to traverse the must be
Expand Down
61 changes: 43 additions & 18 deletions llvm/lib/Transforms/IPO/Attributor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -728,12 +728,10 @@ struct AAFromMustBeExecutedContext : public Base {

SetVector<const Use *> NextUses;

auto EIt = Explorer.begin(CtxI), EEnd = Explorer.end(CtxI);
for (const Use *U : Uses) {
if (const Instruction *UserI = dyn_cast<Instruction>(U->getUser())) {
auto EIt = Explorer.begin(CtxI), EEnd = Explorer.end(CtxI);
bool Found = EIt.count(UserI);
while (!Found && ++EIt != EEnd)
Found = EIt.getCurrentInst() == UserI;
bool Found = Explorer.findInContextOf(UserI, EIt, EEnd);
if (Found && Base::followUse(A, U, UserI))
for (const Use &Us : UserI->uses())
NextUses.insert(&Us);
Expand Down Expand Up @@ -3634,7 +3632,21 @@ ChangeStatus AAHeapToStackImpl::updateImpl(Attributor &A) {
const Function *F = getAssociatedFunction();
const auto *TLI = A.getInfoCache().getTargetLibraryInfoForFunction(*F);

MustBeExecutedContextExplorer &Explorer =
A.getInfoCache().getMustBeExecutedContextExplorer();

auto FreeCheck = [&](Instruction &I) {
const auto &Frees = FreesForMalloc.lookup(&I);
if (Frees.size() != 1)
return false;
Instruction *UniqueFree = *Frees.begin();
return Explorer.findInContextOf(UniqueFree, I.getNextNode());
};

auto UsesCheck = [&](Instruction &I) {
bool ValidUsesOnly = true;
bool MustUse = true;

SmallPtrSet<const Use *, 8> Visited;
SmallVector<const Use *, 8> Worklist;

Expand All @@ -3652,10 +3664,12 @@ ChangeStatus AAHeapToStackImpl::updateImpl(Attributor &A) {
continue;
if (auto *SI = dyn_cast<StoreInst>(UserI)) {
if (SI->getValueOperand() == U->get()) {
LLVM_DEBUG(dbgs() << "[H2S] escaping store to memory: " << *UserI << "\n");
return false;
LLVM_DEBUG(dbgs()
<< "[H2S] escaping store to memory: " << *UserI << "\n");
ValidUsesOnly = false;
} else {
// A store into the malloc'ed memory is fine.
}
// A store into the malloc'ed memory is fine.
continue;
}

Expand All @@ -3673,8 +3687,14 @@ ChangeStatus AAHeapToStackImpl::updateImpl(Attributor &A) {

// Record malloc.
if (isFreeCall(UserI, TLI)) {
FreesForMalloc[&I].insert(
cast<Instruction>(const_cast<User *>(UserI)));
if (MustUse) {
FreesForMalloc[&I].insert(
cast<Instruction>(const_cast<User *>(UserI)));
} else {
LLVM_DEBUG(dbgs() << "[H2S] free potentially on different mallocs: "
<< *UserI << "\n");
ValidUsesOnly = false;
}
continue;
}

Expand All @@ -3688,22 +3708,25 @@ ChangeStatus AAHeapToStackImpl::updateImpl(Attributor &A) {

if (!NoCaptureAA.isAssumedNoCapture() || !NoFreeAA.isAssumedNoFree()) {
LLVM_DEBUG(dbgs() << "[H2S] Bad user: " << *UserI << "\n");
return false;
ValidUsesOnly = false;
}
continue;
}

if (isa<GetElementPtrInst>(UserI) || isa<BitCastInst>(UserI)) {
if (isa<GetElementPtrInst>(UserI) || isa<BitCastInst>(UserI) ||
isa<PHINode>(UserI) || isa<SelectInst>(UserI)) {
MustUse &= !(isa<PHINode>(UserI) || isa<SelectInst>(UserI));
for (Use &U : UserI->uses())
Worklist.push_back(&U);
continue;
}

// Unknown user.
// Unknown user for which we can not track uses further (in a way that
// makes sense).
LLVM_DEBUG(dbgs() << "[H2S] Unknown user: " << *UserI << "\n");
return false;
ValidUsesOnly = false;
}
return true;
return ValidUsesOnly;
};

auto MallocCallocCheck = [&](Instruction &I) {
Expand All @@ -3720,7 +3743,7 @@ ChangeStatus AAHeapToStackImpl::updateImpl(Attributor &A) {
if (IsMalloc) {
if (auto *Size = dyn_cast<ConstantInt>(I.getOperand(0)))
if (Size->getValue().sle(MaxHeapToStackSize))
if (UsesCheck(I)) {
if (UsesCheck(I) || FreeCheck(I)) {
MallocCalls.insert(&I);
return true;
}
Expand All @@ -3730,7 +3753,7 @@ ChangeStatus AAHeapToStackImpl::updateImpl(Attributor &A) {
if (auto *Size = dyn_cast<ConstantInt>(I.getOperand(1)))
if ((Size->getValue().umul_ov(Num->getValue(), Overflow))
.sle(MaxHeapToStackSize))
if (!Overflow && UsesCheck(I)) {
if (!Overflow && (UsesCheck(I) || FreeCheck(I))) {
MallocCalls.insert(&I);
return true;
}
Expand All @@ -3756,8 +3779,10 @@ struct AAHeapToStackFunction final : public AAHeapToStackImpl {
/// See AbstractAttribute::trackStatistics()
void trackStatistics() const override {
STATS_DECL(MallocCalls, Function,
"Number of MallocCalls converted to allocas");
BUILD_STAT_NAME(MallocCalls, Function) += MallocCalls.size();
"Number of malloc calls converted to allocas");
for (auto *C : MallocCalls)
if (!BadMallocCalls.count(C))
++BUILD_STAT_NAME(MallocCalls, Function);
}
};

Expand Down
28 changes: 24 additions & 4 deletions llvm/test/Transforms/FunctionAttrs/heap_to_stack.ll
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ declare void @func_throws(...)

declare void @sync_func(i8* %p)

declare void @sync_will_return(i8* %p) willreturn
declare void @sync_will_return(i8* %p) willreturn nounwind

declare void @no_sync_func(i8* nocapture %p) nofree nosync willreturn

Expand Down Expand Up @@ -202,11 +202,11 @@ define i32 @test_lifetime() {
}

; TEST 11
; FIXME: should be ok

define void @test11() {
%1 = tail call noalias i8* @malloc(i64 4)
; CHECK: @malloc(i64 4)
; CHECK: test11
; CHECK-NEXT: alloc
; CHECK-NEXT: @sync_will_return(i8* %1)
tail call void @sync_will_return(i8* %1)
tail call void @free(i8* %1)
Expand Down Expand Up @@ -330,9 +330,29 @@ define void @test16b(i8 %v, i8** %P) {
%1 = tail call noalias i8* @malloc(i64 4)
; CHECK-NEXT: store i8* %1, i8** %P
store i8* %1, i8** %P
; CHECK-NEXT: @no_sync_func(i8* %1)
; CHECK-NEXT: @no_sync_func(i8* nocapture %1)
tail call void @no_sync_func(i8* %1)
; CHECK-NEXT: @free(i8* %1)
tail call void @free(i8* %1)
ret void
}

define void @test16c(i8 %v, i8** %P) {
; CHECK: %1 = alloca
%1 = tail call noalias i8* @malloc(i64 4)
; CHECK-NEXT: store i8* %1, i8** %P
store i8* %1, i8** %P
; CHECK-NEXT: @no_sync_func(i8* nocapture %1)
tail call void @no_sync_func(i8* %1) nounwind
; CHECK-NOT: @free
tail call void @free(i8* %1)
ret void
}

define void @test16d(i8 %v, i8** %P) {
; CHECK: %1 = tail call noalias i8* @malloc(i64 4)
%1 = tail call noalias i8* @malloc(i64 4)
; CHECK-NEXT: store i8* %1, i8** %P
store i8* %1, i8** %P
ret void
}
20 changes: 10 additions & 10 deletions llvm/test/Transforms/FunctionAttrs/noalias_returned.ll
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ define void @test12_1() {
; CHECK-NEXT: tail call void @use_nocapture(i8* noalias nonnull align 4 dereferenceable(1) [[A]])
; CHECK-NEXT: tail call void @use_nocapture(i8* noalias nonnull align 4 dereferenceable(1) [[A]])
; CHECK-NEXT: tail call void @use_nocapture(i8* noalias nocapture [[B]])
; CHECK-NEXT: tail call void @use_nocapture(i8* noalias [[B]])
; CHECK-NEXT: tail call void @use_nocapture(i8* noalias nocapture [[B]])
; CHECK-NEXT: ret void
;
%A = alloca i8, align 4
Expand All @@ -221,10 +221,10 @@ define void @test12_2(){
; CHECK-NEXT: [[A:%.*]] = tail call noalias i8* @malloc(i64 4)
; FIXME: This should be @use_nocapture(i8* noalias [[A]])
; CHECK-NEXT: tail call void @use_nocapture(i8* nocapture [[A]])
; FIXME: This should be @use_nocapture(i8* noalias [[A]])
; CHECK-NEXT: tail call void @use_nocapture(i8* [[A]])
; FIXME: This should be @use_nocapture(i8* noalias nocapture [[A]])
; CHECK-NEXT: tail call void @use_nocapture(i8* nocapture [[A]])
; CHECK-NEXT: tail call void @use(i8* [[A]])
; CHECK-NEXT: tail call void @use_nocapture(i8* [[A]])
; CHECK-NEXT: tail call void @use_nocapture(i8* nocapture [[A]])
; CHECK-NEXT: ret void
;
%A = tail call noalias i8* @malloc(i64 4)
Expand All @@ -239,7 +239,7 @@ declare void @two_args(i8* nocapture , i8* nocapture)
define void @test12_3(){
; CHECK-LABEL: @test12_3(
%A = tail call noalias i8* @malloc(i64 4)
; CHECK: tail call void @two_args(i8* nocapture %A, i8* %A)
; CHECK: tail call void @two_args(i8* nocapture %A, i8* nocapture %A)
tail call void @two_args(i8* %A, i8* %A)
ret void
}
Expand All @@ -252,17 +252,17 @@ define void @test12_4(){
%A_1 = getelementptr i8, i8* %A, i64 1
%B_0 = getelementptr i8, i8* %B, i64 0

; CHECK: tail call void @two_args(i8* noalias %A, i8* noalias %B)
; CHECK: tail call void @two_args(i8* noalias nocapture %A, i8* noalias nocapture %B)
tail call void @two_args(i8* %A, i8* %B)

; CHECK: tail call void @two_args(i8* %A, i8* nocapture %A_0)
; CHECK: tail call void @two_args(i8* nocapture %A, i8* nocapture %A_0)
tail call void @two_args(i8* %A, i8* %A_0)

; CHECK: tail call void @two_args(i8* %A, i8* %A_1)
; CHECK: tail call void @two_args(i8* nocapture %A, i8* nocapture %A_1)
tail call void @two_args(i8* %A, i8* %A_1)

; FIXME: This should be @two_args(i8* noalias %A_0, i8* noalias %B_0)
; CHECK: tail call void @two_args(i8* %A_0, i8* nocapture %B_0)
; FIXME: This should be @two_args(i8* noalias nocapture %A_0, i8* noalias nocapture %B_0)
; CHECK: tail call void @two_args(i8* nocapture %A_0, i8* nocapture %B_0)
tail call void @two_args(i8* %A_0, i8* %B_0)
ret void
}

0 comments on commit 0be9cf2

Please sign in to comment.