Commit 86509e8c authored by Johannes Doerfert's avatar Johannes Doerfert
Browse files

[Attributor] Use assumed information to determine side-effects

We relied on wouldInstructionBeTriviallyDead before but that functions
does not take assumed information, especially for calls, into account.
The replacement, AAIsDead::isAssumeSideEffectFree, does.

This change makes AAIsDeadCallSiteReturn more complex as we can have
a dead call or only dead users.

The test have been modified to include a side effect where there was
none in order to keep the coverage.

Reviewed By: sstefan1

Differential Revision: https://reviews.llvm.org/D73311
parent 190a1114
Loading
Loading
Loading
Loading
+129 −36
Original line number Diff line number Diff line
@@ -2820,29 +2820,16 @@ struct AAIsDeadValueImpl : public AAIsDead {

  /// See AAIsDead::isKnownDead(Instruction *I).
  bool isKnownDead(const Instruction *I) const override {
    return I == getCtxI() && getKnown();
    return isAssumedDead(I) && getKnown();
  }

  /// See AbstractAttribute::getAsStr().
  const std::string getAsStr() const override {
    return isAssumedDead() ? "assumed-dead" : "assumed-live";
  }
};

struct AAIsDeadFloating : public AAIsDeadValueImpl {
  AAIsDeadFloating(const IRPosition &IRP) : AAIsDeadValueImpl(IRP) {}

  /// See AbstractAttribute::initialize(...).
  void initialize(Attributor &A) override {
    if (Instruction *I = dyn_cast<Instruction>(&getAssociatedValue()))
      if (!wouldInstructionBeTriviallyDead(I))
        indicatePessimisticFixpoint();
    if (isa<UndefValue>(getAssociatedValue()))
      indicatePessimisticFixpoint();
  }

  /// See AbstractAttribute::updateImpl(...).
  ChangeStatus updateImpl(Attributor &A) override {
  /// Check if all uses are assumed dead.
  bool areAllUsesAssumedDead(Attributor &A) {
    auto UsePred = [&](const Use &U, bool &Follow) {
      Instruction *UserI = cast<Instruction>(U.getUser());
      if (CallSite CS = CallSite(UserI)) {
@@ -2862,7 +2849,53 @@ struct AAIsDeadFloating : public AAIsDeadValueImpl {
      return wouldInstructionBeTriviallyDead(UserI);
    };

    if (!A.checkForAllUses(UsePred, *this, getAssociatedValue()))
    return A.checkForAllUses(UsePred, *this, getAssociatedValue());
  }

  /// Determine if \p I is assumed to be side-effect free.
  bool isAssumedSideEffectFree(Attributor &A, Instruction *I) {
    if (!I || wouldInstructionBeTriviallyDead(I))
      return true;

    auto *CB = dyn_cast<CallBase>(I);
    if (!CB || isa<IntrinsicInst>(CB))
      return false;

    const IRPosition &CallIRP = IRPosition::callsite_function(*CB);
    const auto &NoUnwindAA = A.getAAFor<AANoUnwind>(*this, CallIRP);
    if (!NoUnwindAA.isAssumedNoUnwind())
      return false;

    const auto &MemBehaviorAA = A.getAAFor<AAMemoryBehavior>(*this, CallIRP);
    if (!MemBehaviorAA.isAssumedReadOnly())
      return false;

    return true;
  }
};

struct AAIsDeadFloating : public AAIsDeadValueImpl {
  AAIsDeadFloating(const IRPosition &IRP) : AAIsDeadValueImpl(IRP) {}

  /// See AbstractAttribute::initialize(...).
  void initialize(Attributor &A) override {
    if (isa<UndefValue>(getAssociatedValue())) {
      indicatePessimisticFixpoint();
      return;
    }

    Instruction *I = dyn_cast<Instruction>(&getAssociatedValue());
    if (!isAssumedSideEffectFree(A, I))
      indicatePessimisticFixpoint();
  }

  /// See AbstractAttribute::updateImpl(...).
  ChangeStatus updateImpl(Attributor &A) override {
    Instruction *I = dyn_cast<Instruction>(&getAssociatedValue());
    if (!isAssumedSideEffectFree(A, I))
      return indicatePessimisticFixpoint();

    if (!areAllUsesAssumedDead(A))
      return indicatePessimisticFixpoint();
    return ChangeStatus::UNCHANGED;
  }
@@ -2870,12 +2903,16 @@ struct AAIsDeadFloating : public AAIsDeadValueImpl {
  /// See AbstractAttribute::manifest(...).
  ChangeStatus manifest(Attributor &A) override {
    Value &V = getAssociatedValue();
    if (auto *I = dyn_cast<Instruction>(&V))
      if (wouldInstructionBeTriviallyDead(I)) {
    if (auto *I = dyn_cast<Instruction>(&V)) {
      // 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
      // still needed.
      if (isAssumedSideEffectFree(A, I) && !isa<InvokeInst>(I)) {
        A.deleteAfterManifest(*I);
        return ChangeStatus::CHANGED;
      }

    }
    if (V.use_empty())
      return ChangeStatus::UNCHANGED;

@@ -2956,6 +2993,69 @@ struct AAIsDeadCallSiteArgument : public AAIsDeadValueImpl {
  void trackStatistics() const override { STATS_DECLTRACK_CSARG_ATTR(IsDead) }
};

struct AAIsDeadCallSiteReturned : public AAIsDeadFloating {
  AAIsDeadCallSiteReturned(const IRPosition &IRP)
      : AAIsDeadFloating(IRP), IsAssumedSideEffectFree(true) {}

  /// See AAIsDead::isAssumedDead().
  bool isAssumedDead() const override {
    return AAIsDeadFloating::isAssumedDead() && IsAssumedSideEffectFree;
  }

  /// Return true if all users are assumed dead.
  bool hasOnlyAssumedDeadUses() const { return getAssumed(); }

  /// See AbstractAttribute::initialize(...).
  void initialize(Attributor &A) override {
    if (isa<UndefValue>(getAssociatedValue())) {
      indicatePessimisticFixpoint();
      return;
    }

    // We track this separately as a secondary state.
    IsAssumedSideEffectFree = isAssumedSideEffectFree(A, getCtxI());
  }

  /// See AbstractAttribute::updateImpl(...).
  ChangeStatus updateImpl(Attributor &A) override {
    ChangeStatus Changed = ChangeStatus::UNCHANGED;
    if (IsAssumedSideEffectFree && !isAssumedSideEffectFree(A, getCtxI())) {
      IsAssumedSideEffectFree = false;
      Changed = ChangeStatus::CHANGED;
    }

    if (!areAllUsesAssumedDead(A))
      return indicatePessimisticFixpoint();
    return Changed;
  }

  /// See AbstractAttribute::manifest(...).
  ChangeStatus manifest(Attributor &A) override {
    if (auto *CI = dyn_cast<CallInst>(&getAssociatedValue()))
      if (CI->isMustTailCall())
        return ChangeStatus::UNCHANGED;
    return AAIsDeadFloating::manifest(A);
  }

  /// See AbstractAttribute::trackStatistics()
  void trackStatistics() const override {
    if (IsAssumedSideEffectFree)
      STATS_DECLTRACK_CSRET_ATTR(IsDead)
    else
      STATS_DECLTRACK_CSRET_ATTR(UnusedResult)
  }

  /// See AbstractAttribute::getAsStr().
  const std::string getAsStr() const override {
    return isAssumedDead()
               ? "assumed-dead"
               : (getAssumed() ? "assumed-dead-users" : "assumed-live");
  }

private:
  bool IsAssumedSideEffectFree;
};

struct AAIsDeadReturned : public AAIsDeadValueImpl {
  AAIsDeadReturned(const IRPosition &IRP) : AAIsDeadValueImpl(IRP) {}

@@ -2970,7 +3070,8 @@ struct AAIsDeadReturned : public AAIsDeadValueImpl {
          IRPosition::callsite_returned(ACS.getCallSite());
      const auto &RetIsDeadAA = A.getAAFor<AAIsDead>(*this, CSRetPos);
      AllKnownDead &= RetIsDeadAA.isKnownDead();
      return RetIsDeadAA.isAssumedDead();
      return static_cast<const AAIsDeadCallSiteReturned &>(RetIsDeadAA)
          .hasOnlyAssumedDeadUses();
    };

    bool AllCallSitesKnown;
@@ -3003,16 +3104,6 @@ struct AAIsDeadReturned : public AAIsDeadValueImpl {
  void trackStatistics() const override { STATS_DECLTRACK_FNRET_ATTR(IsDead) }
};

struct AAIsDeadCallSiteReturned : public AAIsDeadFloating {
  AAIsDeadCallSiteReturned(const IRPosition &IRP) : AAIsDeadFloating(IRP) {}

  /// See AbstractAttribute::initialize(...).
  void initialize(Attributor &A) override {}

  /// See AbstractAttribute::trackStatistics()
  void trackStatistics() const override { STATS_DECLTRACK_CSRET_ATTR(IsDead) }
};

struct AAIsDeadFunction : public AAIsDead {
  AAIsDeadFunction(const IRPosition &IRP) : AAIsDead(IRP) {}

@@ -7530,6 +7621,12 @@ void Attributor::identifyDefaultAbstractAttributes(Function &F) {

  auto CallSitePred = [&](Instruction &I) -> bool {
    CallSite CS(&I);
    IRPosition CSRetPos = IRPosition::callsite_returned(CS);

    // Call sites might be dead if they do not have side effects and no live
    // users. The return value might be dead if there are no live users.
    getOrCreateAAFor<AAIsDead>(CSRetPos);

    if (Function *Callee = CS.getCalledFunction()) {
      // Skip declerations except if annotations on their call sites were
      // explicitly requested.
@@ -7541,14 +7638,10 @@ void Attributor::identifyDefaultAbstractAttributes(Function &F) {

        IRPosition CSRetPos = IRPosition::callsite_returned(CS);

        // Call site return values might be dead.
        getOrCreateAAFor<AAIsDead>(CSRetPos);

        // Call site return integer values might be limited by a constant range.
        if (Callee->getReturnType()->isIntegerTy()) {
        if (Callee->getReturnType()->isIntegerTy())
          getOrCreateAAFor<AAValueConstantRange>(CSRetPos);
      }
      }

      for (int i = 0, e = CS.getNumArgOperands(); i < e; i++) {

+2 −0
Original line number Diff line number Diff line
@@ -21,8 +21,10 @@ define internal void @test(i32** %X) !dbg !2 {

define internal void @test_byval(%struct.pair* byval %P) {
; CHECK-LABEL: define {{[^@]+}}@test_byval()
; CHECK-NEXT:    call void @sink(i32 0)
; CHECK-NEXT:    ret void
;
  call void @sink(i32 0)
  ret void
}

+4 −0
Original line number Diff line number Diff line
@@ -31,8 +31,12 @@ define internal void @callee_t0f(i8* nocapture readnone %tp13, i8* nocapture rea
; CHECK-LABEL: define {{[^@]+}}@callee_t0f
; CHECK-SAME: (i8* noalias nocapture nofree nonnull readnone [[TP13:%.*]], i8* noalias nocapture nofree nonnull readnone [[TP14:%.*]], i8* noalias nocapture nofree nonnull readnone [[TP15:%.*]], i8* noalias nocapture nofree nonnull readnone [[TP16:%.*]], i8* noalias nocapture nofree nonnull readnone [[TP17:%.*]], ...)
; CHECK-NEXT:  entry:
; CHECK-NEXT:    call void @sink(i32 0)
; CHECK-NEXT:    ret void
;
entry:
  call void @sink(i32 0)
  ret void
}

declare void @sink(i32)
+0 −3
Original line number Diff line number Diff line
@@ -60,7 +60,6 @@ define internal i32 @cb2(i32 %unknown) {
; CHECK-LABEL: define {{[^@]+}}@cb2
; CHECK-SAME: (i32 returned [[UNKNOWN:%.*]])
; CHECK-NEXT:  entry:
; CHECK-NEXT:    [[CALL:%.*]] = call i32 @cb0(i32 0)
; CHECK-NEXT:    ret i32 [[UNKNOWN]]
;
entry:
@@ -91,8 +90,6 @@ entry:
define void @foo() {
; CHECK-LABEL: define {{[^@]+}}@foo()
; CHECK-NEXT:  entry:
; CHECK-NEXT:    [[CALL:%.*]] = call i32 @cb0(i32 0)
; CHECK-NEXT:    [[CALL1:%.*]] = call i32 @cb3(i32 1)
; CHECK-NEXT:    call void @broker(i32 (i32)* nonnull @cb0, i32 (i32)* nonnull @cb1, i32 (i32)* nonnull @cb0, i32 0, i32 1)
; CHECK-NEXT:    call void @broker(i32 (i32)* nonnull @cb1, i32 (i32)* nonnull @cb2, i32 (i32)* nonnull @cb2, i32 0, i32 1)
; CHECK-NEXT:    call void @broker(i32 (i32)* nonnull @cb3, i32 (i32)* nonnull @cb2, i32 (i32)* nonnull @cb3, i32 0, i32 1)
+13 −13
Original line number Diff line number Diff line
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --function-signature --scrub-attributes --turn off
; RUN: opt -attributor -attributor-manifest-internal -attributor-disable=false -attributor-max-iterations-verify -attributor-annotate-decl-cs -attributor-max-iterations=5 -S < %s | FileCheck %s --check-prefixes=ATTRIBUTOR,ATTRIBUTOR_MODULE
; RUN: opt -attributor-cgscc -attributor-manifest-internal -attributor-disable=false -attributor-annotate-decl-cs -attributor-max-iterations=5 -S < %s | FileCheck %s --check-prefixes=ATTRIBUTOR,ATTRIBUTOR_CGSCC
; RUN: opt -passes=attributor -attributor-manifest-internal -attributor-disable=false -attributor-max-iterations-verify -attributor-annotate-decl-cs -attributor-max-iterations=5 -S < %s | FileCheck %s --check-prefixes=ATTRIBUTOR,ATTRIBUTOR_MODULE
; RUN: opt -passes=attributor-cgscc -attributor-manifest-internal -attributor-disable=false -attributor-annotate-decl-cs -attributor-max-iterations=5 -S < %s | FileCheck %s --check-prefixes=ATTRIBUTOR,ATTRIBUTOR_CGSCC
; RUN: opt -attributor -attributor-manifest-internal -attributor-disable=false -attributor-max-iterations-verify -attributor-annotate-decl-cs -attributor-max-iterations=6 -S < %s | FileCheck %s --check-prefixes=ATTRIBUTOR,ATTRIBUTOR_MODULE
; RUN: opt -attributor-cgscc -attributor-manifest-internal -attributor-disable=false -attributor-annotate-decl-cs -attributor-max-iterations=6 -S < %s | FileCheck %s --check-prefixes=ATTRIBUTOR,ATTRIBUTOR_CGSCC
; RUN: opt -passes=attributor -attributor-manifest-internal -attributor-disable=false -attributor-max-iterations-verify -attributor-annotate-decl-cs -attributor-max-iterations=6 -S < %s | FileCheck %s --check-prefixes=ATTRIBUTOR,ATTRIBUTOR_MODULE
; RUN: opt -passes=attributor-cgscc -attributor-manifest-internal -attributor-disable=false -attributor-annotate-decl-cs -attributor-max-iterations=6 -S < %s | FileCheck %s --check-prefixes=ATTRIBUTOR,ATTRIBUTOR_CGSCC

target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"

@@ -137,18 +137,18 @@ define internal i8* @f3(i8* readnone %0) local_unnamed_addr #0 {

; TEST 7
; Better than IR information
define align 4 i32* @test7(i32* align 32 %p) #0 {
define align 4 i8* @test7() #0 {
; ATTRIBUTOR_MODULE-LABEL: define {{[^@]+}}@test7
; ATTRIBUTOR_MODULE-SAME: (i32* nofree readnone returned align 32 "no-capture-maybe-returned" [[P:%.*]])
; ATTRIBUTOR_MODULE-NEXT:    ret i32* [[P]]
; ATTRIBUTOR_MODULE-NEXT:    [[C:%.*]] = tail call i8* @f1(i8* noalias nofree nonnull readnone align 8 dereferenceable(1) @a1)
; ATTRIBUTOR_MODULE-NEXT:    ret i8* [[C]]
;
; ATTRIBUTOR_CGSCC-LABEL: define {{[^@]+}}@test7
; ATTRIBUTOR_CGSCC-SAME: (i32* nofree readnone returned align 32 "no-capture-maybe-returned" [[P:%.*]])
; ATTRIBUTOR_CGSCC-NEXT:    [[TMP1:%.*]] = tail call i8* @f1(i8* noalias nofree nonnull readnone align 8 dereferenceable(1) @a1)
; ATTRIBUTOR_CGSCC-NEXT:    ret i32* [[P]]
; ATTRIBUTOR_CGSCC-SAME: ()
; ATTRIBUTOR_CGSCC-NEXT:    [[C:%.*]] = tail call nonnull align 8 dereferenceable(1) i8* @f1(i8* noalias nofree nonnull readnone align 8 dereferenceable(1) @a1)
; ATTRIBUTOR_CGSCC-NEXT:    ret i8* [[C]]
;
  tail call i8* @f1(i8* align 8 dereferenceable(1) @a1)
  ret i32* %p
  %c = tail call i8* @f1(i8* align 8 dereferenceable(1) @a1)
  ret i8* %c
}

; TEST 7b
@@ -281,7 +281,7 @@ define void @test8_helper() {
  ret void
}

declare void @user_i32_ptr(i32*) readnone nounwind
declare void @user_i32_ptr(i32* nocapture readnone) nounwind
define internal void @test8(i32* %a, i32* %b, i32* %c) {
; ATTRIBUTOR_MODULE: define internal void @test8(i32* noalias nocapture readnone align 4 %a, i32* noalias nocapture readnone align 4 %b, i32* noalias nocapture readnone %c)
; ATTRIBUTOR_CGSCC: define internal void @test8(i32* nocapture readnone align 4 %a, i32* nocapture readnone align 4 %b, i32* nocapture readnone %c)
Loading