[llvm] [DFAJumpThreading] Propagate DebugLocs to branches in select unfolding (PR #205851)

Stephen Tozer via llvm-commits llvm-commits at lists.llvm.org
Thu Jun 25 09:17:32 PDT 2026


https://github.com/SLTozer created https://github.com/llvm/llvm-project/pull/205851

When DFAJumpThreading replaces a select with control flow, it generates new blocks, new branches in those blocks, and potentially replaces the branch in an existing block. Prior to this patch, none of these branches were assigned debug locations; this patch replaces them as follows:

For the case where we generate two new blocks between the select block and use block, and use a PHI of those blocks to replace the select, we use the select's debug location for the branch instructions, since they are doing the work of the select.

For the case where we generate one new block and replace the unconditional branch from the select block with a conditional branch to the new block and the use block, we treat the new branches as replacing both the select and the original branch, so each branch takes the merged location of the original select+br.

This patch also ensures that when we create a new path which would end with a switch statement, such that the switch statement can be safely replaced with an unconditional branch, we propagate the switch's debug location to the replacing unconditional branch.

>From 612ab2f728d2cabe0106eb0d6d3ca65622281bc4 Mon Sep 17 00:00:00 2001
From: Stephen Tozer <stephen.tozer at sony.com>
Date: Thu, 25 Jun 2026 15:16:41 +0100
Subject: [PATCH] [DFAJumpThreading] Propagate DebugLocs to branches in select
 unfolding

When DFAJumpThreading replaces a select with control flow, it generates new
blocks, new branches in those blocks, and potentially replaces the branch
in an existing block. Prior to this patch, none of these branches were
assigned debug locations; this patch replaces them as follows:

For the case where we generate two new blocks between the select block and
use block, and use a PHI of those blocks to replace the select, we use the
select's debug location for the branch instructions, since they are doing
the work of the select.

For the case where we generate one new block and replace the unconditional
branch from the select block with a conditional branch to the new block and
the use block, we treat the new branches as replacing both the select and
the original branch, so each branch takes the merged location of the
original select+br.

This patch also ensures that when we create a new path which would end with
a switch statement, such that the switch statement can be safely replaced
with an unconditional branch, we propagate the switch's debug location to
the replacing unconditional branch.
---
 .../Transforms/Scalar/DFAJumpThreading.cpp    |  20 +++-
 .../DFAJumpThreading/br-debuglocs.ll          | 101 ++++++++++++++++++
 .../DFAJumpThreading/br-debuglocs2.ll         |  89 +++++++++++++++
 3 files changed, 207 insertions(+), 3 deletions(-)
 create mode 100644 llvm/test/Transforms/DFAJumpThreading/br-debuglocs.ll
 create mode 100644 llvm/test/Transforms/DFAJumpThreading/br-debuglocs2.ll

diff --git a/llvm/lib/Transforms/Scalar/DFAJumpThreading.cpp b/llvm/lib/Transforms/Scalar/DFAJumpThreading.cpp
index 8b2435eefa849..39058cab216c0 100644
--- a/llvm/lib/Transforms/Scalar/DFAJumpThreading.cpp
+++ b/llvm/lib/Transforms/Scalar/DFAJumpThreading.cpp
@@ -217,7 +217,14 @@ void DFAJumpThreading::unfold(DomTreeUpdater *DTU, LoopInfo *LI,
         SI->getContext(), Twine(SI->getName(), ".si.unfold.false"),
         EndBlock->getParent(), EndBlock);
     NewBBs->push_back(NewBlock);
-    UncondBrInst::Create(EndBlock, NewBlock);
+    // The branch from NewBlock and the new CondBr from StartBlock collectively
+    // substitute the existing Select+Br instructions, so following the rules
+    // for updating source locations we assign each of them the merged location
+    // of the Select+Br.
+    DebugLoc SelectBranchLoc = DebugLoc::getMergedLocation(
+        StartBlockTerm->getDebugLoc(), SI->getDebugLoc());
+    Instruction *NewToEndBr = UncondBrInst::Create(EndBlock, NewBlock);
+    NewToEndBr->setDebugLoc(SelectBranchLoc);
     DTU->applyUpdates({{DominatorTree::Insert, NewBlock, EndBlock}});
 
     // StartBlock
@@ -268,6 +275,7 @@ void DFAJumpThreading::unfold(DomTreeUpdater *DTU, LoopInfo *LI,
     StartBlockTerm->eraseFromParent();
     auto *BI =
         CondBrInst::Create(SI->getCondition(), EndBlock, NewBlock, StartBlock);
+    BI->setDebugLoc(SelectBranchLoc);
     if (!ProfcheckDisableMetadataFixes)
       BI->setMetadata(LLVMContext::MD_prof,
                       SI->getMetadata(LLVMContext::MD_prof));
@@ -302,10 +310,15 @@ void DFAJumpThreading::unfold(DomTreeUpdater *DTU, LoopInfo *LI,
     //   |     /
     // EndBlock
     //  (Use)
-    UncondBrInst::Create(EndBlock, NewBlockF);
+    Instruction *NewFToEnd = UncondBrInst::Create(EndBlock, NewBlockF);
     // Insert the real conditional branch based on the original condition.
     auto *BI =
         CondBrInst::Create(SI->getCondition(), EndBlock, NewBlockF, NewBlockT);
+    // The branches from NewBlockT and NewBlockF are performing the Select
+    // logic, and so assume its source location.
+    DebugLoc SelectLoc = SI->getDebugLoc();
+    NewFToEnd->setDebugLoc(SelectLoc);
+    BI->setDebugLoc(SelectLoc);
     if (!ProfcheckDisableMetadataFixes)
       BI->setMetadata(LLVMContext::MD_prof,
                       SI->getMetadata(LLVMContext::MD_prof));
@@ -1409,8 +1422,9 @@ struct TransformDFA {
         DTUpdates.push_back({DominatorTree::Delete, LastBlock, Succ});
     }
 
+    DebugLoc SwitchLoc = Switch->getDebugLoc();
     Switch->eraseFromParent();
-    UncondBrInst::Create(NextCase, LastBlock);
+    UncondBrInst::Create(NextCase, LastBlock)->setDebugLoc(SwitchLoc);
 
     DTU->applyUpdates(DTUpdates);
   }
diff --git a/llvm/test/Transforms/DFAJumpThreading/br-debuglocs.ll b/llvm/test/Transforms/DFAJumpThreading/br-debuglocs.ll
new file mode 100644
index 0000000000000..ae6caf3058f44
--- /dev/null
+++ b/llvm/test/Transforms/DFAJumpThreading/br-debuglocs.ll
@@ -0,0 +1,101 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -S -passes=dfa-jump-threading %s | FileCheck %s
+
+;; Tests that we attempt to propagate debug locations to the generated branch
+;; instructions when we replace a select instruction with control flow.
+;; The first select (%select) is replaced with a pair of new blocks, one using a
+;; condbr and the other using a br, and both take the select's debug location.
+;; The second select (%select3) and the branch from its block are both replaced
+;; with a new condbr and a new block with a br, so both new branches take the
+;; merge of the select and branch debug locations.
+
+source_filename = "reduced.ll"
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-unknown-linux-gnu"
+
+define void @widget() !dbg !5 {
+; CHECK-LABEL: define void @widget(
+; CHECK-SAME: ) !dbg [[DBG5:![0-9]+]] {
+; CHECK-NEXT:  [[BB:.*:]]
+; CHECK-NEXT:    br label %[[BB1:.*]], !dbg [[DBG8:![0-9]+]]
+; CHECK:       [[BB1]]:
+; CHECK-NEXT:    br i1 false, label %[[BB2:.*]], label %[[BB4:.*]], !dbg [[DBG9:![0-9]+]]
+; CHECK:       [[BB2]]:
+; CHECK-NEXT:    br i1 false, label %[[BB4]], label %[[SELECT_SI_UNFOLD_TRUE:.*]], !dbg [[DBG10:![0-9]+]]
+; CHECK:       [[SELECT_SI_UNFOLD_TRUE]]:
+; CHECK-NEXT:    [[DOTSI_UNFOLD_PHI:%.*]] = phi i32 [ 0, %[[BB2]] ]
+; CHECK-NEXT:    br i1 false, label %[[SELECT3_SI_UNFOLD_FALSE:.*]], label %[[SELECT_SI_UNFOLD_FALSE:.*]], !dbg [[DBG11:![0-9]+]]
+; CHECK:       [[SELECT_SI_UNFOLD_FALSE]]:
+; CHECK-NEXT:    [[DOTSI_UNFOLD_PHI1:%.*]] = phi i32 [ 0, %[[SELECT_SI_UNFOLD_TRUE]] ]
+; CHECK-NEXT:    br label %[[SELECT3_SI_UNFOLD_FALSE]], !dbg [[DBG11]]
+; CHECK:       [[SELECT3_SI_UNFOLD_FALSE]]:
+; CHECK-NEXT:    [[SELECT_SI_UNFOLD_PHI:%.*]] = phi i32 [ [[DOTSI_UNFOLD_PHI1]], %[[SELECT_SI_UNFOLD_FALSE]] ], [ [[DOTSI_UNFOLD_PHI]], %[[SELECT_SI_UNFOLD_TRUE]] ]
+; CHECK-NEXT:    br label %[[BB4]], !dbg [[DBG10]]
+; CHECK:       [[BB4]]:
+; CHECK-NEXT:    [[PHI:%.*]] = phi i32 [ 0, %[[BB2]] ], [ 0, %[[BB1]] ], [ [[SELECT_SI_UNFOLD_PHI]], %[[SELECT3_SI_UNFOLD_FALSE]] ], !dbg [[DBG12:![0-9]+]]
+; CHECK-NEXT:    br label %[[BB5:.*]], !dbg [[DBG13:![0-9]+]]
+; CHECK:       [[BB5]]:
+; CHECK-NEXT:    switch i32 [[PHI]], label %[[BB6:.*]] [
+; CHECK-NEXT:    ], !dbg [[DBG14:![0-9]+]]
+; CHECK:       [[BB6]]:
+; CHECK-NEXT:    br label %[[BB5]], !dbg [[DBG15:![0-9]+]]
+;
+bb:
+  br label %bb1, !dbg !8
+
+bb1:                                              ; preds = %bb
+  br i1 false, label %bb2, label %bb4, !dbg !9
+
+bb2:                                              ; preds = %bb1
+  %select = select i1 false, i32 0, i32 0, !dbg !10
+  %select3 = select i1 false, i32 0, i32 %select, !dbg !11
+  br label %bb4, !dbg !12
+
+bb4:                                              ; preds = %bb2, %bb1
+  %phi = phi i32 [ %select3, %bb2 ], [ 0, %bb1 ], !dbg !13
+  br label %bb5, !dbg !14
+
+bb5:                                              ; preds = %bb6, %bb4
+  switch i32 %phi, label %bb6 [
+  ], !dbg !15
+
+bb6:                                              ; preds = %bb5
+  br label %bb5, !dbg !16
+}
+
+!llvm.dbg.cu = !{!0}
+!llvm.debugify = !{!2, !3}
+!llvm.module.flags = !{!4}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C, file: !1, producer: "debugify", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug)
+!1 = !DIFile(filename: "reduced.ll", directory: "/")
+!2 = !{i32 9}
+!3 = !{i32 0}
+!4 = !{i32 2, !"Debug Info Version", i32 3}
+!5 = distinct !DISubprogram(name: "widget", linkageName: "widget", scope: null, file: !1, line: 1, type: !6, scopeLine: 1, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0)
+!6 = !DISubroutineType(types: !7)
+!7 = !{}
+!8 = !DILocation(line: 1, column: 1, scope: !5)
+!9 = !DILocation(line: 2, column: 1, scope: !5)
+!10 = !DILocation(line: 3, column: 1, scope: !5)
+!11 = !DILocation(line: 4, column: 1, scope: !5)
+!12 = !DILocation(line: 4, column: 5, scope: !5)
+!13 = !DILocation(line: 6, column: 1, scope: !5)
+!14 = !DILocation(line: 7, column: 1, scope: !5)
+!15 = !DILocation(line: 8, column: 1, scope: !5)
+!16 = !DILocation(line: 9, column: 1, scope: !5)
+;.
+; CHECK: [[META0:![0-9]+]] = distinct !DICompileUnit(language: DW_LANG_C, file: [[META1:![0-9]+]], producer: "debugify", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug)
+; CHECK: [[META1]] = !DIFile(filename: "{{.*}}reduced.ll", directory: {{.*}})
+; CHECK: [[DBG5]] = distinct !DISubprogram(name: "widget", linkageName: "widget", scope: null, file: [[META1]], line: 1, type: [[META6:![0-9]+]], scopeLine: 1, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: [[META0]])
+; CHECK: [[META6]] = !DISubroutineType(types: [[META7:![0-9]+]])
+; CHECK: [[META7]] = !{}
+; CHECK: [[DBG8]] = !DILocation(line: 1, column: 1, scope: [[DBG5]])
+; CHECK: [[DBG9]] = !DILocation(line: 2, column: 1, scope: [[DBG5]])
+; CHECK: [[DBG10]] = !DILocation(line: 4, scope: [[DBG5]])
+; CHECK: [[DBG11]] = !DILocation(line: 3, column: 1, scope: [[DBG5]])
+; CHECK: [[DBG12]] = !DILocation(line: 6, column: 1, scope: [[DBG5]])
+; CHECK: [[DBG13]] = !DILocation(line: 7, column: 1, scope: [[DBG5]])
+; CHECK: [[DBG14]] = !DILocation(line: 8, column: 1, scope: [[DBG5]])
+; CHECK: [[DBG15]] = !DILocation(line: 9, column: 1, scope: [[DBG5]])
+;.
diff --git a/llvm/test/Transforms/DFAJumpThreading/br-debuglocs2.ll b/llvm/test/Transforms/DFAJumpThreading/br-debuglocs2.ll
new file mode 100644
index 0000000000000..c666fd8fffcbd
--- /dev/null
+++ b/llvm/test/Transforms/DFAJumpThreading/br-debuglocs2.ll
@@ -0,0 +1,89 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -S -passes=dfa-jump-threading %s | FileCheck %s
+
+;; Tests that when we perform jump threading, creating a new path which doesn't
+;; require a switch statement at its end, we keep the switch statement's
+;; DebugLoc on the final unconditional branch, since the unconditional branch
+;; represents the switch (with a known outcome).
+
+source_filename = "reduced.ll"
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-unknown-linux-gnu"
+
+define i32 @widget() !dbg !5 {
+; CHECK-LABEL: define i32 @widget(
+; CHECK-SAME: ) !dbg [[DBG5:![0-9]+]] {
+; CHECK-NEXT:  [[BB:.*:]]
+; CHECK-NEXT:    br label %[[BB1:.*]], !dbg [[DBG8:![0-9]+]]
+; CHECK:       [[BB1]]:
+; CHECK-NEXT:    switch i32 0, label %[[BB2_JT0:.*]] [
+; CHECK-NEXT:      i32 -2147467260, label %[[BB2_JT0]]
+; CHECK-NEXT:    ], !dbg [[DBG9:![0-9]+]]
+; CHECK:       [[BB2:.*:]]
+; CHECK-NEXT:    br label %[[BB3:.*]], !dbg [[DBG10:![0-9]+]]
+; CHECK:       [[BB2_JT0]]:
+; CHECK-NEXT:    [[PHI_JT0:%.*]] = phi i32 [ 0, %[[BB1]] ], [ 0, %[[BB1]] ], !dbg [[DBG11:![0-9]+]]
+; CHECK-NEXT:    br label %[[BB3_JT0:.*]], !dbg [[DBG10]]
+; CHECK:       [[BB3]]:
+; CHECK-NEXT:    switch i32 undef, label %[[BB4:.*]] [
+; CHECK-NEXT:      i32 0, label %[[BB4]]
+; CHECK-NEXT:      i32 4, label %[[BB4]]
+; CHECK-NEXT:    ], !dbg [[DBG12:![0-9]+]]
+; CHECK:       [[BB3_JT0]]:
+; CHECK-NEXT:    br label %[[BB4]], !dbg [[DBG12]]
+; CHECK:       [[BB4]]:
+; CHECK-NEXT:    br label %[[BB1]], !dbg [[DBG13:![0-9]+]]
+;
+bb:
+  br label %bb1, !dbg !8
+
+bb1:                                              ; preds = %bb4, %bb
+  switch i32 0, label %bb2 [
+  i32 -2147467260, label %bb2
+  ], !dbg !9
+
+bb2:                                              ; preds = %bb1, %bb1
+  %phi = phi i32 [ 0, %bb1 ], [ 0, %bb1 ], !dbg !10
+  br label %bb3, !dbg !11
+
+bb3:                                              ; preds = %bb2
+  switch i32 %phi, label %bb4 [
+  i32 0, label %bb4
+  i32 4, label %bb4
+  ], !dbg !12
+
+bb4:                                              ; preds = %bb3, %bb3, %bb3
+  br label %bb1, !dbg !13
+}
+
+!llvm.dbg.cu = !{!0}
+!llvm.debugify = !{!2, !3}
+!llvm.module.flags = !{!4}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C, file: !1, producer: "debugify", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug)
+!1 = !DIFile(filename: "reduced.ll", directory: "/")
+!2 = !{i32 6}
+!3 = !{i32 0}
+!4 = !{i32 2, !"Debug Info Version", i32 3}
+!5 = distinct !DISubprogram(name: "widget", linkageName: "widget", scope: null, file: !1, line: 1, type: !6, scopeLine: 1, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0)
+!6 = !DISubroutineType(types: !7)
+!7 = !{}
+!8 = !DILocation(line: 1, column: 1, scope: !5)
+!9 = !DILocation(line: 2, column: 1, scope: !5)
+!10 = !DILocation(line: 3, column: 1, scope: !5)
+!11 = !DILocation(line: 4, column: 1, scope: !5)
+!12 = !DILocation(line: 5, column: 1, scope: !5)
+!13 = !DILocation(line: 6, column: 1, scope: !5)
+;.
+; CHECK: [[META0:![0-9]+]] = distinct !DICompileUnit(language: DW_LANG_C, file: [[META1:![0-9]+]], producer: "debugify", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug)
+; CHECK: [[META1]] = !DIFile(filename: "{{.*}}reduced.ll", directory: {{.*}})
+; CHECK: [[DBG5]] = distinct !DISubprogram(name: "widget", linkageName: "widget", scope: null, file: [[META1]], line: 1, type: [[META6:![0-9]+]], scopeLine: 1, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: [[META0]])
+; CHECK: [[META6]] = !DISubroutineType(types: [[META7:![0-9]+]])
+; CHECK: [[META7]] = !{}
+; CHECK: [[DBG8]] = !DILocation(line: 1, column: 1, scope: [[DBG5]])
+; CHECK: [[DBG9]] = !DILocation(line: 2, column: 1, scope: [[DBG5]])
+; CHECK: [[DBG10]] = !DILocation(line: 4, column: 1, scope: [[DBG5]])
+; CHECK: [[DBG11]] = !DILocation(line: 3, column: 1, scope: [[DBG5]])
+; CHECK: [[DBG12]] = !DILocation(line: 5, column: 1, scope: [[DBG5]])
+; CHECK: [[DBG13]] = !DILocation(line: 6, column: 1, scope: [[DBG5]])
+;.



More information about the llvm-commits mailing list