[llvm] [SimplifyCFG] correct and move debug info for mergeConditionalStoreToAddress (PR #180789)

Jameson Nash via llvm-commits llvm-commits at lists.llvm.org
Tue Feb 10 09:35:15 PST 2026


https://github.com/vtjnash created https://github.com/llvm/llvm-project/pull/180789

Previously, a combination of TryToSimplifyUncondBranchFromEmptyBlock and SpeculatedStoreValue was changing the separate conditional stores into a store of one value, which was then being hoisted to a non-conditional store of that one value (and a DCE of the other). This makes all linked stores use the new value, which is unconditionally correct. It isn't easy for TryToSimplifyUncondBranchFromEmptyBlock to otherwise guess why the value is different and try to recover which one is correct when doing the conditional update. The end result being that the debug info might have the wrong value. Now instead this updates the debug info at the same time to reflect that the merged store will be equivalent, hoping to turn these into the same info. This ensures that later passes don't need to reverse how the different stores connected back to the new IR, since either debug info now contains correct information for either branch taken. (Is this the correct way to do this? I also tried to find ways to preserve SSA, but AssignmentTracking.md says not to move this; and ways to get TryToSimplifyUncondBranchFromEmptyBlock and SpeculatedStoreValue to describe the conditional store, but there's no MemSSA-like hypothetical structure linking a dbg_value to a previous dbg_value to be able to use for the conditional descriptor)

And additionally, without `combineMetadataForCSE`, it was dropping the debug assignment for the merged store.

>From 73949a3f6f4e450126df573c0e2c3518f78e37bb Mon Sep 17 00:00:00 2001
From: Jameson Nash <vtjnash at gmail.com>
Date: Tue, 10 Feb 2026 15:07:44 +0000
Subject: [PATCH] [SimplifyCFG] correct and move debug info for
 mergeConditionalStoreToAddress

Previously, a combination of TryToSimplifyUncondBranchFromEmptyBlock and
SpeculatedStoreValue was changing the separate conditional stores into a
store of one value, which was then being hoisted to a non-conditional
store of that one value (and a DCE of the other). This makes all linked
stores use the new value, which is unconditionally correct. It isn't
easy for TryToSimplifyUncondBranchFromEmptyBlock to otherwise guess why
the value is different and try to recover which one is correct when
doing the conditional update. The end result being that the debug info
might have the wrong value. Now instead this updates the debug info at
the same time to reflect that the merged store will be equivalent,
hoping to turn these into the same info. This ensures that later passes
don't need to reverse how the different stores connected back to the new
IR, since either debug info now contains correct information for either
branch taken.

And additionally, without `combineMetadataForCSE`, it was dropping the
debug assignment for the merged store.
---
 llvm/lib/Transforms/Utils/SimplifyCFG.cpp     | 16 +++-
 .../merge-cond-stores-debuginfo.ll            | 78 +++++++++++++++++++
 2 files changed, 92 insertions(+), 2 deletions(-)
 create mode 100644 llvm/test/Transforms/SimplifyCFG/merge-cond-stores-debuginfo.ll

diff --git a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
index 8b56109990b21..eb730a800cc95 100644
--- a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
+++ b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
@@ -3346,7 +3346,7 @@ bool SimplifyCFGOpt::speculativelyExecuteBB(BranchInst *BI,
     SpeculatedStore->applyMergedLocation(BI->getDebugLoc(),
                                          SpeculatedStore->getDebugLoc());
     // The value stored is still conditional, but the store itself is now
-    // unconditonally executed, so we must be sure that any linked dbg.assign
+    // unconditionally executed, so we must be sure that any linked dbg.assign
     // intrinsics are tracking the new stored value (the result of the
     // select). If we don't, and the store were to be removed by another pass
     // (e.g. DSE), then we'd eventually end up emitting a location describing
@@ -4465,7 +4465,19 @@ static bool mergeConditionalStoreToAddress(
 
   QB.SetInsertPoint(T);
   StoreInst *SI = cast<StoreInst>(QB.CreateStore(QPHI, Address));
-  SI->setAAMetadata(PStore->getAAMetadata().merge(QStore->getAAMetadata()));
+  combineMetadataForCSE(QStore, PStore, true);
+  SI->copyMetadata(*QStore);
+  // Update any dbg.assign intrinsics to track the merged value (QPHI) instead
+  // of the original constant values, likely making these identical.
+  for (auto *DbgAssign : at::getDVRAssignmentMarkers(SI)) {
+    if (llvm::is_contained(DbgAssign->location_ops(),
+                           PStore->getValueOperand()))
+      DbgAssign->replaceVariableLocationOp(PStore->getValueOperand(), QPHI);
+    if (llvm::is_contained(DbgAssign->location_ops(),
+                           QStore->getValueOperand()))
+      DbgAssign->replaceVariableLocationOp(QStore->getValueOperand(), QPHI);
+  }
+
   // Choose the minimum alignment. If we could prove both stores execute, we
   // could use biggest one.  In this case, though, we only know that one of the
   // stores executes.  And we don't know it's safe to take the alignment from a
diff --git a/llvm/test/Transforms/SimplifyCFG/merge-cond-stores-debuginfo.ll b/llvm/test/Transforms/SimplifyCFG/merge-cond-stores-debuginfo.ll
new file mode 100644
index 0000000000000..4eb8028a9e19e
--- /dev/null
+++ b/llvm/test/Transforms/SimplifyCFG/merge-cond-stores-debuginfo.ll
@@ -0,0 +1,78 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt -passes=simplifycfg < %s -S | FileCheck %s
+
+; Test that SimplifyCFG properly preserves debug info when merging conditional stores.
+; This test verifies that:
+; 1. DIAssignID metadata is properly merged.
+; 2. The hoisted dbg_assign value correctly represents the merged store value.
+
+define void @merge_stores_different_values(ptr %p, i32 %a, i32 %b) !dbg !10 {
+; CHECK-LABEL: @merge_stores_different_values(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[X1:%.*]] = icmp eq i32 [[A:%.*]], 0, !dbg [[DBG15:![0-9]+]]
+; CHECK-NEXT:    [[X2:%.*]] = icmp eq i32 [[B:%.*]], 0, !dbg [[DBG16:![0-9]+]]
+; CHECK-NEXT:      #dbg_assign(i32 [[SPEC_SELECT:%.*]], [[META17:![0-9]+]], !DIExpression(), [[META18:![0-9]+]], ptr [[P:%.*]], !DIExpression(), [[META19:![0-9]+]])
+; CHECK-NEXT:    [[SPEC_SELECT]] = select i1 [[X2]], i32 200, i32 100, !dbg [[DBG20:![0-9]+]]
+; CHECK-NEXT:    [[TMP0:%.*]] = xor i1 [[X1]], true, !dbg [[DBG21:![0-9]+]]
+; CHECK-NEXT:    [[TMP1:%.*]] = xor i1 [[X2]], true, !dbg [[DBG21]]
+; CHECK-NEXT:    [[TMP2:%.*]] = or i1 [[TMP0]], [[TMP1]], !dbg [[DBG21]]
+; CHECK-NEXT:    br i1 [[TMP2]], label [[TMP3:%.*]], label [[TMP4:%.*]], !dbg [[DBG21]]
+; CHECK:       3:
+; CHECK-NEXT:    store i32 [[SPEC_SELECT]], ptr [[P]], align 4, !dbg [[META19]], !DIAssignID [[META18]]
+; CHECK-NEXT:    br label [[TMP4]], !dbg [[DBG21]]
+; CHECK:       4:
+; CHECK-NEXT:    ret void, !dbg [[DBG21]]
+;
+entry:
+  %x1 = icmp eq i32 %a, 0, !dbg !15
+  br i1 %x1, label %fallthrough, label %yes1, !dbg !16
+
+yes1:                                             ; preds = %entry
+  store i32 200, ptr %p, align 4, !dbg !17, !DIAssignID !18
+    #dbg_assign(i32 200, !19, !DIExpression(), !18, ptr %p, !DIExpression(), !17)
+  br label %fallthrough, !dbg !20
+
+fallthrough:                                      ; preds = %yes1, %entry
+  %x2 = icmp eq i32 %b, 0, !dbg !21
+  br i1 %x2, label %end, label %yes2, !dbg !22
+
+yes2:                                             ; preds = %fallthrough
+  store i32 100, ptr %p, align 4, !dbg !23, !DIAssignID !24
+    #dbg_assign(i32 100, !19, !DIExpression(), !24, ptr %p, !DIExpression(), !23)
+  br label %end, !dbg !25
+
+end:                                              ; preds = %yes2, %fallthrough
+  ret void, !dbg !26
+}
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!2, !3, !4, !5, !6, !7, !8}
+!llvm.ident = !{!9}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C11, file: !1, producer: "clang version 19.0.0", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None)
+!1 = !DIFile(filename: "test.c", directory: "/tmp")
+!2 = !{i32 7, !"Dwarf Version", i32 5}
+!3 = !{i32 2, !"Debug Info Version", i32 3}
+!4 = !{i32 1, !"wchar_size", i32 4}
+!5 = !{i32 8, !"PIC Level", i32 2}
+!6 = !{i32 7, !"PIE Level", i32 2}
+!7 = !{i32 7, !"uwtable", i32 2}
+!8 = !{i32 7, !"debug-info-assignment-tracking", i1 true}
+!9 = !{!"clang version 19.0.0"}
+!10 = distinct !DISubprogram(name: "merge_stores_different_values", scope: !1, file: !1, line: 10, type: !11, scopeLine: 10, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !14)
+!11 = !DISubroutineType(types: !12)
+!12 = !{null, !13, !13, !13}
+!13 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+!14 = !{}
+!15 = !DILocation(line: 12, column: 7, scope: !10)
+!16 = !DILocation(line: 12, column: 5, scope: !10)
+!17 = !DILocation(line: 13, column: 12, scope: !10)
+!18 = distinct !DIAssignID()
+!19 = !DILocalVariable(name: "val", scope: !10, file: !1, line: 11, type: !13)
+!20 = !DILocation(line: 13, column: 5, scope: !10)
+!21 = !DILocation(line: 14, column: 7, scope: !10)
+!22 = !DILocation(line: 14, column: 5, scope: !10)
+!23 = !DILocation(line: 15, column: 12, scope: !10)
+!24 = distinct !DIAssignID()
+!25 = !DILocation(line: 15, column: 5, scope: !10)
+!26 = !DILocation(line: 16, column: 1, scope: !10)



More information about the llvm-commits mailing list