[llvm] r366736 - [Attributor] Liveness analysis.
Eric Christopher via llvm-commits
llvm-commits at lists.llvm.org
Mon Jul 22 14:04:51 PDT 2019
I've temporarily reverted this as it seems to be breaking the build.
-eric
On Mon, Jul 22, 2019 at 1:53 PM Stefan Stipanovic via llvm-commits
<llvm-commits at lists.llvm.org> wrote:
>
> Author: sstefan
> Date: Mon Jul 22 13:54:30 2019
> New Revision: 366736
>
> URL: http://llvm.org/viewvc/llvm-project?rev=366736&view=rev
> Log:
> [Attributor] Liveness analysis.
>
> Liveness analysis abstract attribute used to indicate which BasicBlocks are dead and can therefore be ignored.
> Right now we are only looking at noreturn calls.
>
> Reviewers: jdoerfert, uenoku
>
> Subscribers: hiraditya, llvm-commits
>
> Differential revision: https://reviews.llvm.org/D64162
>
> Added:
> llvm/trunk/test/Transforms/FunctionAttrs/liveness.ll
> Modified:
> llvm/trunk/include/llvm/Transforms/IPO/Attributor.h
> llvm/trunk/include/llvm/Transforms/Utils/Local.h
> llvm/trunk/lib/Transforms/IPO/Attributor.cpp
> llvm/trunk/lib/Transforms/Utils/Local.cpp
>
> Modified: llvm/trunk/include/llvm/Transforms/IPO/Attributor.h
> URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/include/llvm/Transforms/IPO/Attributor.h?rev=366736&r1=366735&r2=366736&view=diff
> ==============================================================================
> --- llvm/trunk/include/llvm/Transforms/IPO/Attributor.h (original)
> +++ llvm/trunk/include/llvm/Transforms/IPO/Attributor.h Mon Jul 22 13:54:30 2019
> @@ -805,6 +805,46 @@ struct AANoAlias : public AbstractAttrib
> static constexpr Attribute::AttrKind ID = Attribute::NoAlias;
> };
>
> +/// An AbstractAttribute for noreturn.
> +struct AANoReturn : public AbstractAttribute {
> +
> + /// See AbstractAttribute::AbstractAttribute(...).
> + AANoReturn(Value &V, InformationCache &InfoCache)
> + : AbstractAttribute(V, InfoCache) {}
> +
> + /// Return true if the underlying object is known to never return.
> + virtual bool isKnownNoReturn() const = 0;
> +
> + /// Return true if the underlying object is assumed to never return.
> + virtual bool isAssumedNoReturn() const = 0;
> +
> + /// See AbstractAttribute::getAttrKind()
> + Attribute::AttrKind getAttrKind() const override { return ID; }
> +
> + /// The identifier used by the Attributor for this class of attributes.
> + static constexpr Attribute::AttrKind ID = Attribute::NoReturn;
> +};
> +
> +/// An abstract interface for liveness abstract attribute.
> +struct AAIsDead : public AbstractAttribute {
> +
> + /// See AbstractAttribute::AbstractAttribute(...).
> + AAIsDead(Value &V, InformationCache &InfoCache)
> + : AbstractAttribute(V, InfoCache) {}
> +
> + /// See AbstractAttribute::getAttrKind()
> + Attribute::AttrKind getAttrKind() const override { return ID; }
> +
> + static constexpr Attribute::AttrKind ID =
> + Attribute::AttrKind(Attribute::EndAttrKinds + 1);
> +
> + /// Returns true if \p BB is assumed dead.
> + virtual bool isAssumedDead(BasicBlock *BB) const = 0;
> +
> + /// Returns true if \p BB is known dead.
> + virtual bool isKnownDead(BasicBlock *BB) const = 0;
> +};
> +
> } // end namespace llvm
>
> #endif // LLVM_TRANSFORMS_IPO_FUNCTIONATTRS_H
>
> Modified: llvm/trunk/include/llvm/Transforms/Utils/Local.h
> URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/include/llvm/Transforms/Utils/Local.h?rev=366736&r1=366735&r2=366736&view=diff
> ==============================================================================
> --- llvm/trunk/include/llvm/Transforms/Utils/Local.h (original)
> +++ llvm/trunk/include/llvm/Transforms/Utils/Local.h Mon Jul 22 13:54:30 2019
> @@ -271,6 +271,9 @@ inline unsigned getKnownAlignment(Value
> return getOrEnforceKnownAlignment(V, 0, DL, CxtI, AC, DT);
> }
>
> +/// This function converts the specified invoek into a normall call.
> +void changeToCall(InvokeInst *II, DomTreeUpdater *DTU = nullptr);
> +
> ///===---------------------------------------------------------------------===//
> /// Dbg Intrinsic utilities
> ///
>
> Modified: llvm/trunk/lib/Transforms/IPO/Attributor.cpp
> URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/lib/Transforms/IPO/Attributor.cpp?rev=366736&r1=366735&r2=366736&view=diff
> ==============================================================================
> --- llvm/trunk/lib/Transforms/IPO/Attributor.cpp (original)
> +++ llvm/trunk/lib/Transforms/IPO/Attributor.cpp Mon Jul 22 13:54:30 2019
> @@ -16,6 +16,7 @@
> #include "llvm/Transforms/IPO/Attributor.h"
>
> #include "llvm/ADT/DepthFirstIterator.h"
> +#include "llvm/ADT/STLExtras.h"
> #include "llvm/ADT/SetVector.h"
> #include "llvm/ADT/SmallPtrSet.h"
> #include "llvm/ADT/SmallVector.h"
> @@ -31,6 +32,9 @@
> #include "llvm/Support/CommandLine.h"
> #include "llvm/Support/Debug.h"
> #include "llvm/Support/raw_ostream.h"
> +#include "llvm/Transforms/Utils/BasicBlockUtils.h"
> +#include "llvm/Transforms/Utils/Local.h"
> +
> #include <cassert>
>
> using namespace llvm;
> @@ -1411,6 +1415,173 @@ ChangeStatus AANoAliasReturned::updateIm
> return ChangeStatus::UNCHANGED;
> }
>
> +/// -------------------AAIsDead Function Attribute-----------------------
> +
> +struct AAIsDeadFunction : AAIsDead, BooleanState {
> +
> + AAIsDeadFunction(Function &F, InformationCache &InfoCache)
> + : AAIsDead(F, InfoCache) {}
> +
> + /// See AbstractAttribute::getState()
> + /// {
> + AbstractState &getState() override { return *this; }
> + const AbstractState &getState() const override { return *this; }
> + /// }
> +
> + /// See AbstractAttribute::getManifestPosition().
> + ManifestPosition getManifestPosition() const override { return MP_FUNCTION; }
> +
> + void initialize(Attributor &A) override {
> + Function &F = getAnchorScope();
> +
> + ToBeExploredPaths.insert(&(F.getEntryBlock().front()));
> + AssumedLiveBlocks.insert(&(F.getEntryBlock()));
> + for (size_t i = 0; i < ToBeExploredPaths.size(); ++i)
> + explorePath(A, ToBeExploredPaths[i]);
> + }
> +
> + /// Explores new instructions starting from \p I. If instruction is dead, stop
> + /// and return true if it discovered a new instruction.
> + bool explorePath(Attributor &A, Instruction *I);
> +
> + const std::string getAsStr() const override {
> + return "LiveBBs(" + std::to_string(AssumedLiveBlocks.size()) + "/" +
> + std::to_string(getAnchorScope().size()) + ")";
> + }
> +
> + /// See AbstractAttribute::manifest(...).
> + ChangeStatus manifest(Attributor &A) override {
> + assert(getState().isValidState() &&
> + "Attempted to manifest an invalid state!");
> +
> + ChangeStatus HasChanged = ChangeStatus::UNCHANGED;
> +
> + for (Instruction *I : NoReturnCalls) {
> + BasicBlock *BB = I->getParent();
> +
> + /// Invoke is replaced with a call and unreachable is placed after it.
> + if (auto *II = dyn_cast<InvokeInst>(I)) {
> + changeToCall(II);
> + changeToUnreachable(BB->getTerminator(), /* UseLLVMTrap */ false);
> + LLVM_DEBUG(dbgs() << "[AAIsDead] Replaced invoke with call inst\n");
> + continue;
> + }
> +
> + SplitBlock(BB, I->getNextNode());
> + changeToUnreachable(BB->getTerminator(), /* UseLLVMTrap */ false);
> + HasChanged = ChangeStatus::CHANGED;
> + }
> +
> + return HasChanged;
> + }
> +
> + /// See AbstractAttribute::updateImpl(...).
> + ChangeStatus updateImpl(Attributor &A) override;
> +
> + /// See AAIsDead::isAssumedDead().
> + bool isAssumedDead(BasicBlock *BB) const override {
> + if (!getAssumed())
> + return false;
> + return !AssumedLiveBlocks.count(BB);
> + }
> +
> + /// See AAIsDead::isKnownDead().
> + bool isKnownDead(BasicBlock *BB) const override {
> + if (!getKnown())
> + return false;
> + return !AssumedLiveBlocks.count(BB);
> + }
> +
> + /// Collection of to be explored paths.
> + SmallSetVector<Instruction *, 8> ToBeExploredPaths;
> +
> + /// Collection of all assumed live BasicBlocks.
> + DenseSet<BasicBlock *> AssumedLiveBlocks;
> +
> + /// Collection of calls with noreturn attribute, assumed or knwon.
> + SmallSetVector<Instruction *, 4> NoReturnCalls;
> +};
> +
> +bool AAIsDeadFunction::explorePath(Attributor &A, Instruction *I) {
> + BasicBlock *BB = I->getParent();
> +
> + while (I) {
> + ImmutableCallSite ICS(I);
> +
> + if (ICS) {
> + auto *NoReturnAA = A.getAAFor<AANoReturn>(*this, *I);
> +
> + if (NoReturnAA && NoReturnAA->isAssumedNoReturn()) {
> + if (!NoReturnCalls.insert(I))
> + // If I is already in the NoReturnCalls set, then it stayed noreturn
> + // and we didn't discover any new instructions.
> + return false;
> +
> + // Discovered new noreturn call, return true to indicate that I is not
> + // noreturn anymore and should be deleted from NoReturnCalls.
> + return true;
> + }
> +
> + if (ICS.hasFnAttr(Attribute::NoReturn)) {
> + if(!NoReturnCalls.insert(I))
> + return false;
> +
> + return true;
> + }
> + }
> +
> + I = I->getNextNode();
> + }
> +
> + // get new paths (reachable blocks).
> + for (BasicBlock *SuccBB : successors(BB)) {
> + Instruction *Inst = &(SuccBB->front());
> + AssumedLiveBlocks.insert(SuccBB);
> + ToBeExploredPaths.insert(Inst);
> + }
> +
> + return true;
> +}
> +
> +ChangeStatus AAIsDeadFunction::updateImpl(Attributor &A) {
> + Function &F = getAnchorScope();
> +
> + // Temporary collection to iterate over existing noreturn instructions. This
> + // will alow easier modification of NoReturnCalls collection
> + SmallVector<Instruction *, 8> NoReturnChanged;
> + ChangeStatus Status = ChangeStatus::UNCHANGED;
> +
> + for (Instruction *I : NoReturnCalls)
> + NoReturnChanged.push_back(I);
> +
> + for (Instruction *I : NoReturnChanged) {
> + size_t Size = ToBeExploredPaths.size();
> +
> + // Still noreturn.
> + if (!explorePath(A, I))
> + continue;
> +
> + NoReturnCalls.remove(I);
> +
> + // No new paths.
> + if (Size == ToBeExploredPaths.size())
> + continue;
> +
> + // At least one new path.
> + Status = ChangeStatus::CHANGED;
> +
> + // explore new paths.
> + while (Size != ToBeExploredPaths.size())
> + explorePath(A, ToBeExploredPaths[Size++]);
> + }
> +
> + LLVM_DEBUG(dbgs() << "[AAIsDead] AssumedLiveBlocks: "
> + << AssumedLiveBlocks.size()
> + << "Total number of blocks: " << F.size() << "\n");
> +
> + return Status;
> +}
> +
> /// ----------------------------------------------------------------------------
> /// Attributor
> /// ----------------------------------------------------------------------------
> @@ -1627,6 +1798,9 @@ void Attributor::identifyDefaultAbstract
> // Every function might be "will-return".
> registerAA(*new AAWillReturnFunction(F, InfoCache));
>
> + // Check for dead BasicBlocks in every function.
> + registerAA(*new AAIsDeadFunction(F, InfoCache));
> +
> // Walk all instructions to find more attribute opportunities and also
> // interesting instructions that might be queried by abstract attributes
> // during their initialization or update.
>
> Modified: llvm/trunk/lib/Transforms/Utils/Local.cpp
> URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/lib/Transforms/Utils/Local.cpp?rev=366736&r1=366735&r2=366736&view=diff
> ==============================================================================
> --- llvm/trunk/lib/Transforms/Utils/Local.cpp (original)
> +++ llvm/trunk/lib/Transforms/Utils/Local.cpp Mon Jul 22 13:54:30 2019
> @@ -1964,7 +1964,7 @@ unsigned llvm::changeToUnreachable(Instr
> }
>
> /// changeToCall - Convert the specified invoke into a normal call.
> -static void changeToCall(InvokeInst *II, DomTreeUpdater *DTU = nullptr) {
> +void llvm::changeToCall(InvokeInst *II, DomTreeUpdater *DTU = nullptr) {
> SmallVector<Value*, 8> Args(II->arg_begin(), II->arg_end());
> SmallVector<OperandBundleDef, 1> OpBundles;
> II->getOperandBundlesAsDefs(OpBundles);
>
> Added: llvm/trunk/test/Transforms/FunctionAttrs/liveness.ll
> URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/test/Transforms/FunctionAttrs/liveness.ll?rev=366736&view=auto
> ==============================================================================
> --- llvm/trunk/test/Transforms/FunctionAttrs/liveness.ll (added)
> +++ llvm/trunk/test/Transforms/FunctionAttrs/liveness.ll Mon Jul 22 13:54:30 2019
> @@ -0,0 +1,250 @@
> +; RUN: opt -attributor --attributor-disable=false -S < %s | FileCheck %s
> +
> +declare void @no_return_call() noreturn
> +
> +declare void @normal_call()
> +
> +declare i32 @foo()
> +
> +declare i32 @foo_noreturn() noreturn
> +
> +declare i32 @bar()
> +
> +; TEST 1: cond.true is dead, but cond.end is not, since cond.false is live
> +
> +; This is just an example. For example we can put a sync call in a
> +; dead block and check if it is deduced.
> +
> +define i32 @dead_block_present(i32 %a) #0 {
> +entry:
> + %cmp = icmp eq i32 %a, 0
> + br i1 %cmp, label %cond.true, label %cond.false
> +
> +cond.true: ; preds = %entry
> + call void @no_return_call()
> + ; CHECK: call void @no_return_call()
> + ; CHECK-NEXT: unreachable
> + %call = call i32 @foo()
> + br label %cond.end
> +
> +cond.false: ; preds = %entry
> + call void @normal_call()
> + %call1 = call i32 @bar()
> + br label %cond.end
> +
> +cond.end: ; preds = %cond.false, %cond.true
> + %cond = phi i32 [ %call, %cond.true ], [ %call1, %cond.false ]
> + ret i32 %cond
> +}
> +
> +; TEST 2: both cond.true and cond.false are dead, therfore cond.end is dead as well.
> +
> +define i32 @all_dead(i32 %a) #0 {
> +entry:
> + %cmp = icmp eq i32 %a, 0
> + br i1 %cmp, label %cond.true, label %cond.false
> +
> +cond.true: ; preds = %entry
> + call void @no_return_call()
> + ; CHECK: call void @no_return_call()
> + ; CHECK-NEXT: unreachable
> + %call = call i32 @foo()
> + br label %cond.end
> +
> +cond.false: ; preds = %entry
> + call void @no_return_call()
> + ; CHECK: call void @no_return_call()
> + ; CHECK-NEXT: unreachable
> + %call1 = call i32 @bar()
> + br label %cond.end
> +
> +cond.end: ; preds = %cond.false, %cond.true
> + %cond = phi i32 [ %call, %cond.true ], [ %call1, %cond.false ]
> + ret i32 %cond
> +}
> +
> +declare i32 @__gxx_personality_v0(...)
> +
> +; TEST 3: All blocks are live.
> +
> +; CHECK: define i32 @all_live(i32 %a)
> +define i32 @all_live(i32 %a) #0 {
> +entry:
> + %cmp = icmp eq i32 %a, 0
> + br i1 %cmp, label %cond.true, label %cond.false
> +
> +cond.true: ; preds = %entry
> + call void @normal_call()
> + %call = call i32 @foo_noreturn()
> + br label %cond.end
> +
> +cond.false: ; preds = %entry
> + call void @normal_call()
> + %call1 = call i32 @bar()
> + br label %cond.end
> +
> +cond.end: ; preds = %cond.false, %cond.true
> + %cond = phi i32 [ %call, %cond.true ], [ %call1, %cond.false ]
> + ret i32 %cond
> +}
> +
> +; TEST 4 noreturn invoke instruction replaced by a call and an unreachable instruction
> +; put after it.
> +
> +; CHECK: define i32 @invoke_noreturn(i32 %a)
> +define i32 @invoke_noreturn(i32 %a) personality i8* bitcast (i32 (...)* @__gxx_personality_v0 to i8*) {
> +entry:
> + %cmp = icmp eq i32 %a, 0
> + br i1 %cmp, label %cond.true, label %cond.false
> +
> +cond.true: ; preds = %entry
> + call void @normal_call()
> + %call = invoke i32 @foo_noreturn() to label %continue
> + unwind label %cleanup
> + ; CHECK: call i32 @foo_noreturn()
> + ; CHECK-NEXT unreachable
> +
> +cond.false: ; preds = %entry
> + call void @normal_call()
> + %call1 = call i32 @bar()
> + br label %cond.end
> +
> +cond.end: ; preds = %cond.false, %continue
> + %cond = phi i32 [ %call, %continue ], [ %call1, %cond.false ]
> + ret i32 %cond
> +
> +continue:
> + br label %cond.end
> +
> +cleanup:
> + %res = landingpad { i8*, i32 }
> + catch i8* null
> + ret i32 0
> +}
> +
> +; TEST 5: Undefined behvior, taken from LangRef.
> +; FIXME: Should be able to detect undefined behavior.
> +
> +; CHECK define @ub(i32)
> +define void @ub(i32* ) {
> + %poison = sub nuw i32 0, 1 ; Results in a poison value.
> + %still_poison = and i32 %poison, 0 ; 0, but also poison.
> + %poison_yet_again = getelementptr i32, i32* %0, i32 %still_poison
> + store i32 0, i32* %poison_yet_again ; Undefined behavior due to store to poison.
> + ret void
> +}
> +
> +define void @inf_loop() #0 {
> +entry:
> + br label %while.body
> +
> +while.body: ; preds = %entry, %while.body
> + br label %while.body
> +}
> +
> +; TEST 6: Infinite loop.
> +; FIXME: Detect infloops, and mark affected blocks dead.
> +
> +define i32 @test5(i32, i32) #0 {
> + %3 = icmp sgt i32 %0, %1
> + br i1 %3, label %cond.if, label %cond.elseif
> +
> +cond.if: ; preds = %2
> + %4 = tail call i32 @bar()
> + br label %cond.end
> +
> +cond.elseif: ; preds = %2
> + call void @inf_loop()
> + %5 = icmp slt i32 %0, %1
> + br i1 %5, label %cond.end, label %cond.else
> +
> +cond.else: ; preds = %cond.elseif
> + %6 = tail call i32 @foo()
> + br label %cond.end
> +
> +cond.end: ; preds = %cond.if, %cond.else, %cond.elseif
> + %7 = phi i32 [ %1, %cond.elseif ], [ 0, %cond.else ], [ 0, %cond.if ]
> + ret i32 %7
> +}
> +
> +define void @rec() #0 {
> +entry:
> + call void @rec()
> + ret void
> +}
> +
> +; TEST 7: Recursion
> +; FIXME: everything after first block should be marked dead
> +; and unreachable should be put after call to @rec().
> +
> +define i32 @test6(i32, i32) #0 {
> + call void @rec()
> + %3 = icmp sgt i32 %0, %1
> + br i1 %3, label %cond.if, label %cond.elseif
> +
> +cond.if: ; preds = %2
> + %4 = tail call i32 @bar()
> + br label %cond.end
> +
> +cond.elseif: ; preds = %2
> + call void @rec()
> + %5 = icmp slt i32 %0, %1
> + br i1 %5, label %cond.end, label %cond.else
> +
> +cond.else: ; preds = %cond.elseif
> + %6 = tail call i32 @foo()
> + br label %cond.end
> +
> +cond.end: ; preds = %cond.if, %cond.else, %cond.elseif
> + %7 = phi i32 [ %1, %cond.elseif ], [ 0, %cond.else ], [ 0, %cond.if ]
> + ret i32 %7
> +}
> +; TEST 8: Recursion
> +; FIXME: contains recursive call to itself in cond.elseif block
> +
> +define i32 @test7(i32, i32) #0 {
> + %3 = icmp sgt i32 %0, %1
> + br i1 %3, label %cond.if, label %cond.elseif
> +
> +cond.if: ; preds = %2
> + %4 = tail call i32 @bar()
> + br label %cond.end
> +
> +cond.elseif: ; preds = %2
> + %5 = tail call i32 @test7(i32 %0, i32 %1)
> + %6 = icmp slt i32 %0, %1
> + br i1 %6, label %cond.end, label %cond.else
> +
> +cond.else: ; preds = %cond.elseif
> + %7 = tail call i32 @foo()
> + br label %cond.end
> +
> +cond.end: ; preds = %cond.if, %cond.else, %cond.elseif
> + %8 = phi i32 [ %1, %cond.elseif ], [ 0, %cond.else ], [ 0, %cond.if ]
> + ret i32 %8
> +}
> +
> +; TEST 9: Only first block is live.
> +
> +define i32 @first_block_no_return(i32 %a) #0 {
> +entry:
> + call void @no_return_call()
> + ; CHECK: call void @no_return_call()
> + ; CHECK-NEXT: unreachable
> + %cmp = icmp eq i32 %a, 0
> + br i1 %cmp, label %cond.true, label %cond.false
> +
> +cond.true: ; preds = %entry
> + call void @normal_call()
> + %call = call i32 @foo()
> + br label %cond.end
> +
> +cond.false: ; preds = %entry
> + call void @normal_call()
> + %call1 = call i32 @bar()
> + br label %cond.end
> +
> +cond.end: ; preds = %cond.false, %cond.true
> + %cond = phi i32 [ %call, %cond.true ], [ %call1, %cond.false ]
> + ret i32 %cond
> +}
>
>
> _______________________________________________
> llvm-commits mailing list
> llvm-commits at lists.llvm.org
> https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-commits
More information about the llvm-commits
mailing list