diff --git a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp index a1f759dd1df83..d8adc351904b3 100644 --- a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp +++ b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp @@ -1853,6 +1853,111 @@ static void hoistConditionalLoadsStores( } } +static std::optional +visitConditions(Value *V, const Value *BaseCond, const BasicBlock *ContextBB, + SmallVectorImpl &ImpliedConditions, + const DataLayout &DL) { + if (!V) + return std::nullopt; + + Instruction *I = dyn_cast(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 Imp = isImpliedCondition(V, BaseCond, DL); + std::optional LHS = visitConditions(I->getOperand(0), BaseCond, + ContextBB, ImpliedConditions, DL); + std::optional 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(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 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(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. @@ -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 Imp = isImpliedByDomCondition(BI->getCondition(), BI, DL); diff --git a/llvm/test/Transforms/SimplifyCFG/hoist-implied-condition.ll b/llvm/test/Transforms/SimplifyCFG/hoist-implied-condition.ll new file mode 100644 index 0000000000000..37655daacced5 --- /dev/null +++ b/llvm/test/Transforms/SimplifyCFG/hoist-implied-condition.ll @@ -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 +} diff --git a/llvm/test/Transforms/SimplifyCFG/switch_create-custom-dl.ll b/llvm/test/Transforms/SimplifyCFG/switch_create-custom-dl.ll index 336fc5e14d758..eb52d32e40a66 100644 --- a/llvm/test/Transforms/SimplifyCFG/switch_create-custom-dl.ll +++ b/llvm/test/Transforms/SimplifyCFG/switch_create-custom-dl.ll @@ -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 diff --git a/llvm/test/Transforms/SimplifyCFG/switch_create.ll b/llvm/test/Transforms/SimplifyCFG/switch_create.ll index 18c4ade46162c..c5582683ef103 100644 --- a/llvm/test/Transforms/SimplifyCFG/switch_create.ll +++ b/llvm/test/Transforms/SimplifyCFG/switch_create.ll @@ -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