[llvm] [SimplifyCFG] Avoid threading for loop headers (PR #151142)

Arne Stenkrona via llvm-commits llvm-commits at lists.llvm.org
Tue Jul 29 08:21:56 PDT 2025


https://github.com/ArneStenkrona2 updated https://github.com/llvm/llvm-project/pull/151142

>From 507d8000c3b92752da381b074e7de8e408b5491d Mon Sep 17 00:00:00 2001
From: Arne Stenkrona <arne.stenkrona at arm.com>
Date: Thu, 8 May 2025 16:19:35 +0200
Subject: [PATCH 1/2] [SimplifyCFG] Avoid threading for loop headers

Updates SimplifyCFG to avoid jump threading through loop headers if
-keep-loops is requested. Canonical loop form requires a loop header
that dominates all blocks in the loop. If we thread through a header,
we risk breaking its domination of the loop. This change avoids this
issue by conservatively avoiding threading through headers entirely.
---
 llvm/lib/Transforms/Utils/SimplifyCFG.cpp     | 10 ++++-
 .../CodeGen/ARM/2013-05-05-IfConvertBug.ll    |  6 +--
 .../2008-07-13-InfLoopMiscompile.ll           |  3 +-
 .../2025-07-29-non-canoncial-loop.ll          | 37 +++++++++++++++++++
 .../SimplifyCFG/branch-phi-thread.ll          |  2 +-
 .../Transforms/SimplifyCFG/jump-threading.ll  |  2 +-
 .../SimplifyCFG/two-entry-phi-return.ll       |  2 +-
 7 files changed, 52 insertions(+), 10 deletions(-)
 create mode 100644 llvm/test/Transforms/SimplifyCFG/2025-07-29-non-canoncial-loop.ll

diff --git a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
index 94b0ab892f2dd..d8385beb349d6 100644
--- a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
+++ b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
@@ -8030,8 +8030,14 @@ bool SimplifyCFGOpt::simplifyCondBranch(BranchInst *BI, IRBuilder<> &Builder) {
   // If this is a branch on something for which we know the constant value in
   // predecessors (e.g. a phi node in the current block), thread control
   // through this block.
-  if (foldCondBranchOnValueKnownInPredecessor(BI, DTU, DL, Options.AC))
-    return requestResimplify();
+  // Note: If BB is a loop header then there is a risk that threading introduces
+  // a non-canonical loop by moving a back edge. So we avoid this optimization
+  // for loop headers if NeedCanonicalLoop is set.
+  bool InHeader = !LoopHeaders.empty() && is_contained(LoopHeaders, BB);
+  bool AvoidThreading = Options.NeedCanonicalLoop && InHeader;
+  if (!AvoidThreading)
+    if (foldCondBranchOnValueKnownInPredecessor(BI, DTU, DL, Options.AC))
+      return requestResimplify();
 
   // Scan predecessor blocks for conditional branches.
   for (BasicBlock *Pred : predecessors(BB))
diff --git a/llvm/test/CodeGen/ARM/2013-05-05-IfConvertBug.ll b/llvm/test/CodeGen/ARM/2013-05-05-IfConvertBug.ll
index 344bb15d2a8b8..8f798fac06f54 100644
--- a/llvm/test/CodeGen/ARM/2013-05-05-IfConvertBug.ll
+++ b/llvm/test/CodeGen/ARM/2013-05-05-IfConvertBug.ll
@@ -1,7 +1,7 @@
 ; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
-; RUN: llc < %s -mtriple=thumbv7-apple-ios -mcpu=cortex-a8 | FileCheck %s
-; RUN: llc < %s -mtriple=thumbv8 | FileCheck -check-prefix=CHECK-V8 %s
-; RUN: llc < %s -mtriple=thumbv7 -arm-restrict-it | FileCheck -check-prefix=CHECK-RESTRICT-IT %s
+; RUN: llc -keep-loops="false" < %s -mtriple=thumbv7-apple-ios -mcpu=cortex-a8 | FileCheck %s
+; RUN: llc -keep-loops="false" < %s -mtriple=thumbv8 | FileCheck -check-prefix=CHECK-V8 %s
+; RUN: llc -keep-loops="false" < %s -mtriple=thumbv7 -arm-restrict-it | FileCheck -check-prefix=CHECK-RESTRICT-IT %s
 
 define i32 @t1(i32 %a, i32 %b, ptr %retaddr) {
 ; CHECK-LABEL: t1:
diff --git a/llvm/test/Transforms/SimplifyCFG/2008-07-13-InfLoopMiscompile.ll b/llvm/test/Transforms/SimplifyCFG/2008-07-13-InfLoopMiscompile.ll
index 2e9e7b19c73e2..44d92e1a1c210 100644
--- a/llvm/test/Transforms/SimplifyCFG/2008-07-13-InfLoopMiscompile.ll
+++ b/llvm/test/Transforms/SimplifyCFG/2008-07-13-InfLoopMiscompile.ll
@@ -1,5 +1,5 @@
 ; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
-; RUN: opt < %s -passes=simplifycfg -simplifycfg-require-and-preserve-domtree=1 -S | FileCheck %s
+; RUN: opt < %s -passes=simplifycfg -simplifycfg-require-and-preserve-domtree=1 -keep-loops="false" -S | FileCheck %s
 ; PR2540
 ; Outval should end up with a select from 0/2, not all constants.
 
@@ -52,4 +52,3 @@ func_1.exit:		; preds = %cowblock, %entry
 }
 
 declare i32 @printf(ptr, ...) nounwind
-
diff --git a/llvm/test/Transforms/SimplifyCFG/2025-07-29-non-canoncial-loop.ll b/llvm/test/Transforms/SimplifyCFG/2025-07-29-non-canoncial-loop.ll
new file mode 100644
index 0000000000000..a64108f6346b9
--- /dev/null
+++ b/llvm/test/Transforms/SimplifyCFG/2025-07-29-non-canoncial-loop.ll
@@ -0,0 +1,37 @@
+; RUN: opt < %s -passes=simplifycfg -simplifycfg-require-and-preserve-domtree=1 -S | FileCheck --check-prefix=NO-THREADING %s
+; Checks that we do not thread the control flow through the loop header bb1 as
+; that will introduce a non-canonical loop
+
+; NO-THREADING-LABEL: define void @__start
+; NO-THREADING: bb3:
+; NO-THREADING-NEXT: br i1 %cond, label %bb1, label %bb5
+
+; RUN: opt < %s -passes=simplifycfg -simplifycfg-require-and-preserve-domtree=1 --keep-loops="false" -S | FileCheck --check-prefix=THREADING %s
+; Checks that we thread the control flow through the loop header bb1 since we
+; do not request --keep-loops
+
+; THREADING-LABEL: define void @__start
+; THREADING: bb3:
+; THREADING-NEXT: br i1 %cond, label %bb4, label %bb5
+
+define void @__start(i1 %cond) {
+entry:
+  br label %bb1
+
+bb1:                                            ; preds = %bb3, %entry
+  br i1 %cond, label %bb4, label %bb2
+
+bb2:                                            ; preds = %bb1
+  %_0_ = add i16 0, 0
+  br label %bb3
+
+bb3:                                            ; preds = %bb4, %bb2
+  br i1 %cond, label %bb1, label %bb5
+
+bb4:                                            ; preds = %bb1
+  %_1_ = add i32 0, 1
+  br label %bb3
+
+bb5:                                            ; preds = %bb3
+  ret void
+}
diff --git a/llvm/test/Transforms/SimplifyCFG/branch-phi-thread.ll b/llvm/test/Transforms/SimplifyCFG/branch-phi-thread.ll
index 0afec05ecbd6a..ec9423bd81675 100644
--- a/llvm/test/Transforms/SimplifyCFG/branch-phi-thread.ll
+++ b/llvm/test/Transforms/SimplifyCFG/branch-phi-thread.ll
@@ -1,5 +1,5 @@
 ; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
-; RUN: opt < %s -passes=simplifycfg,adce -simplifycfg-require-and-preserve-domtree=1 -S | FileCheck %s
+; RUN: opt < %s -passes=simplifycfg,adce -simplifycfg-require-and-preserve-domtree=1 -keep-loops="false" -S | FileCheck %s
 
 declare void @f1()
 
diff --git a/llvm/test/Transforms/SimplifyCFG/jump-threading.ll b/llvm/test/Transforms/SimplifyCFG/jump-threading.ll
index 50a32413a0551..a4073ae6eb0b4 100644
--- a/llvm/test/Transforms/SimplifyCFG/jump-threading.ll
+++ b/llvm/test/Transforms/SimplifyCFG/jump-threading.ll
@@ -1,5 +1,5 @@
 ; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
-; RUN: opt -S -passes=simplifycfg < %s | FileCheck %s
+; RUN: opt -S -passes=simplifycfg -keep-loops="false" < %s | FileCheck %s
 
 declare void @foo()
 declare void @bar()
diff --git a/llvm/test/Transforms/SimplifyCFG/two-entry-phi-return.ll b/llvm/test/Transforms/SimplifyCFG/two-entry-phi-return.ll
index 57930c91b9796..f6d71ddda74fe 100644
--- a/llvm/test/Transforms/SimplifyCFG/two-entry-phi-return.ll
+++ b/llvm/test/Transforms/SimplifyCFG/two-entry-phi-return.ll
@@ -1,5 +1,5 @@
 ; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
-; RUN: opt < %s -passes=simplifycfg -simplifycfg-require-and-preserve-domtree=1 -S | FileCheck %s
+; RUN: opt < %s -passes=simplifycfg -simplifycfg-require-and-preserve-domtree=1 -keep-loops="false" -S | FileCheck %s
 
 define i1 @qux(ptr %m, ptr %n, ptr %o, ptr %p) nounwind  {
 ; CHECK-LABEL: @qux(

>From e6c922727d473eb4290c5e5349bf5de71cb34caf Mon Sep 17 00:00:00 2001
From: Arne Stenkrona <arne.stenkrona at arm.com>
Date: Tue, 29 Jul 2025 16:02:38 +0200
Subject: [PATCH 2/2] Move threading check, run update_test_checks.py

---
 llvm/lib/Transforms/Utils/SimplifyCFG.cpp     | 29 ++++----
 .../2025-07-29-non-canoncial-loop.ll          | 73 +++++++++++++------
 2 files changed, 65 insertions(+), 37 deletions(-)

diff --git a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
index d8385beb349d6..89ff74ed73f68 100644
--- a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
+++ b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
@@ -286,6 +286,7 @@ class SimplifyCFGOpt {
   bool simplifyBranch(BranchInst *Branch, IRBuilder<> &Builder);
   bool simplifyUncondBranch(BranchInst *BI, IRBuilder<> &Builder);
   bool simplifyCondBranch(BranchInst *BI, IRBuilder<> &Builder);
+  bool foldCondBranchOnValueKnownInPredecessor(BranchInst *BI);
 
   bool tryToSimplifyUncondBranchWithICmpInIt(ICmpInst *ICI,
                                              IRBuilder<> &Builder);
@@ -3604,15 +3605,23 @@ foldCondBranchOnValueKnownInPredecessorImpl(BranchInst *BI, DomTreeUpdater *DTU,
   return false;
 }
 
-static bool foldCondBranchOnValueKnownInPredecessor(BranchInst *BI,
-                                                    DomTreeUpdater *DTU,
-                                                    const DataLayout &DL,
-                                                    AssumptionCache *AC) {
+#include <iostream>
+
+bool SimplifyCFGOpt::foldCondBranchOnValueKnownInPredecessor(BranchInst *BI) {
+  // Note: If BB is a loop header then there is a risk that threading introduces
+  // a non-canonical loop by moving a back edge. So we avoid this optimization
+  // for loop headers if NeedCanonicalLoop is set.
+  bool InHeader = is_contained(LoopHeaders, BI->getParent());
+  bool AvoidThreading = Options.NeedCanonicalLoop && InHeader;
+  if (AvoidThreading) {
+    return false;
+  }
+
   std::optional<bool> Result;
   bool EverChanged = false;
   do {
     // Note that None means "we changed things, but recurse further."
-    Result = foldCondBranchOnValueKnownInPredecessorImpl(BI, DTU, DL, AC);
+    Result = foldCondBranchOnValueKnownInPredecessorImpl(BI, DTU, DL, Options.AC);
     EverChanged |= Result == std::nullopt || *Result;
   } while (Result == std::nullopt);
   return EverChanged;
@@ -8030,14 +8039,8 @@ bool SimplifyCFGOpt::simplifyCondBranch(BranchInst *BI, IRBuilder<> &Builder) {
   // If this is a branch on something for which we know the constant value in
   // predecessors (e.g. a phi node in the current block), thread control
   // through this block.
-  // Note: If BB is a loop header then there is a risk that threading introduces
-  // a non-canonical loop by moving a back edge. So we avoid this optimization
-  // for loop headers if NeedCanonicalLoop is set.
-  bool InHeader = !LoopHeaders.empty() && is_contained(LoopHeaders, BB);
-  bool AvoidThreading = Options.NeedCanonicalLoop && InHeader;
-  if (!AvoidThreading)
-    if (foldCondBranchOnValueKnownInPredecessor(BI, DTU, DL, Options.AC))
-      return requestResimplify();
+  if (foldCondBranchOnValueKnownInPredecessor(BI))
+    return requestResimplify();
 
   // Scan predecessor blocks for conditional branches.
   for (BasicBlock *Pred : predecessors(BB))
diff --git a/llvm/test/Transforms/SimplifyCFG/2025-07-29-non-canoncial-loop.ll b/llvm/test/Transforms/SimplifyCFG/2025-07-29-non-canoncial-loop.ll
index a64108f6346b9..322dd98f48df1 100644
--- a/llvm/test/Transforms/SimplifyCFG/2025-07-29-non-canoncial-loop.ll
+++ b/llvm/test/Transforms/SimplifyCFG/2025-07-29-non-canoncial-loop.ll
@@ -1,37 +1,62 @@
-; RUN: opt < %s -passes=simplifycfg -simplifycfg-require-and-preserve-domtree=1 -S | FileCheck --check-prefix=NO-THREADING %s
-; Checks that we do not thread the control flow through the loop header bb1 as
-; that will introduce a non-canonical loop
-
-; NO-THREADING-LABEL: define void @__start
-; NO-THREADING: bb3:
-; NO-THREADING-NEXT: br i1 %cond, label %bb1, label %bb5
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
+; RUN: opt < %s -passes=simplifycfg -simplifycfg-require-and-preserve-domtree=1 --keep-loops="true" -S | FileCheck --check-prefix=NO-THREADING %s
+; Checks that we do not thread the control flow through the loop header loop_header as
+; that will introduce a non-canonical loop.
 
 ; RUN: opt < %s -passes=simplifycfg -simplifycfg-require-and-preserve-domtree=1 --keep-loops="false" -S | FileCheck --check-prefix=THREADING %s
-; Checks that we thread the control flow through the loop header bb1 since we
-; do not request --keep-loops
-
-; THREADING-LABEL: define void @__start
-; THREADING: bb3:
-; THREADING-NEXT: br i1 %cond, label %bb4, label %bb5
+; Checks that we thread the control flow through the loop header loop_header since we
+; do not request --keep-loops.
 
 define void @__start(i1 %cond) {
+; NO-THREADING-LABEL: define void @__start(
+; NO-THREADING-SAME: i1 [[COND:%.*]]) {
+; NO-THREADING-NEXT:  [[ENTRY:.*:]]
+; NO-THREADING-NEXT:    br label %[[LOOP_HEADER:.*]]
+; NO-THREADING:       [[LOOP_HEADER]]:
+; NO-THREADING-NEXT:    br i1 [[COND]], label %[[LOOP_BODY_1:.*]], label %[[LOOP_BODY_0:.*]]
+; NO-THREADING:       [[LOOP_BODY_0]]:
+; NO-THREADING-NEXT:    [[_0_:%.*]] = add i16 0, 0
+; NO-THREADING-NEXT:    br label %[[LOOP_EXIT:.*]]
+; NO-THREADING:       [[LOOP_BODY_1]]:
+; NO-THREADING-NEXT:    [[_1_:%.*]] = add i32 0, 1
+; NO-THREADING-NEXT:    br label %[[LOOP_EXIT]]
+; NO-THREADING:       [[LOOP_EXIT]]:
+; NO-THREADING-NEXT:    br i1 [[COND]], label %[[LOOP_HEADER]], label %[[EXIT:.*]]
+; NO-THREADING:       [[EXIT]]:
+; NO-THREADING-NEXT:    ret void
+;
+; THREADING-LABEL: define void @__start(
+; THREADING-SAME: i1 [[COND:%.*]]) {
+; THREADING-NEXT:  [[ENTRY:.*:]]
+; THREADING-NEXT:    br i1 [[COND]], label %[[LOOP_BODY_1:.*]], label %[[LOOP_BODY_0:.*]]
+; THREADING:       [[LOOP_BODY_0]]:
+; THREADING-NEXT:    [[_0_:%.*]] = add i16 0, 0
+; THREADING-NEXT:    br label %[[LOOP_EXIT:.*]]
+; THREADING:       [[LOOP_BODY_1]]:
+; THREADING-NEXT:    [[_1_:%.*]] = add i32 0, 1
+; THREADING-NEXT:    br label %[[LOOP_EXIT]]
+; THREADING:       [[LOOP_EXIT]]:
+; THREADING-NEXT:    br i1 [[COND]], label %[[LOOP_BODY_1]], label %[[EXIT:.*]]
+; THREADING:       [[EXIT]]:
+; THREADING-NEXT:    ret void
+;
 entry:
-  br label %bb1
+  br label %loop_header
 
-bb1:                                            ; preds = %bb3, %entry
-  br i1 %cond, label %bb4, label %bb2
+loop_header:                                            ; preds = %loop_exit, %entry
+  br i1 %cond, label %loop_body_1, label %loop_body_0
 
-bb2:                                            ; preds = %bb1
+loop_body_0:                                            ; preds = %loop_header
   %_0_ = add i16 0, 0
-  br label %bb3
+  br label %loop_exit
 
-bb3:                                            ; preds = %bb4, %bb2
-  br i1 %cond, label %bb1, label %bb5
-
-bb4:                                            ; preds = %bb1
+loop_body_1:                                            ; preds = %loop_header
   %_1_ = add i32 0, 1
-  br label %bb3
+  br label %loop_exit
+
+loop_exit:                                            ; preds = %loop_body_1, %loop_body_0
+  br i1 %cond, label %loop_header, label %exit
 
-bb5:                                            ; preds = %bb3
+exit:                                            ; preds = %loop_exit
   ret void
 }



More information about the llvm-commits mailing list