diff --git a/llvm/include/llvm/Transforms/IPO/Attributor.h b/llvm/include/llvm/Transforms/IPO/Attributor.h --- a/llvm/include/llvm/Transforms/IPO/Attributor.h +++ b/llvm/include/llvm/Transforms/IPO/Attributor.h @@ -2459,6 +2459,9 @@ /// Returns true if HeapToStack conversion is known to be possible. bool isKnownHeapToStack() const { return getKnown(); } + /// Returns true if instruction (malloc/free) is identified for deletion. + virtual bool isAssumedToBeDeleted(Attributor &A, Instruction *I) const; + /// Return an IR position, see struct IRPosition. const IRPosition &getIRPosition() const { return *this; } diff --git a/llvm/lib/Transforms/IPO/Attributor.cpp b/llvm/lib/Transforms/IPO/Attributor.cpp --- a/llvm/lib/Transforms/IPO/Attributor.cpp +++ b/llvm/lib/Transforms/IPO/Attributor.cpp @@ -1687,11 +1687,13 @@ ChangeStatus updateImpl(Attributor &A) override { auto CheckForNoFree = [&](Instruction &I) { ImmutableCallSite ICS(&I); + if (ICS.hasFnAttr(Attribute::NoFree)) return true; const auto &NoFreeAA = A.getAAFor(*this, IRPosition::callsite_function(ICS)); + return NoFreeAA.isAssumedNoFree(); }; @@ -2904,6 +2906,14 @@ ChangeStatus manifest(Attributor &A) override { Value &V = getAssociatedValue(); if (auto *I = dyn_cast(&V)) { + const auto *TLI = A.getInfoCache().getTargetLibraryInfoForFunction( + *(I->getParent()->getParent())); + + // These are Mallocs/Frees that are marked for deletion by H2S. + // No need to delete them after manifest (already deleted by H2S). + if (isMallocOrCallocLikeFn(I, TLI) || isFreeCall(I, TLI)) + return ChangeStatus::CHANGED; + // If we get here we basically know the users are all dead. We check if // isAssumedSideEffectFree returns true here again because it might not be // the case and only the users are dead but the instruction (=call) is @@ -3022,7 +3032,22 @@ /// See AbstractAttribute::updateImpl(...). ChangeStatus updateImpl(Attributor &A) override { ChangeStatus Changed = ChangeStatus::UNCHANGED; - if (IsAssumedSideEffectFree && !isAssumedSideEffectFree(A, getCtxI())) { + Instruction *CtxI = getCtxI(); + + if (!IsAssumedSideEffectFree) { + const IRPosition &IRPFunction = + IRPosition::function(*(CtxI->getParent()->getParent())); + + // Check for any mallocs/frees that will be deleted (dead). + const auto &HeapToStackAA = A.getAAFor( + *this, IRPFunction, /* TrackDepencence */ false); + + if (HeapToStackAA.isAssumedToBeDeleted(A, CtxI)) { + // So isAssumedDead() can actually return true. + IsAssumedSideEffectFree = true; + return indicateOptimisticFixpoint(); + } + } else if (!isAssumedSideEffectFree(A, CtxI)) { IsAssumedSideEffectFree = false; Changed = ChangeStatus::CHANGED; } @@ -4877,6 +4902,29 @@ DenseMap> FreesForMalloc; ChangeStatus updateImpl(Attributor &A) override; + + // Checks if any malloc/frees is marked for deletion. + bool isAssumedToBeDeleted(Attributor &A, Instruction *I) const override { + if (!getAssumed()) + return false; + + const auto *TLI = A.getInfoCache().getTargetLibraryInfoForFunction( + *(I->getParent()->getParent())); + + bool IsFree = isFreeCall(I, TLI); + if (!isMallocOrCallocLikeFn(I, TLI) && !IsFree) + return false; + + auto IsI = [I](Instruction *C) { return C == I; }; + if(llvm::any_of(MallocCalls, IsI)) + return true; + + for (auto &It : FreesForMalloc) + if (llvm::any_of(It.second, IsI)) + return true; + + return false; + } }; ChangeStatus AAHeapToStackImpl::updateImpl(Attributor &A) { diff --git a/llvm/test/Transforms/Attributor/heap_to_stack.ll b/llvm/test/Transforms/Attributor/heap_to_stack.ll --- a/llvm/test/Transforms/Attributor/heap_to_stack.ll +++ b/llvm/test/Transforms/Attributor/heap_to_stack.ll @@ -56,6 +56,8 @@ ; TEST 3 - 1 malloc, 1 free +; CHECK: Function Attrs: nofree nosync willreturn +; CHECK-NEXT: define void @test3 define void @test3() { %1 = tail call noalias i8* @malloc(i64 4) ; CHECK: %1 = alloca i8, i64 4 @@ -78,6 +80,8 @@ declare noalias i8* @calloc(i64, i64) +; CHECK: Function Attrs: nofree nosync willreturn +; CHECK-NEXT: define void @test0 define void @test0() { %1 = tail call noalias i8* @calloc(i64 2, i64 4) ; CHECK: %1 = alloca i8, i64 8 @@ -90,7 +94,10 @@ ret void } -; TEST 4 +; TEST 4 + +; CHECK: Function Attrs: nofree nosync willreturn +; CHECK-NEXT: define void @test4 define void @test4() { %1 = tail call noalias i8* @malloc(i64 4) ; CHECK: %1 = alloca i8, i64 4 @@ -125,6 +132,8 @@ ; TEST 6 - all exit paths have a call to free +; CHECK: Function Attrs: nofree nosync +; CHECK-NEXT: define void @test6 define void @test6(i32) { %2 = tail call noalias i8* @malloc(i64 4) ; CHECK: %2 = alloca i8, i64 4 @@ -192,6 +201,8 @@ ; TEST 10 - 1 malloc, 1 free +; CHECK: Function Attrs: nofree nosync willreturn +; CHECK-NEXT: define i32 @test10 define i32 @test10() { %1 = tail call noalias i8* @malloc(i64 4) ; CHECK: %1 = alloca i8, i64 4 @@ -205,6 +216,8 @@ ret i32 %3 } +; CHECK: Function Attrs: willreturn +; CHECK-NEXT: define i32 @test_lifetime define i32 @test_lifetime() { %1 = tail call noalias i8* @malloc(i64 4) ; CHECK: %1 = alloca i8, i64 4 @@ -219,11 +232,12 @@ ret i32 %3 } -; TEST 11 +; TEST 11 +; CHECK: Function Attrs: nounwind willreturn +; CHECK-NEXT: define void @test11 define void @test11() { %1 = tail call noalias i8* @malloc(i64 4) - ; CHECK: test11 ; CHECK-NEXT: alloc ; CHECK-NEXT: @sync_will_return(i8* %1) tail call void @sync_will_return(i8* %1) @@ -232,6 +246,9 @@ } ; TEST 12 + +; CHECK: Function Attrs: nofree nosync nounwind +; CHECK-NEXT: define i32 @irreducible_cfg define i32 @irreducible_cfg(i32 %0) { ; CHECK: alloca i8, i64 4 ; CHECK-NEXT: %3 = bitcast @@ -273,6 +290,8 @@ } +; CHECK: Function Attrs: nofree nosync nounwind +; CHECK-NEXT: define i32 @malloc_in_loop define i32 @malloc_in_loop(i32 %0) { %2 = alloca i32, align 4 %3 = alloca i32*, align 8 @@ -357,6 +376,8 @@ ret void } +; CHECK: Function Attrs: nofree nosync willreturn +; CHECK-NEXT: define void @test16a define void @test16a(i8 %v, i8** %P) { ; CHECK: %1 = alloca %1 = tail call noalias i8* @malloc(i64 4) @@ -381,6 +402,8 @@ ret void } +; CHECK: Function Attrs: nofree nosync nounwind willreturn +; CHECK-NEXT: define void @test16c define void @test16c(i8 %v, i8** %P) { ; CHECK: %1 = alloca %1 = tail call noalias i8* @malloc(i64 4)