Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 108 additions & 0 deletions llvm/lib/Transforms/Utils/SimplifyCFG.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1853,6 +1853,111 @@ static void hoistConditionalLoadsStores(
}
}

static std::optional<bool>
visitConditions(Value *V, const Value *BaseCond, const BasicBlock *ContextBB,
SmallVectorImpl<Instruction *> &ImpliedConditions,
const DataLayout &DL) {
if (!V)
return std::nullopt;

Instruction *I = dyn_cast<Instruction>(V);
if (!I)
return std::nullopt;

// we only care about conditions in the same basic block
if (ContextBB != I->getParent())
return std::nullopt;

// isImpliedCondition only handles integer conditions.
if (!I->getType()->isIntOrIntVectorTy(1) ||
!BaseCond->getType()->isIntOrIntVectorTy(1))
return std::nullopt;

std::optional<bool> Imp = isImpliedCondition(V, BaseCond, DL);
std::optional<bool> LHS = visitConditions(I->getOperand(0), BaseCond,
ContextBB, ImpliedConditions, DL);
std::optional<bool> RHS =
visitConditions((I->getNumOperands() >= 2 ? I->getOperand(1) : nullptr),
BaseCond, ContextBB, ImpliedConditions, DL);

// TODO: Handle negated condition case.
// Leaf condition node that implies the base condition.
if (Imp == true && !LHS.has_value() && !RHS.has_value()) {
ImpliedConditions.push_back(I);
}

return Imp;
}

static bool hoistImplyingConditions(BranchInst *BI, IRBuilder<> &Builder,
const DataLayout &DL) {
// Only look for CFG like
// A -> B, C
// B -> D, C
// or
// A -> B, C
// B -> C, D
// TODO: Handle the false branch case as well.
BasicBlock *ParentTrueBB = BI->getSuccessor(0);
BasicBlock *ParentFalseBB = BI->getSuccessor(1);

BranchInst *ChildBI = dyn_cast<BranchInst>(ParentTrueBB->getTerminator());
if (!ChildBI)
return false;

// TODO: Handle the unconditional branch case.
if (ChildBI->isUnconditional())
return false;

BasicBlock *ChildTrueBB = ChildBI->getSuccessor(0);
BasicBlock *ChildFalseBB = ChildBI->getSuccessor(1);
if (ParentFalseBB != ChildTrueBB && ParentFalseBB != ChildFalseBB)
return false;

// Avoid cases that have loops for simplicity.
if (ChildTrueBB == BI->getParent() || ChildFalseBB == BI->getParent())
return false;

auto NoSideEffects = [](BasicBlock &BB) {
return llvm::none_of(BB, [](const Instruction &I) {
return I.mayWriteToMemory() || I.mayHaveSideEffects();
});
};
// If the basic blocks have side effects, don't hoist conditions.
if (!NoSideEffects(*ParentTrueBB) || !NoSideEffects(*ParentFalseBB))
return false;

bool IsCommonBBonTruePath = (ParentFalseBB == ChildTrueBB);
// Check if parent branch condition is implied by the child branch
// condition. If so, we can hoist the child branch condition to the
// parent branch. For example:
// Parent branch condition: x > y
// Child branch condition: x == z (given z > y)
// We can hoist x == z to the parent branch and eliminate x > y
// condition check as x == z is a much stronger branch condition.
// So it will result in the true path being taken less often.
// Now that we know ChildBI condition implies parent BI condition,
// we need to find out which conditions to hoist out.
SmallVector<Instruction *, 2> HoistCandidates;
visitConditions(ChildBI->getCondition(), BI->getCondition(),
(!IsCommonBBonTruePath ? ParentTrueBB : ParentFalseBB),
HoistCandidates, DL);
// We don't handle multiple hoist candidates for now.
if (HoistCandidates.empty() || HoistCandidates.size() > 2)
return false;

// We can hoist the condition.
Instruction *ParentBranchCond = dyn_cast<Instruction>(BI->getCondition());
Builder.SetInsertPoint(BI);
Instruction *HoistedCondition = Builder.Insert(HoistCandidates[0]->clone());
ParentBranchCond->replaceAllUsesWith(HoistedCondition);
ParentBranchCond->eraseFromParent();
HoistCandidates[0]->replaceAllUsesWith(
ConstantInt::getTrue(ParentTrueBB->getContext()));

return true;
}

static bool isSafeCheapLoadStore(const Instruction *I,
const TargetTransformInfo &TTI) {
// Not handle volatile or atomic.
Expand Down Expand Up @@ -8121,6 +8226,9 @@ bool SimplifyCFGOpt::simplifyCondBranch(BranchInst *BI, IRBuilder<> &Builder) {
if (simplifyBranchOnICmpChain(BI, Builder, DL))
return true;

if (hoistImplyingConditions(BI, Builder, DL))
return requestResimplify();

// If this basic block has dominating predecessor blocks and the dominating
// blocks' conditions imply BI's condition, we know the direction of BI.
std::optional<bool> Imp = isImpliedByDomCondition(BI->getCondition(), BI, DL);
Expand Down
114 changes: 114 additions & 0 deletions llvm/test/Transforms/SimplifyCFG/hoist-implied-condition.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
; RUN: opt < %s -passes=simplifycfg -S | FileCheck %s

define i32 @src(ptr %contents.0, i64 %contents.1) {
; CHECK-LABEL: define i32 @src(
; CHECK-SAME: ptr [[CONTENTS_0:%.*]], i64 [[CONTENTS_1:%.*]]) {
; CHECK-NEXT: [[START:.*:]]
; CHECK-NEXT: [[TMP0:%.*]] = icmp eq i64 [[CONTENTS_1]], 16
; CHECK-NEXT: br i1 [[TMP0]], label %[[IF:.*]], label %[[EXIT:.*]]
; CHECK: [[IF]]:
; CHECK-NEXT: [[LOAD:%.*]] = load i64, ptr [[CONTENTS_0]], align 4
; CHECK-NEXT: [[CMP2:%.*]] = icmp eq i64 [[LOAD]], 123
; CHECK-NEXT: [[CMP3:%.*]] = icmp eq i64 [[CONTENTS_1]], 16
; CHECK-NEXT: [[AND:%.*]] = and i1 [[CMP2]], true
; CHECK-NEXT: br i1 [[AND]], label %[[COMMON_RET:.*]], label %[[EXIT]]
; CHECK: [[COMMON_RET]]:
; CHECK-NEXT: [[COMMON_RET_OP:%.*]] = phi i32 [ 0, %[[EXIT]] ], [ 1, %[[IF]] ]
; CHECK-NEXT: ret i32 [[COMMON_RET_OP]]
; CHECK: [[EXIT]]:
; CHECK-NEXT: br label %[[COMMON_RET]]
;
start:
%cmp1 = icmp ugt i64 %contents.1, 7
br i1 %cmp1, label %if, label %exit

if:
%load = load i64, ptr %contents.0
%cmp2 = icmp eq i64 %load, 123
%cmp3 = icmp eq i64 %contents.1, 16
%and = and i1 %cmp2, %cmp3
br i1 %and, label %if2, label %exit

if2:
ret i32 1

exit:
ret i32 0
}

define i32 @src-and(ptr %contents.0, i64 %contents.1) {
; CHECK-LABEL: define i32 @src-and(
; CHECK-SAME: ptr [[CONTENTS_0:%.*]], i64 [[CONTENTS_1:%.*]]) {
; CHECK-NEXT: [[START:.*:]]
; CHECK-NEXT: [[TMP0:%.*]] = icmp eq i64 [[CONTENTS_1]], 16
; CHECK-NEXT: br i1 [[TMP0]], label %[[IF:.*]], label %[[EXIT:.*]]
; CHECK: [[IF]]:
; CHECK-NEXT: [[LOAD:%.*]] = load i64, ptr [[CONTENTS_0]], align 4
; CHECK-NEXT: [[CMP2:%.*]] = icmp eq i64 [[LOAD]], 123
; CHECK-NEXT: [[CMP3:%.*]] = icmp eq i64 [[CONTENTS_1]], 16
; CHECK-NEXT: [[AND:%.*]] = or i1 [[CMP2]], true
; CHECK-NEXT: br i1 [[AND]], label %[[COMMON_RET:.*]], label %[[EXIT]]
; CHECK: [[COMMON_RET]]:
; CHECK-NEXT: [[COMMON_RET_OP:%.*]] = phi i32 [ 0, %[[EXIT]] ], [ 1, %[[IF]] ]
; CHECK-NEXT: ret i32 [[COMMON_RET_OP]]
; CHECK: [[EXIT]]:
; CHECK-NEXT: br label %[[COMMON_RET]]
;
start:
%cmp1 = icmp ugt i64 %contents.1, 7
br i1 %cmp1, label %if, label %exit

if:
%load = load i64, ptr %contents.0
%cmp2 = icmp eq i64 %load, 123
%cmp3 = icmp eq i64 %contents.1, 16
%and = or i1 %cmp2, %cmp3
br i1 %and, label %if2, label %exit

if2:
ret i32 1

exit:
ret i32 0
}

define i32 @src-sideeffects(ptr %contents.0, i64 %contents.1) {
; CHECK-LABEL: define i32 @src-sideeffects(
; CHECK-SAME: ptr [[CONTENTS_0:%.*]], i64 [[CONTENTS_1:%.*]]) {
; CHECK-NEXT: [[START:.*:]]
; CHECK-NEXT: [[CMP1:%.*]] = icmp ugt i64 [[CONTENTS_1]], 7
; CHECK-NEXT: br i1 [[CMP1]], label %[[IF:.*]], label %[[EXIT:.*]]
; CHECK: [[IF]]:
; CHECK-NEXT: [[LOAD:%.*]] = load i64, ptr [[CONTENTS_0]], align 4
; CHECK-NEXT: [[CMP2:%.*]] = icmp eq i64 [[LOAD]], 123
; CHECK-NEXT: [[CMP3:%.*]] = icmp eq i64 [[CONTENTS_1]], 16
; CHECK-NEXT: [[AND:%.*]] = and i1 [[CMP2]], [[CMP3]]
; CHECK-NEXT: [[ADD:%.*]] = add i64 [[LOAD]], [[CONTENTS_1]]
; CHECK-NEXT: store i64 [[ADD]], ptr [[CONTENTS_0]], align 4
; CHECK-NEXT: br i1 [[AND]], label %[[COMMON_RET:.*]], label %[[EXIT]]
; CHECK: [[COMMON_RET]]:
; CHECK-NEXT: [[COMMON_RET_OP:%.*]] = phi i32 [ 0, %[[EXIT]] ], [ 1, %[[IF]] ]
; CHECK-NEXT: ret i32 [[COMMON_RET_OP]]
; CHECK: [[EXIT]]:
; CHECK-NEXT: br label %[[COMMON_RET]]
;
start:
%cmp1 = icmp ugt i64 %contents.1, 7
br i1 %cmp1, label %if, label %exit

if:
%load = load i64, ptr %contents.0
%cmp2 = icmp eq i64 %load, 123
%cmp3 = icmp eq i64 %contents.1, 16
%and = and i1 %cmp2, %cmp3
%add = add i64 %load, %contents.1
store i64 %add, ptr %contents.0
br i1 %and, label %if2, label %exit

if2:
ret i32 1

exit:
ret i32 0
}
Original file line number Diff line number Diff line change
Expand Up @@ -491,7 +491,7 @@ define void @test12() nounwind {
; CHECK: bb55.us.us:
; CHECK-NEXT: [[B:%.*]] = icmp ugt i32 undef, undef
; CHECK-NEXT: [[A:%.*]] = icmp eq i32 undef, undef
; CHECK-NEXT: [[OR_COND:%.*]] = or i1 [[B]], [[A]]
; CHECK-NEXT: [[OR_COND:%.*]] = or i1 [[B]], true
; CHECK-NEXT: br i1 [[OR_COND]], label [[BB55_US_US]], label [[MALFORMED]]
; CHECK: malformed:
; CHECK-NEXT: ret void
Expand Down
2 changes: 1 addition & 1 deletion llvm/test/Transforms/SimplifyCFG/switch_create.ll
Original file line number Diff line number Diff line change
Expand Up @@ -644,7 +644,7 @@ define void @test12() nounwind {
; CHECK: bb55.us.us:
; CHECK-NEXT: [[B:%.*]] = icmp ugt i32 undef, undef
; CHECK-NEXT: [[A:%.*]] = icmp eq i32 undef, undef
; CHECK-NEXT: [[OR_COND:%.*]] = or i1 [[B]], [[A]]
; CHECK-NEXT: [[OR_COND:%.*]] = or i1 [[B]], true
; CHECK-NEXT: br i1 [[OR_COND]], label [[BB55_US_US]], label [[MALFORMED]]
; CHECK: malformed:
; CHECK-NEXT: ret void
Expand Down
Loading