Skip to content

[IfConversion] Fix bug related to !HasFallThrough #145471

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 25, 2025
Merged

[IfConversion] Fix bug related to !HasFallThrough #145471

merged 3 commits into from
Jun 25, 2025

Conversation

bjope
Copy link
Collaborator

@bjope bjope commented Jun 24, 2025

We can not trust that !HasFallThrough implies that there is not
fallthrough exit in cases when analyzeBranch failed.

Adding a new blockNeverFallThrough helper to make the tests on
!HasFallThrough safe by also checking IsBrAnalyzable. We also
try to prove no-fallthrough by inspecting the successor list. If
the textual successor isn't in the successor list we know that
there is no fallthrough.

@llvmbot
Copy link
Member

llvmbot commented Jun 24, 2025

@llvm/pr-subscribers-backend-arm

Author: Björn Pettersson (bjope)

Changes

We can not trust that !HasFallThrough implies that there is not
fallthrough exit in cases when analyzeBranch failed.

Adding a new blockNeverFallThrough helper to make the tests on
!HasFallThrough safe by also checking IsBrAnalyzable.

We could add more logic to blockNeverFallThrough to also check
if the textual successor is absent from the MBBs successor list
(as that would indicate that there is no fallthrough edge). I'm
not sure it would make much difference though, and in this patch
focus is to just fix the bug.


Full diff: https://github.com/llvm/llvm-project/pull/145471.diff

3 Files Affected:

  • (modified) llvm/lib/CodeGen/IfConversion.cpp (+18-7)
  • (modified) llvm/test/CodeGen/ARM/ifcvt_triangleWoCvtToNextEdge.mir (+12-10)
  • (added) llvm/test/CodeGen/ARM/ifcvt_unanalyzable_fallthrough.mir (+114)
diff --git a/llvm/lib/CodeGen/IfConversion.cpp b/llvm/lib/CodeGen/IfConversion.cpp
index 5265bd74d2dbf..fea3de6ff5cab 100644
--- a/llvm/lib/CodeGen/IfConversion.cpp
+++ b/llvm/lib/CodeGen/IfConversion.cpp
@@ -117,7 +117,11 @@ namespace {
     /// IsAnalyzed      - True if BB has been analyzed (info is still valid).
     /// IsEnqueued      - True if BB has been enqueued to be ifcvt'ed.
     /// IsBrAnalyzable  - True if analyzeBranch() returns false.
-    /// HasFallThrough  - True if BB may fallthrough to the following BB.
+    /// HasFallThrough  - True if BB has fallthrough to the following BB.
+    ///                   Note that BB may have a fallthrough if both
+    ///                   !HasFallThrough and !IsBrAnalyzable is true. Also note
+    ///                   that blockNeverFallThrough() can be used to prove that
+    ///                   there is no fall through.
     /// IsUnpredicable  - True if BB is known to be unpredicable.
     /// ClobbersPred    - True if BB could modify predicates (e.g. has
     ///                   cmp, call, etc.)
@@ -125,7 +129,10 @@ namespace {
     /// ExtraCost       - Extra cost for multi-cycle instructions.
     /// ExtraCost2      - Some instructions are slower when predicated
     /// BB              - Corresponding MachineBasicBlock.
-    /// TrueBB / FalseBB- See analyzeBranch().
+    /// TrueBB / FalseBB- See analyzeBranch(), but note that FalseBB can be set
+    ///                   by AnalyzeBranches even if there is a fallthrough. So
+    ///                   it doesn't correspond exactly to the result from
+    ///                   TTI::analyzeBranch.
     /// BrCond          - Conditions for end of block conditional branches.
     /// Predicate       - Predicate used in the BB.
     struct BBInfo {
@@ -397,6 +404,11 @@ namespace {
       return BBI.IsBrAnalyzable && BBI.TrueBB == nullptr;
     }
 
+    /// Returns true if Block is known not to fallthrough to the following BB.
+    bool blockNeverFallThrough(BBInfo &BBI) const {
+      return BBI.IsBrAnalyzable && !BBI.HasFallThrough;
+    }
+
     /// Used to sort if-conversion candidates.
     static bool IfcvtTokenCmp(const std::unique_ptr<IfcvtToken> &C1,
                               const std::unique_ptr<IfcvtToken> &C2) {
@@ -1715,9 +1727,8 @@ bool IfConverter::IfConvertTriangle(BBInfo &BBI, IfcvtKind Kind) {
     // Only merge them if the true block does not fallthrough to the false
     // block. By not merging them, we make it possible to iteratively
     // ifcvt the blocks.
-    if (!HasEarlyExit &&
-        NextMBB.pred_size() == 1 && !NextBBI->HasFallThrough &&
-        !NextMBB.hasAddressTaken()) {
+    if (!HasEarlyExit && NextMBB.pred_size() == 1 &&
+        blockNeverFallThrough(*NextBBI) && !NextMBB.hasAddressTaken()) {
       MergeBlocks(BBI, *NextBBI);
       FalseBBDead = true;
     } else {
@@ -2052,8 +2063,8 @@ bool IfConverter::IfConvertDiamond(BBInfo &BBI, IfcvtKind Kind,
     BBI.BB->removeSuccessor(FalseBBI.BB, true);
 
     BBInfo &TailBBI = BBAnalysis[TailBB->getNumber()];
-    bool CanMergeTail = !TailBBI.HasFallThrough &&
-      !TailBBI.BB->hasAddressTaken();
+    bool CanMergeTail =
+        blockNeverFallThrough(TailBBI) && !TailBBI.BB->hasAddressTaken();
     // The if-converted block can still have a predicated terminator
     // (e.g. a predicated return). If that is the case, we cannot merge
     // it with the tail block.
diff --git a/llvm/test/CodeGen/ARM/ifcvt_triangleWoCvtToNextEdge.mir b/llvm/test/CodeGen/ARM/ifcvt_triangleWoCvtToNextEdge.mir
index 005b433904e30..0cab01e1bd8d0 100644
--- a/llvm/test/CodeGen/ARM/ifcvt_triangleWoCvtToNextEdge.mir
+++ b/llvm/test/CodeGen/ARM/ifcvt_triangleWoCvtToNextEdge.mir
@@ -21,16 +21,18 @@ name:            foo
 body:             |
   ; CHECK-LABEL: name: foo
   ; CHECK: bb.0:
-  ; CHECK:   successors: %bb.2(0x40000000), %bb.1(0x40000000)
-  ; CHECK:   tBcc %bb.2, 1 /* CC::ne */, $cpsr
-  ; CHECK: bb.1:
-  ; CHECK:   successors:
-  ; CHECK:   tBL 14 /* CC::al */, $cpsr, @__stack_chk_fail
-  ; CHECK: bb.2:
-  ; CHECK:   tBL 1 /* CC::ne */, $cpsr, @__stack_chk_fail
-  ; CHECK:   $sp = tADDspi $sp, 2, 14 /* CC::al */, $noreg
-  ; CHECK:   $sp = tADDspi $sp, 2, 14 /* CC::al */, $noreg
-  ; CHECK:   tTAILJMPdND @bar, 14 /* CC::al */, $cpsr
+  ; CHECK-NEXT:   successors: %bb.1(0x40000000), %bb.2(0x40000000)
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT:   tBcc %bb.2, 0 /* CC::eq */, $cpsr
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT: bb.1:
+  ; CHECK-NEXT:   tBL 1 /* CC::ne */, $cpsr, @__stack_chk_fail
+  ; CHECK-NEXT:   $sp = tADDspi $sp, 2, 14 /* CC::al */, $noreg
+  ; CHECK-NEXT:   $sp = tADDspi $sp, 2, 14 /* CC::al */, $noreg
+  ; CHECK-NEXT:   tTAILJMPdND @bar, 14 /* CC::al */, $cpsr
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT: bb.2:
+  ; CHECK-NEXT:   tBL 14 /* CC::al */, $cpsr, @__stack_chk_fail
 
   bb.0:
     tBcc %bb.1, 1, $cpsr
diff --git a/llvm/test/CodeGen/ARM/ifcvt_unanalyzable_fallthrough.mir b/llvm/test/CodeGen/ARM/ifcvt_unanalyzable_fallthrough.mir
new file mode 100644
index 0000000000000..72b887afdad96
--- /dev/null
+++ b/llvm/test/CodeGen/ARM/ifcvt_unanalyzable_fallthrough.mir
@@ -0,0 +1,114 @@
+# NOTE: Assertions have been autogenerated by utils/update_mir_test_checks.py
+# RUN: llc -mtriple=thumbv7-apple-ios -run-pass=if-converter %s -o - | FileCheck %s
+
+# Testcase with unanalyzable branches (that may fallthrough) in the BB
+# following the diamond/triangle.
+
+# Goal here is to showcase a problem seen in the IfConverter when
+# AnalyzeBranch is indicating that the branches couldn't be analyzed. Problem
+# was originally seen for an out-of-tree target, and here we use ARM and a
+# MBB with two conditional jumps to make AnalyzeBranch return false.
+#
+# The problem was that if-converter when analyzing branches was using a
+# variable named HasFallThrough, to remember that an MBB could fallthrough to
+# the textual successor. When HasFallThrough is set we know that there are
+# fallthrough exits, but the opposite is not guaranteed. If
+# HasFallThrough==false there could still be fallthrough exists in situations
+# when analyzeBranch found unanalyzable branches. There were however a couple
+# of places in the code that checked !HasFallThrough assuming that it would
+# imply that there was no fallthrough exit.
+#
+# As a consequence we could end up merging blocks at the end of a converted
+# diamond/triangle and while doing that we messed up when fixing up the CFG
+# related to fallthrough edges. For the test cases below we incorrectly ended
+# up ended up with a fallthrough from the MBBs with two Bcc instructions to
+# the MBB with the STRH after if conversion.
+#
+---
+name:            avoidMergeBlockDiamond
+body:             |
+  ; CHECK-LABEL: name: avoidMergeBlockDiamond
+  ; CHECK: bb.0:
+  ; CHECK-NEXT:   successors: %bb.1(0x80000000)
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT:   $sp = tADDspi $sp, 2, 1 /* CC::ne */, $cpsr
+  ; CHECK-NEXT:   $sp = tADDspi $sp, 1, 0 /* CC::eq */, $cpsr, implicit $sp
+  ; CHECK-NEXT:   $sp = tADDspi $sp, 3, 14 /* CC::al */, $noreg
+  ; CHECK-NEXT:   tBcc %bb.1, 1 /* CC::ne */, $cpsr
+  ; CHECK-NEXT:   tBcc %bb.1, 1 /* CC::ne */, $cpsr
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT: bb.1:
+  ; CHECK-NEXT:   tBX_RET 14 /* CC::al */, $noreg
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT: bb.2:
+  ; CHECK-NEXT:   successors: %bb.2(0x80000000)
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT:   STRH $sp, $sp, $noreg, 0, 14 /* CC::al */, $noreg
+  ; CHECK-NEXT:   tB %bb.2, 14 /* CC::al */, $noreg
+  bb.0:
+    tBcc %bb.2, 1, $cpsr
+
+  bb.1:
+    $sp = tADDspi $sp, 1, 14, _
+    tB %bb.4, 14, $noreg
+
+  bb.2:
+    $sp = tADDspi $sp, 2, 14, _
+    tB %bb.4, 14, $noreg
+
+  bb.3:
+    STRH $sp, $sp, $noreg, 0, 14, $noreg
+    tB %bb.3, 14, $noreg
+
+  bb.4:
+    $sp = tADDspi $sp, 3, 14, _
+    tBcc %bb.5, 1, $cpsr
+    tBcc %bb.5, 1, $cpsr
+
+  bb.5:
+  successors:
+    tBX_RET 14, _
+...
+
+# Similar to the above, but with a triangle.
+---
+name:            avoidMergeBlockTriangle
+body:             |
+  ; CHECK-LABEL: name: avoidMergeBlockTriangle
+  ; CHECK: bb.0:
+  ; CHECK-NEXT:   successors: %bb.1(0x80000000)
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT:   $sp = tADDspi $sp, 1, 1 /* CC::ne */, $cpsr
+  ; CHECK-NEXT:   $sp = tADDspi $sp, 2, 14 /* CC::al */, $noreg
+  ; CHECK-NEXT:   tBcc %bb.1, 1 /* CC::ne */, $cpsr
+  ; CHECK-NEXT:   tBcc %bb.1, 1 /* CC::ne */, $cpsr
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT: bb.1:
+  ; CHECK-NEXT:   tBX_RET 14 /* CC::al */, $noreg
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT: bb.2:
+  ; CHECK-NEXT:   successors: %bb.2(0x80000000)
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT:   STRH $sp, $sp, $noreg, 0, 14 /* CC::al */, $noreg
+  ; CHECK-NEXT:   tB %bb.2, 14 /* CC::al */, $noreg
+  bb.0:
+    tBcc %bb.1, 1, $cpsr
+    tB %bb.3, 14, $noreg
+
+  bb.1:
+    $sp = tADDspi $sp, 1, 14, _
+    tB %bb.3, 14, $noreg
+
+  bb.2:
+    STRH $sp, $sp, $noreg, 0, 14, $noreg
+    tB %bb.2, 14, $noreg
+
+  bb.3:
+    $sp = tADDspi $sp, 2, 14, _
+    tBcc %bb.4, 1, $cpsr
+    tBcc %bb.4, 1, $cpsr
+
+  bb.4:
+  successors:
+    tBX_RET 14, _
+...

# As a consequence we could end up merging blocks at the end of a converted
# diamond/triangle and while doing that we messed up when fixing up the CFG
# related to fallthrough edges. For the test cases below we incorrectly ended
# up ended up with a fallthrough from the MBBs with two Bcc instructions to
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove the second "ended up".

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

; CHECK: $sp = tADDspi $sp, 2, 14 /* CC::al */, $noreg
; CHECK: $sp = tADDspi $sp, 2, 14 /* CC::al */, $noreg
; CHECK: tTAILJMPdND @bar, 14 /* CC::al */, $cpsr
; CHECK-NEXT: successors: %bb.1(0x40000000), %bb.2(0x40000000)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe mention something in the commit message about why we get a diff in this testcase? I suppose it was correct before too, just that now the condition in bb.0 and bb.1/bb.2 are swapped compared to before?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I took another look at this. And I'll make an update to also check for empty successor list in blockNeverFallThrough, which would imply that there is no fallthrough. That way we can let IfConversion merge the blocks (just like it used to do here), and avoid the lit test diff.
Given that BlockFolding (called by IfConversion in the end) did a similar cleanup it did not matter much for this specific test case, but maybe there could be potential regressions in similar situations with no-successor blocks. So it is probably better to handle that as well.

(I had some doubts earlier about inspecting the successor lists in blockNeverFallThrough, but I actually had missed the check for empty successor lists in my earlier experiments. Now it makes more sense.)

// fallthrough.
MachineFunction::iterator PI = BBI.BB->getIterator();
MachineFunction::iterator I = std::next(PI);
if (I != BBI.BB->getParent()->end() && !PI->isSuccessor(&*I))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I == BBI.BB->getParent()->end() implies this is the last block of the function, so it can't fallthrough.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right. I added a fixup to catch that.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With that, can you also drop the redundant succ_empty() check?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes!

@bjope bjope requested a review from efriedma-quic June 24, 2025 22:32
Copy link
Collaborator

@efriedma-quic efriedma-quic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

bjope added 3 commits June 25, 2025 09:23
We can not trust that !HasFallThrough implies that there is not
fallthrough exit in cases when analyzeBranch failed.

Adding a new blockNeverFallThrough helper to make the tests on
!HasFallThrough safe by also checking IsBrAnalyzable. We also
try to prove no-fallthrough by inspecting the successor list. If
the textual successor isn't in the successor list we know that
there is no fallthrough.
@bjope bjope merged commit 237b8de into llvm:main Jun 25, 2025
5 of 7 checks passed
anthonyhatran pushed a commit to anthonyhatran/llvm-project that referenced this pull request Jun 26, 2025
We can not trust that !HasFallThrough implies that there is not
fallthrough exit in cases when analyzeBranch failed.

Adding a new blockNeverFallThrough helper to make the tests on
!HasFallThrough safe by also checking IsBrAnalyzable. We also
try to prove no-fallthrough by inspecting the successor list. If
the textual successor isn't in the successor list we know that
there is no fallthrough.

The bug has probably been around for years. Found it when
working on an out-of-tree target.
rlavaee pushed a commit to rlavaee/llvm-project that referenced this pull request Jul 1, 2025
We can not trust that !HasFallThrough implies that there is not
fallthrough exit in cases when analyzeBranch failed.

Adding a new blockNeverFallThrough helper to make the tests on
!HasFallThrough safe by also checking IsBrAnalyzable. We also
try to prove no-fallthrough by inspecting the successor list. If
the textual successor isn't in the successor list we know that
there is no fallthrough.

The bug has probably been around for years. Found it when
working on an out-of-tree target.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants