Commit 188f9a34 authored by Hideto Ueno's avatar Hideto Ueno
Browse files

[Attributor] AAValueConstantRange: Value range analysis using constant range

Summary:
This patch introduces `AAValueConstantRange`, which answers a possible range for integer value in a specific program point.
One of the motivations is propagating existing `range` metadata. (I think we need to change the situation that `range` metadata cannot be put to Argument).

The state is a tuple of `ConstantRange` and it is initialized to (known, assumed) = ([-∞, +∞], empty).

Currently, AAValueConstantRange is created in `getAssumedConstant` method when `AAValueSimplify` returns `nullptr`(worst state).

Supported
 - BinaryOperator(add, sub, ...)
 - CmpInst(icmp eq, ...)
 - !range metadata

`AAValueConstantRange` is not intended to extend to polyhedral range value analysis.

Reviewers: jdoerfert, sstefan1

Reviewed By: jdoerfert

Subscribers: phosek, davezarzycki, baziotis, hiraditya, javed.absar, llvm-commits

Tags: #llvm

Differential Revision: https://reviews.llvm.org/D71620
parent b891490c
Loading
Loading
Loading
Loading
+161 −0
Original line number Diff line number Diff line
@@ -105,6 +105,7 @@
#include "llvm/Analysis/MustExecute.h"
#include "llvm/Analysis/TargetLibraryInfo.h"
#include "llvm/IR/CallSite.h"
#include "llvm/IR/ConstantRange.h"
#include "llvm/IR/PassManager.h"

namespace llvm {
@@ -1503,6 +1504,116 @@ private:
  }
};

/// State for an integer range.
struct IntegerRangeState : public AbstractState {

  /// Bitwidth of the associated value.
  uint32_t BitWidth;

  /// State representing assumed range, initially set to empty.
  ConstantRange Assumed;

  /// State representing known range, initially set to [-inf, inf].
  ConstantRange Known;

  IntegerRangeState(uint32_t BitWidth)
      : BitWidth(BitWidth), Assumed(ConstantRange::getEmpty(BitWidth)),
        Known(ConstantRange::getFull(BitWidth)) {}

  /// Return the worst possible representable state.
  static ConstantRange getWorstState(uint32_t BitWidth) {
    return ConstantRange::getFull(BitWidth);
  }

  /// Return the best possible representable state.
  static ConstantRange getBestState(uint32_t BitWidth) {
    return ConstantRange::getEmpty(BitWidth);
  }

  /// Return associated values' bit width.
  uint32_t getBitWidth() const { return BitWidth; }

  /// See AbstractState::isValidState()
  bool isValidState() const override {
    return BitWidth > 0 && !Assumed.isFullSet();
  }

  /// See AbstractState::isAtFixpoint()
  bool isAtFixpoint() const override { return Assumed == Known; }

  /// See AbstractState::indicateOptimisticFixpoint(...)
  ChangeStatus indicateOptimisticFixpoint() override {
    Known = Assumed;
    return ChangeStatus::CHANGED;
  }

  /// See AbstractState::indicatePessimisticFixpoint(...)
  ChangeStatus indicatePessimisticFixpoint() override {
    Assumed = Known;
    return ChangeStatus::CHANGED;
  }

  /// Return the known state encoding
  ConstantRange getKnown() const { return Known; }

  /// Return the assumed state encoding.
  ConstantRange getAssumed() const { return Assumed; }

  /// Unite assumed range with the passed state.
  void unionAssumed(const ConstantRange &R) {
    // Don't loose a known range.
    Assumed = Assumed.unionWith(R).intersectWith(Known);
  }

  /// See IntegerRangeState::unionAssumed(..).
  void unionAssumed(const IntegerRangeState &R) {
    unionAssumed(R.getAssumed());
  }

  /// Unite known range with the passed state.
  void unionKnown(const ConstantRange &R) {
    // Don't loose a known range.
    Known = Known.unionWith(R);
    Assumed = Assumed.unionWith(Known);
  }

  /// See IntegerRangeState::unionKnown(..).
  void unionKnown(const IntegerRangeState &R) { unionKnown(R.getKnown()); }

  /// Intersect known range with the passed state.
  void intersectKnown(const ConstantRange &R) {
    Assumed = Assumed.intersectWith(R);
    Known = Known.intersectWith(R);
  }

  /// See IntegerRangeState::intersectKnown(..).
  void intersectKnown(const IntegerRangeState &R) {
    intersectKnown(R.getKnown());
  }

  /// Equality for IntegerRangeState.
  bool operator==(const IntegerRangeState &R) const {
    return getAssumed() == R.getAssumed() && getKnown() == R.getKnown();
  }

  /// "Clamp" this state with \p R. The result is subtype dependent but it is
  /// intended that only information assumed in both states will be assumed in
  /// this one afterwards.
  IntegerRangeState operator^=(const IntegerRangeState &R) {
    // NOTE: `^=` operator seems like `intersect` but in this case, we need to
    // take `union`.
    unionAssumed(R);
    return *this;
  }

  IntegerRangeState operator&=(const IntegerRangeState &R) {
    // NOTE: `&=` operator seems like `intersect` but in this case, we need to
    // take `union`.
    unionKnown(R);
    unionAssumed(R);
    return *this;
  }
};
/// Helper struct necessary as the modular build fails if the virtual method
/// IRAttribute::manifest is defined in the Attributor.cpp.
struct IRAttributeManifest {
@@ -1696,6 +1807,7 @@ template <typename base_ty, base_ty BestState, base_ty WorstState>
raw_ostream &
operator<<(raw_ostream &OS,
           const IntegerStateBase<base_ty, BestState, WorstState> &State);
raw_ostream &operator<<(raw_ostream &OS, const IntegerRangeState &State);
///}

struct AttributorPass : public PassInfoMixin<AttributorPass> {
@@ -2331,6 +2443,55 @@ struct AAMemoryBehavior
  static const char ID;
};

/// An abstract interface for range value analysis.
struct AAValueConstantRange : public IntegerRangeState,
                              public AbstractAttribute,
                              public IRPosition {
  AAValueConstantRange(const IRPosition &IRP)
      : IntegerRangeState(
            IRP.getAssociatedValue().getType()->getIntegerBitWidth()),
        IRPosition(IRP) {}

  /// Return an IR position, see struct IRPosition.
  const IRPosition &getIRPosition() const override { return *this; }

  /// See AbstractAttribute::getState(...).
  IntegerRangeState &getState() override { return *this; }
  const AbstractState &getState() const override { return *this; }

  /// Create an abstract attribute view for the position \p IRP.
  static AAValueConstantRange &createForPosition(const IRPosition &IRP,
                                                 Attributor &A);

  /// Return an assumed range for the assocaited value a program point \p CtxI.
  /// If \p I is nullptr, simply return an assumed range.
  virtual ConstantRange
  getAssumedConstantRange(Attributor &A,
                          const Instruction *CtxI = nullptr) const = 0;

  /// Return a known range for the assocaited value at a program point \p CtxI.
  /// If \p I is nullptr, simply return a known range.
  virtual ConstantRange
  getKnownConstantRange(Attributor &A,
                        const Instruction *CtxI = nullptr) const = 0;

  /// Return an assumed constant for the assocaited value a program point \p
  /// CtxI.
  Optional<ConstantInt *>
  getAssumedConstantInt(Attributor &A, const Instruction *CtxI = nullptr) const {
    ConstantRange RangeV = getAssumedConstantRange(A, CtxI);
    if (auto *C = RangeV.getSingleElement())
      return cast<ConstantInt>(
          ConstantInt::get(getAssociatedValue().getType(), *C));
    if (RangeV.isEmptySet())
      return llvm::None;
    return nullptr;
  }

  /// Unique ID (due to the unique address)
  static const char ID;
};

} // end namespace llvm

#endif // LLVM_TRANSFORMS_IPO_FUNCTIONATTRS_H
+499 −7

File changed.

Preview size limit exceeded, changes collapsed.

+2 −4
Original line number Diff line number Diff line
@@ -9,8 +9,7 @@ define i1 @invokecaller(i1 %C) personality i32 (...)* @__gxx_personality_v0 {
; CHECK-NEXT:    [[X:%.*]] = call i32 @foo(i1 [[C]])
; CHECK-NEXT:    br label [[OK:%.*]]
; CHECK:       OK:
; CHECK-NEXT:    [[Y:%.*]] = icmp ne i32 52, 0
; CHECK-NEXT:    ret i1 [[Y]]
; CHECK-NEXT:    ret i1 true
; CHECK:       FAIL:
; CHECK-NEXT:    unreachable
;
@@ -46,8 +45,7 @@ define i1 @caller(i1 %C) {
; CHECK-LABEL: define {{[^@]+}}@caller
; CHECK-SAME: (i1 [[C:%.*]])
; CHECK-NEXT:    [[X:%.*]] = call i32 @foo(i1 [[C]])
; CHECK-NEXT:    [[Y:%.*]] = icmp ne i32 52, 0
; CHECK-NEXT:    ret i1 [[Y]]
; CHECK-NEXT:    ret i1 true
;
  %X = call i32 @foo( i1 %C )             ; <i32> [#uses=1]
  %Y = icmp ne i32 %X, 0          ; <i1> [#uses=1]
+3 −4
Original line number Diff line number Diff line
@@ -33,12 +33,11 @@ define internal i32 @test1(i1 %c) {
; CHECK-NEXT:    br label [[IF_THEN:%.*]]
; CHECK:       if.then:
; CHECK-NEXT:    [[CALL:%.*]] = call i32 @testf(i1 [[C]])
; CHECK-NEXT:    [[RES:%.*]] = icmp eq i32 10, 10
; CHECK-NEXT:    br i1 [[RES]], label [[RET1:%.*]], label [[RET2:%.*]]
; CHECK-NEXT:    br label [[RET1:%.*]]
; CHECK:       ret1:
; CHECK-NEXT:    ret i32 99
; CHECK:       ret2:
; CHECK-NEXT:    ret i32 0
; CHECK-NEXT:    unreachable
;
entry:
  br label %if.then
@@ -59,7 +58,7 @@ define i32 @main(i1 %c) {
; CHECK-LABEL: define {{[^@]+}}@main
; CHECK-SAME: (i1 [[C:%.*]])
; CHECK-NEXT:    [[RES:%.*]] = call i32 @test1(i1 [[C]])
; CHECK-NEXT:    ret i32 [[RES]]
; CHECK-NEXT:    ret i32 99
;
  %res = call i32 @test1(i1 %c)
  ret i32 %res
+104 −1
Original line number Diff line number Diff line
; RUN: opt -attributor -attributor-manifest-internal --attributor-disable=false -attributor-max-iterations-verify -attributor-annotate-decl-cs -attributor-max-iterations=2 -S < %s | FileCheck %s --check-prefix=ATTRIBUTOR
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
; RUN: opt -attributor -attributor-manifest-internal --attributor-disable=false -attributor-max-iterations-verify -attributor-annotate-decl-cs -attributor-max-iterations=4 -S < %s | FileCheck %s --check-prefix=ATTRIBUTOR


declare void @deref_phi_user(i32* %a);
@@ -207,3 +208,105 @@ define void @deref_or_null_and_nonnull(i32* dereferenceable_or_null(100) %0) {
  store i32 1, i32* %0
  ret void
}

; TEST 8
; Use Constant range in deereferenceable
; void g(int *p, long long int *range){
;   int r = *range ; // [10, 99]
;   fill_range(p, *range);
; }

; void fill_range(int* p, long long int start){
;   for(long long int i = start;i<start+10;i++){
;     // If p[i] is inbounds, p is dereferenceable(40) at least.
;     p[i] = i;
;   }
; }

define internal void @fill_range_not_inbounds(i32* %p, i64 %start){
; ATTRIBUTOR-LABEL: define {{[^@]+}}@fill_range_not_inbounds
; NOTE: %p should not be dereferenceable
; ATTRIBUTOR-SAME: (i32* nocapture nofree writeonly [[P:%.*]], i64 [[START:%.*]])
; ATTRIBUTOR-NEXT:  entry:
; ATTRIBUTOR-NEXT:    [[TMP0:%.*]] = add nsw i64 [[START:%.*]], 9
; ATTRIBUTOR-NEXT:    br label [[FOR_BODY:%.*]]
; ATTRIBUTOR:       for.cond.cleanup:
; ATTRIBUTOR-NEXT:    ret void
; ATTRIBUTOR:       for.body:
; ATTRIBUTOR-NEXT:    [[I_06:%.*]] = phi i64 [ [[START]], [[ENTRY:%.*]] ], [ [[INC:%.*]], [[FOR_BODY]] ]
; ATTRIBUTOR-NEXT:    [[CONV:%.*]] = trunc i64 [[I_06]] to i32
; ATTRIBUTOR-NEXT:    [[ARRAYIDX:%.*]] = getelementptr i32, i32* [[P:%.*]], i64 [[I_06]]
; ATTRIBUTOR-NEXT:    store i32 [[CONV]], i32* [[ARRAYIDX]], align 4
; ATTRIBUTOR-NEXT:    [[INC]] = add nsw i64 [[I_06]], 1
; ATTRIBUTOR-NEXT:    [[CMP:%.*]] = icmp slt i64 [[I_06]], [[TMP0]]
; ATTRIBUTOR-NEXT:    br i1 [[CMP]], label [[FOR_BODY]], label [[FOR_COND_CLEANUP:%.*]]
;
entry:
  %0 = add nsw i64 %start, 9
  br label %for.body

for.cond.cleanup:                                 ; preds = %for.body
  ret void

for.body:                                         ; preds = %entry, %for.body
  %i.06 = phi i64 [ %start, %entry ], [ %inc, %for.body ]
  %conv = trunc i64 %i.06 to i32
  %arrayidx = getelementptr i32, i32* %p, i64 %i.06
  store i32 %conv, i32* %arrayidx, align 4
  %inc = add nsw i64 %i.06, 1
  %cmp = icmp slt i64 %i.06, %0
  br i1 %cmp, label %for.body, label %for.cond.cleanup
}
define internal void @fill_range_inbounds(i32* %p, i64 %start){
; ATTRIBUTOR-LABEL: define {{[^@]+}}@fill_range_inbounds
; FIXME: %p should be dereferenceable(40)
; ATTRIBUTOR-SAME: (i32* nocapture nofree writeonly [[P:%.*]], i64 [[START:%.*]])
; ATTRIBUTOR-NEXT:  entry:
; ATTRIBUTOR-NEXT:    [[TMP0:%.*]] = add nsw i64 [[START:%.*]], 9
; ATTRIBUTOR-NEXT:    br label [[FOR_BODY:%.*]]
; ATTRIBUTOR:       for.cond.cleanup:
; ATTRIBUTOR-NEXT:    ret void
; ATTRIBUTOR:       for.body:
; ATTRIBUTOR-NEXT:    [[I_06:%.*]] = phi i64 [ [[START]], [[ENTRY:%.*]] ], [ [[INC:%.*]], [[FOR_BODY]] ]
; ATTRIBUTOR-NEXT:    [[CONV:%.*]] = trunc i64 [[I_06]] to i32
; ATTRIBUTOR-NEXT:    [[ARRAYIDX:%.*]] = getelementptr inbounds i32, i32* [[P:%.*]], i64 [[I_06]]
; ATTRIBUTOR-NEXT:    store i32 [[CONV]], i32* [[ARRAYIDX]], align 4
; ATTRIBUTOR-NEXT:    [[INC]] = add nsw i64 [[I_06]], 1
; ATTRIBUTOR-NEXT:    [[CMP:%.*]] = icmp slt i64 [[I_06]], [[TMP0]]
; ATTRIBUTOR-NEXT:    br i1 [[CMP]], label [[FOR_BODY]], label [[FOR_COND_CLEANUP:%.*]]
;
entry:
  %0 = add nsw i64 %start, 9
  br label %for.body

for.cond.cleanup:                                 ; preds = %for.body
  ret void

for.body:                                         ; preds = %entry, %for.body
  %i.06 = phi i64 [ %start, %entry ], [ %inc, %for.body ]
  %conv = trunc i64 %i.06 to i32
  %arrayidx = getelementptr inbounds i32, i32* %p, i64 %i.06
  store i32 %conv, i32* %arrayidx, align 4
  %inc = add nsw i64 %i.06, 1
  %cmp = icmp slt i64 %i.06, %0
  br i1 %cmp, label %for.body, label %for.cond.cleanup
}

define void @call_fill_range(i32* nocapture %p, i64* nocapture readonly %range) {
; ATTRIBUTOR-LABEL: define {{[^@]+}}@call_fill_range
; ATTRIBUTOR-SAME: (i32* nocapture nofree writeonly [[P:%.*]], i64* nocapture nofree nonnull readonly align 8 dereferenceable(8) [[RANGE:%.*]])
; ATTRIBUTOR-NEXT:  entry:
; ATTRIBUTOR-NEXT:    [[TMP0:%.*]] = load i64, i64* [[RANGE:%.*]], align 8, !range !0
; ATTRIBUTOR-NEXT:    tail call void @fill_range_inbounds(i32* nocapture nofree writeonly [[P:%.*]], i64 [[TMP0]])
; ATTRIBUTOR-NEXT:    tail call void @fill_range_not_inbounds(i32* nocapture nofree writeonly [[P]], i64 [[TMP0]])
; ATTRIBUTOR-NEXT:    ret void
;
entry:
  %0 = load i64, i64* %range, align 8, !range !0
  tail call void @fill_range_inbounds(i32* %p, i64 %0)
  tail call void @fill_range_not_inbounds(i32* %p, i64 %0)
  ret void
}

!0 = !{i64 10, i64 100}
Loading