Skip to content

Commit

Permalink
[LVI] Introduce an intersect operation on lattice values
Browse files Browse the repository at this point in the history
LVI has several separate sources of facts - edge local conditions, recursive queries, assumes, and control independent value facts - which all apply to the same value at the same location. The existing implementation was very conservative about exploiting all of these facts at once.

This change introduces an "intersect" function specifically to abstract the action of picking a good set of facts from all of the separate facts given. At the moment, this function is relatively simple (i.e. mostly just reuses the bits which were already there), but even the minor additions reveal the inherent power. For example, JumpThreading is now capable of doing an inductive proof that a particular value is always positive and removing a half range check.

I'm currently only using the new intersect function in one place. If folks are happy with the direction of the work, I plan on making a series of small changes without review to replace mergeIn with intersect at all the appropriate places.

Differential Revision: http://reviews.llvm.org/D14476

llvm-svn: 259461
  • Loading branch information
preames committed Feb 2, 2016
1 parent 909cc68 commit 44456b8
Show file tree
Hide file tree
Showing 3 changed files with 160 additions and 32 deletions.
117 changes: 85 additions & 32 deletions llvm/lib/Analysis/LazyValueInfo.cpp
Expand Up @@ -354,6 +354,8 @@ namespace {
if (!BlockValueSet.insert(BV).second)
return false; // It's already in the stack.

DEBUG(dbgs() << "PUSH: " << *BV.second << " in " << BV.first->getName()
<< "\n");
BlockValueStack.push(BV);
return true;
}
Expand Down Expand Up @@ -510,6 +512,9 @@ void LazyValueInfoCache::solve() {
assert(hasCachedValueInfo(e.second, e.first) &&
"Result should be in cache!");

DEBUG(dbgs() << "POP " << *e.second << " in " << e.first->getName()
<< " = " << lookup(e.second)[e.first] << "\n");

BlockValueStack.pop();
BlockValueSet.erase(e);
} else {
Expand Down Expand Up @@ -1040,6 +1045,69 @@ static bool getEdgeValueLocal(Value *Val, BasicBlock *BBFrom,
return false;
}

/// Returns true if this lattice value represents at most one possible value.
/// This is as precise as any lattice value can get while still representing
/// reachable code.
static bool hasSingleValue(LVILatticeVal Val) {
if (Val.isConstantRange() &&
Val.getConstantRange().isSingleElement())
// Integer constants are single element ranges
return true;
if (Val.isConstant())
// Non integer constants
return true;
return false;
}

/// Combine two sets of facts about the same value into a single set of
/// facts. Note that this method is not suitable for merging facts along
/// different paths in a CFG; that's what the mergeIn function is for. This
/// is for merging facts gathered about the same value at the same location
/// through two independent means.
/// Notes:
/// * This method does not promise to return the most precise possible lattice
/// value implied by A and B. It is allowed to return any lattice element
/// which is at least as strong as *either* A or B (unless our facts
/// conflict, see below).
/// * Due to unreachable code, the intersection of two lattice values could be
/// contradictory. If this happens, we return some valid lattice value so as
/// not confuse the rest of LVI. Ideally, we'd always return Undefined, but
/// we do not make this guarantee. TODO: This would be a useful enhancement.
static LVILatticeVal intersect(LVILatticeVal A, LVILatticeVal B) {
// Undefined is the strongest state. It means the value is known to be along
// an unreachable path.
if (A.isUndefined())
return A;
if (B.isUndefined())
return B;

// If we gave up for one, but got a useable fact from the other, use it.
if (A.isOverdefined())
return B;
if (B.isOverdefined())
return A;

// Can't get any more precise than constants.
if (hasSingleValue(A))
return A;
if (hasSingleValue(B))
return B;

// Could be either constant range or not constant here.
if (!A.isConstantRange() || !B.isConstantRange()) {
// TODO: Arbitrary choice, could be improved
return A;
}

// Intersect two constant ranges
ConstantRange Range =
A.getConstantRange().intersectWith(B.getConstantRange());
// Note: An empty range is implicitly converted to overdefined internally.
// TODO: We could instead use Undefined here since we've proven a conflict
// and thus know this path must be unreachable.
return LVILatticeVal::getRange(Range);
}

/// \brief Compute the value of Val on the edge BBFrom -> BBTo or the value at
/// the basic block if the edge does not constrain Val.
bool LazyValueInfoCache::getEdgeValue(Value *Val, BasicBlock *BBFrom,
Expand All @@ -1051,46 +1119,29 @@ bool LazyValueInfoCache::getEdgeValue(Value *Val, BasicBlock *BBFrom,
return true;
}

if (getEdgeValueLocal(Val, BBFrom, BBTo, Result)) {
if (!Result.isConstantRange() ||
Result.getConstantRange().getSingleElement())
return true;

// FIXME: this check should be moved to the beginning of the function when
// LVI better supports recursive values. Even for the single value case, we
// can intersect to detect dead code (an empty range).
if (!hasBlockValue(Val, BBFrom)) {
if (pushBlockValue(std::make_pair(BBFrom, Val)))
return false;
Result.markOverdefined();
return true;
}

// Try to intersect ranges of the BB and the constraint on the edge.
LVILatticeVal InBlock = getBlockValue(Val, BBFrom);
mergeAssumeBlockValueConstantRange(Val, InBlock, BBFrom->getTerminator());
// See note on the use of the CxtI with mergeAssumeBlockValueConstantRange,
// and caching, below.
mergeAssumeBlockValueConstantRange(Val, InBlock, CxtI);
if (!InBlock.isConstantRange())
return true;

ConstantRange Range =
Result.getConstantRange().intersectWith(InBlock.getConstantRange());
Result = LVILatticeVal::getRange(Range);
LVILatticeVal LocalResult;
if (!getEdgeValueLocal(Val, BBFrom, BBTo, LocalResult))
// If we couldn't constrain the value on the edge, LocalResult doesn't
// provide any information.
LocalResult.markOverdefined();

if (hasSingleValue(LocalResult)) {
// Can't get any more precise here
Result = LocalResult;
return true;
}

if (!hasBlockValue(Val, BBFrom)) {
if (pushBlockValue(std::make_pair(BBFrom, Val)))
return false;
Result.markOverdefined();
// No new information.
Result = LocalResult;
return true;
}

// If we couldn't compute the value on the edge, use the value from the BB.
Result = getBlockValue(Val, BBFrom);
mergeAssumeBlockValueConstantRange(Val, Result, BBFrom->getTerminator());
// Try to intersect ranges of the BB and the constraint on the edge.
LVILatticeVal InBlock = getBlockValue(Val, BBFrom);
mergeAssumeBlockValueConstantRange(Val, InBlock, BBFrom->getTerminator());
// We can use the context instruction (generically the ultimate instruction
// the calling pass is trying to simplify) here, even though the result of
// this function is generally cached when called from the solve* functions
Expand All @@ -1099,7 +1150,9 @@ bool LazyValueInfoCache::getEdgeValue(Value *Val, BasicBlock *BBFrom,
// functions, the context instruction is not provided. When called from
// LazyValueInfoCache::getValueOnEdge, the context instruction is provided,
// but then the result is not cached.
mergeAssumeBlockValueConstantRange(Val, Result, CxtI);
mergeAssumeBlockValueConstantRange(Val, InBlock, CxtI);

Result = intersect(LocalResult, InBlock);
return true;
}

Expand Down
50 changes: 50 additions & 0 deletions llvm/test/Transforms/CorrelatedValuePropagation/conflict.ll
@@ -0,0 +1,50 @@
; RUN: opt -correlated-propagation -S < %s | FileCheck %s
; Checks that we don't crash on conflicting facts about a value
; (i.e. unreachable code)

; Test that we can handle conflict edge facts
define i8 @test(i8 %a) {
; CHECK-LABEL: @test
%cmp1 = icmp eq i8 %a, 5
br i1 %cmp1, label %next, label %exit
next:
%cmp2 = icmp eq i8 %a, 3
; CHECK: br i1 false, label %dead, label %exit
br i1 %cmp2, label %dead, label %exit
dead:
; CHECK-LABEL: dead:
; CHECK: ret i8 5
; NOTE: undef, or 3 would be equal valid
ret i8 %a
exit:
ret i8 0
}

declare void @llvm.assume(i1)

; Test that we can handle conflicting assume vs edge facts
define i8 @test2(i8 %a) {
; CHECK-LABEL: @test2
%cmp1 = icmp eq i8 %a, 5
call void @llvm.assume(i1 %cmp1)
%cmp2 = icmp eq i8 %a, 3
; CHECK: br i1 false, label %dead, label %exit
br i1 %cmp2, label %dead, label %exit
dead:
ret i8 %a
exit:
ret i8 0
}

define i8 @test3(i8 %a) {
; CHECK-LABEL: @test3
%cmp1 = icmp eq i8 %a, 5
br i1 %cmp1, label %dead, label %exit
dead:
%cmp2 = icmp eq i8 %a, 3
; CHECK: call void @llvm.assume(i1 false)
call void @llvm.assume(i1 %cmp2)
ret i8 %a
exit:
ret i8 0
}
25 changes: 25 additions & 0 deletions llvm/test/Transforms/JumpThreading/induction.ll
@@ -0,0 +1,25 @@
; RUN: opt -S -jump-threading < %s | FileCheck %s

define i8 @test(i32 %a, i32 %length) {
; CHECK-LABEL: @test
entry:
; CHECK: br label %backedge
br label %loop

loop:
; CHECK-LABEL: backedge:
; CHECK: phi i32
; CHECK: br i1 %cont, label %backedge, label %exit
%iv = phi i32 [0, %entry], [%iv.next, %backedge]
;; We can use an inductive argument to prove %iv is always positive
%cnd = icmp sge i32 %iv, 0
br i1 %cnd, label %backedge, label %exit

backedge:
%iv.next = add nsw i32 %iv, 1
%cont = icmp slt i32 %iv.next, 400
br i1 %cont, label %loop, label %exit
exit:
ret i8 0
}

0 comments on commit 44456b8

Please sign in to comment.