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)) + 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,23 @@ /// See AbstractAttribute::updateImpl(...). ChangeStatus updateImpl(Attributor &A) override { ChangeStatus Changed = ChangeStatus::UNCHANGED; - if (IsAssumedSideEffectFree && !isAssumedSideEffectFree(A, getCtxI())) { + Instruction *CtxI = getCtxI(); + + 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; + A.recordDependence(HeapToStackAA, *this, DepClassTy::OPTIONAL); + return Changed; + } + + if (IsAssumedSideEffectFree && !isAssumedSideEffectFree(A, CtxI)) { IsAssumedSideEffectFree = false; Changed = ChangeStatus::CHANGED; } @@ -4877,6 +4903,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 IsMalloc = isMallocOrCallocLikeFn(I, TLI); + if (!IsMalloc && !isFreeCall(I, TLI)) + return false; + + auto IsI = [I](Instruction *C) { return C == I; }; + if (IsMalloc) + return llvm::any_of(MallocCalls, IsI); + + 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/ArgumentPromotion/aggregate-promote.ll b/llvm/test/Transforms/Attributor/ArgumentPromotion/aggregate-promote.ll --- a/llvm/test/Transforms/Attributor/ArgumentPromotion/aggregate-promote.ll +++ b/llvm/test/Transforms/Attributor/ArgumentPromotion/aggregate-promote.ll @@ -1,5 +1,5 @@ ; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --function-signature --scrub-attributes -; RUN: opt -disable-output -passes=attributor -aa-pipeline='basic-aa' -attributor-disable=false -attributor-max-iterations-verify -attributor-max-iterations=1 < %s +; RUN: opt -disable-output -passes=attributor -aa-pipeline='basic-aa' -attributor-disable=false -attributor-max-iterations-verify -attributor-max-iterations=2 < %s %T = type { i32, i32, i32, i32 } @G = constant %T { i32 0, i32 0, i32 17, i32 25 } diff --git a/llvm/test/Transforms/Attributor/ArgumentPromotion/chained.ll b/llvm/test/Transforms/Attributor/ArgumentPromotion/chained.ll --- a/llvm/test/Transforms/Attributor/ArgumentPromotion/chained.ll +++ b/llvm/test/Transforms/Attributor/ArgumentPromotion/chained.ll @@ -1,5 +1,5 @@ ; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --function-signature --scrub-attributes -; RUN: opt -S -passes='attributor' -aa-pipeline='basic-aa' -attributor-disable=false -attributor-max-iterations-verify -attributor-max-iterations=1 < %s | FileCheck %s +; RUN: opt -S -passes='attributor' -aa-pipeline='basic-aa' -attributor-disable=false -attributor-max-iterations-verify -attributor-max-iterations=2 < %s | FileCheck %s @G1 = constant i32 0 @G2 = constant i32* @G1 diff --git a/llvm/test/Transforms/Attributor/ArgumentPromotion/musttail.ll b/llvm/test/Transforms/Attributor/ArgumentPromotion/musttail.ll --- a/llvm/test/Transforms/Attributor/ArgumentPromotion/musttail.ll +++ b/llvm/test/Transforms/Attributor/ArgumentPromotion/musttail.ll @@ -1,5 +1,5 @@ ; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --function-signature --scrub-attributes -; RUN: opt -S -passes=attributor -aa-pipeline='basic-aa' -attributor-disable=false -attributor-max-iterations-verify -attributor-max-iterations=1 < %s | FileCheck %s +; RUN: opt -S -passes=attributor -aa-pipeline='basic-aa' -attributor-disable=false -attributor-max-iterations-verify -attributor-max-iterations=2 < %s | FileCheck %s ; PR36543 ; Don't promote arguments of musttail callee diff --git a/llvm/test/Transforms/Attributor/IPConstantProp/PR26044.ll b/llvm/test/Transforms/Attributor/IPConstantProp/PR26044.ll --- a/llvm/test/Transforms/Attributor/IPConstantProp/PR26044.ll +++ b/llvm/test/Transforms/Attributor/IPConstantProp/PR26044.ll @@ -1,5 +1,5 @@ ; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --function-signature --scrub-attributes -; RUN: opt -S -passes=attributor -aa-pipeline='basic-aa' -attributor-disable=false -attributor-max-iterations-verify -attributor-max-iterations=1 < %s | FileCheck %s +; RUN: opt -S -passes=attributor -aa-pipeline='basic-aa' -attributor-disable=false -attributor-max-iterations-verify -attributor-max-iterations=2 < %s | FileCheck %s target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" target triple = "x86_64-unknown-linux-gnu" 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) diff --git a/llvm/test/Transforms/Attributor/liveness_chains.ll b/llvm/test/Transforms/Attributor/liveness_chains.ll --- a/llvm/test/Transforms/Attributor/liveness_chains.ll +++ b/llvm/test/Transforms/Attributor/liveness_chains.ll @@ -1,8 +1,8 @@ ; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --function-signature --scrub-attributes -; RUN: opt -attributor --attributor-disable=false -attributor-max-iterations-verify -attributor-annotate-decl-cs -attributor-max-iterations=1 -S < %s | FileCheck %s -; RUN: opt -attributor-cgscc --attributor-disable=false -attributor-annotate-decl-cs -attributor-max-iterations=1 -S < %s | FileCheck %s -; RUN: opt -passes=attributor --attributor-disable=false -attributor-max-iterations-verify -attributor-annotate-decl-cs -attributor-max-iterations=1 -S < %s | FileCheck %s -; RUN: opt -passes='attributor-cgscc' --attributor-disable=false -attributor-annotate-decl-cs -attributor-max-iterations=1 -S < %s | FileCheck %s +; RUN: opt -attributor --attributor-disable=false -attributor-max-iterations-verify -attributor-annotate-decl-cs -attributor-max-iterations=2 -S < %s | FileCheck %s +; RUN: opt -attributor-cgscc --attributor-disable=false -attributor-annotate-decl-cs -attributor-max-iterations=2 -S < %s | FileCheck %s +; RUN: opt -passes=attributor --attributor-disable=false -attributor-max-iterations-verify -attributor-annotate-decl-cs -attributor-max-iterations=2 -S < %s | FileCheck %s +; RUN: opt -passes='attributor-cgscc' --attributor-disable=false -attributor-annotate-decl-cs -attributor-max-iterations=2 -S < %s | FileCheck %s ; Make sure we need a single iteration to determine the chains are dead/alive.