[llvm] Merge debug locations when combining stores (PR #225358)

via llvm-commits llvm-commits at lists.llvm.org
Mon Sep 28 05:17:14 PDT 2026


https://github.com/anatasluo updated https://github.com/llvm/llvm-project/pull/225358

>From 35136ee796f7a72f829f2667c7b51dfc18d0b238 Mon Sep 17 00:00:00 2001
From: Longjun Luo <luolongjuna at gmail.com>
Date: Tue, 22 Sep 2026 09:47:59 +0000
Subject: [PATCH 1/2] Merge debug locations when combining stores

Consecutive store merging inherits the lowest-addressed operation's
location, which can attribute a widened memory access to the wrong
inline frame.

Merge locations for constant, extracted-vector-element and load-source
stores using the existing DebugLoc policy. Merge load and store locations
separately, considering only the operations actually combined. Preserve
IR order and leave auxiliary value nodes' locations unchanged.

Add coverage for sibling scopes, identical and missing locations,
independent load/store locations, and partially merged candidate groups.

Assisted-by: OpenAI Codex
---
 llvm/include/llvm/CodeGen/SelectionDAGNodes.h |   5 +
 llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp |  21 ++-
 .../X86/sdag-merge-stores-debugloc.ll         | 178 ++++++++++++++++++
 3 files changed, 199 insertions(+), 5 deletions(-)
 create mode 100644 llvm/test/DebugInfo/X86/sdag-merge-stores-debugloc.ll

diff --git a/llvm/include/llvm/CodeGen/SelectionDAGNodes.h b/llvm/include/llvm/CodeGen/SelectionDAGNodes.h
index 667c98f086827..fede733a5ffcd 100644
--- a/llvm/include/llvm/CodeGen/SelectionDAGNodes.h
+++ b/llvm/include/llvm/CodeGen/SelectionDAGNodes.h
@@ -1266,6 +1266,11 @@ class SDLoc {
 
   unsigned getIROrder() const { return IROrder; }
   const DebugLoc &getDebugLoc() const { return DL; }
+
+  /// Merge the source location without changing the IR order.
+  void mergeDebugLoc(const DebugLoc &Other) {
+    DL = DebugLoc::getMergedLocation(DL, Other);
+  }
 };
 
 // Define inline functions from the SDValue class.
diff --git a/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp b/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
index 17e2ab01bc11f..71a36bdbb1924 100644
--- a/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
@@ -23256,8 +23256,10 @@ bool DAGCombiner::mergeStoresOfConstantsOrVecElts(
   assert((!UseTrunc || !UseVector) &&
          "This optimization cannot emit a vector truncating store");
 
-  // The latest Node in the DAG.
+  // Preserve the lowest-addressed store's IR order. Merge source locations for
+  // the new store only, leaving value construction unchanged.
   SDLoc DL(StoreNodes[0].MemNode);
+  SDLoc StoreDL = DL;
 
   TypeSize ElementSizeBits = MemVT.getStoreSizeInBits();
   unsigned SizeInBits = NumStores * ElementSizeBits;
@@ -23275,6 +23277,7 @@ bool DAGCombiner::mergeStoresOfConstantsOrVecElts(
     // Skip merging if there's an inconsistent flag.
     if (Flags != St->getMemOperand()->getFlags())
       return false;
+    StoreDL.mergeDebugLoc(St->getDebugLoc());
     // Concatenate AA metadata.
     AAInfo = AAInfo.concat(St->getAAInfo());
   }
@@ -23412,7 +23415,7 @@ bool DAGCombiner::mergeStoresOfConstantsOrVecElts(
   SDValue NewStore;
   if (!UseTrunc) {
     NewStore = DAG.getStore(
-        NewChain, DL, StoredVal, FirstInChain->getBasePtr(),
+        NewChain, StoreDL, StoredVal, FirstInChain->getBasePtr(),
         CanReusePtrInfo
             ? FirstInChain->getPointerInfo()
             : MachinePointerInfo(FirstInChain->getPointerInfo().getAddrSpace()),
@@ -23426,7 +23429,7 @@ bool DAGCombiner::mergeStoresOfConstantsOrVecElts(
         DAG.getConstant(C->getAPIntValue().zextOrTrunc(LegalizedStoreSize), DL,
                         LegalizedStoredValTy);
     NewStore = DAG.getTruncStore(
-        NewChain, DL, ExtendedStoreVal, FirstInChain->getBasePtr(),
+        NewChain, StoreDL, ExtendedStoreVal, FirstInChain->getBasePtr(),
         CanReusePtrInfo
             ? FirstInChain->getPointerInfo()
             : MachinePointerInfo(FirstInChain->getPointerInfo().getAddrSpace()),
@@ -24183,6 +24186,13 @@ bool DAGCombiner::tryStoreMergeOfLoads(SmallVectorImpl<MemOpLink> &StoreNodes,
 
     SDLoc LoadDL(LoadNodes[0].MemNode);
     SDLoc StoreDL(StoreNodes[0].MemNode);
+    SDLoc ValueDL = LoadDL;
+    // Merge loads and stores independently, preserving the IR order of each
+    // group's lowest-addressed operation. Exclude candidates left unmerged.
+    for (unsigned I = 1; I != NumElem; ++I) {
+      LoadDL.mergeDebugLoc(LoadNodes[I].MemNode->getDebugLoc());
+      StoreDL.mergeDebugLoc(StoreNodes[I].MemNode->getDebugLoc());
+    }
 
     // The merged loads are required to have the same incoming chain, so
     // using the first's chain is acceptable.
@@ -24216,9 +24226,10 @@ bool DAGCombiner::tryStoreMergeOfLoads(SmallVectorImpl<MemOpLink> &StoreNodes,
         assert(JointMemOpVT == EVT::getIntegerVT(Context, LoadWidth) &&
                "Unexpected type for rotate-able load pair");
         SDValue RotAmt =
-            DAG.getShiftAmountConstant(LoadWidth / 2, JointMemOpVT, LoadDL);
+            DAG.getShiftAmountConstant(LoadWidth / 2, JointMemOpVT, ValueDL);
         // Target can convert to the identical ROTR if it does not have ROTL.
-        StoreOp = DAG.getNode(ISD::ROTL, LoadDL, JointMemOpVT, NewLoad, RotAmt);
+        StoreOp =
+            DAG.getNode(ISD::ROTL, ValueDL, JointMemOpVT, NewLoad, RotAmt);
       }
       NewStore = DAG.getStore(
           NewStoreChain, StoreDL, StoreOp, FirstInChain->getBasePtr(),
diff --git a/llvm/test/DebugInfo/X86/sdag-merge-stores-debugloc.ll b/llvm/test/DebugInfo/X86/sdag-merge-stores-debugloc.ll
new file mode 100644
index 0000000000000..d7b73050a62ba
--- /dev/null
+++ b/llvm/test/DebugInfo/X86/sdag-merge-stores-debugloc.ll
@@ -0,0 +1,178 @@
+; RUN: llc -O2 -mtriple=x86_64-unknown-linux-gnu -verify-machineinstrs -stop-after=finalize-isel -o - %s | FileCheck %s
+
+;; Consecutive stores combined into a single store must merge their locations,
+;; rather than attributing all effects to the lowest-addressed store's scope.
+
+; CHECK-DAG: ![[CALLER:[0-9]+]] = distinct !DISubprogram(name: "siblings",
+; CHECK-DAG: ![[FIRST:[0-9]+]] = distinct !DISubprogram(name: "clear_first",
+; CHECK-DAG: ![[SECOND:[0-9]+]] = distinct !DISubprogram(name: "clear_second",
+; CHECK-DAG: ![[SAME_CALL:[0-9]+]] = !DILocation(line: 21, column: 3, scope: ![[#]])
+; CHECK-DAG: ![[SAME:[0-9]+]] = !DILocation(line: 4, column: 3, scope: ![[FIRST]], inlinedAt: ![[SAME_CALL]])
+; CHECK-DAG: ![[PARTIAL_CALL:[0-9]+]] = !DILocation(line: 41, column: 3, scope: ![[#]])
+; CHECK-DAG: ![[PARTIAL:[0-9]+]] = !DILocation(line: 4, column: 3, scope: ![[FIRST]], inlinedAt: ![[PARTIAL_CALL]])
+; CHECK-DAG: ![[EXTRACT:[0-9]+]] = distinct !DISubprogram(name: "extract_siblings",
+; CHECK-DAG: ![[COPY:[0-9]+]] = distinct !DISubprogram(name: "copy_siblings",
+; CHECK-DAG: ![[INDEPENDENT:[0-9]+]] = distinct !DISubprogram(name: "copy_independent_locations",
+; CHECK-DAG: ![[LOAD_CALL:[0-9]+]] = !DILocation(line: 71, column: 3, scope: ![[INDEPENDENT]])
+; CHECK-DAG: ![[LOAD:[0-9]+]] = !DILocation(line: 4, column: 3, scope: ![[FIRST]], inlinedAt: ![[LOAD_CALL]])
+; CHECK-DAG: ![[STORE_CALL:[0-9]+]] = !DILocation(line: 72, column: 3, scope: ![[INDEPENDENT]])
+; CHECK-DAG: ![[STORE:[0-9]+]] = !DILocation(line: 7, column: 3, scope: ![[SECOND]], inlinedAt: ![[STORE_CALL]])
+; CHECK-DAG: ![[COPY_PARTIAL_CALL:[0-9]+]] = !DILocation(line: 81, column: 3, scope: ![[#]])
+; CHECK-DAG: ![[COPY_PARTIAL:[0-9]+]] = !DILocation(line: 4, column: 3, scope: ![[FIRST]], inlinedAt: ![[COPY_PARTIAL_CALL]])
+
+; CHECK-LABEL: name: siblings
+; CHECK: MOV64mi32 {{.*}}, debug-location !DILocation(line: 0, scope: ![[CALLER]]) :: (store (s64)
+define void @siblings(ptr %p) !dbg !10 {
+  store i32 0, ptr %p, align 4, !dbg !13
+  %q = getelementptr i32, ptr %p, i64 1
+  store i32 0, ptr %q, align 4, !dbg !14
+  ret void
+}
+
+;; Identical locations retain their line and inline scope.
+; CHECK-LABEL: name: same_location
+; CHECK: MOV64mi32 {{.*}}, debug-location ![[SAME]] :: (store (s64)
+define void @same_location(ptr %p) !dbg !20 {
+  store i32 0, ptr %p, align 4, !dbg !22
+  %q = getelementptr i32, ptr %p, i64 1
+  store i32 0, ptr %q, align 4, !dbg !22
+  ret void
+}
+
+;; Do not retain one store's scope when the other's location is unknown.
+; CHECK-LABEL: name: missing_location
+; CHECK: MOV64mi32
+; CHECK-NOT: debug-location
+; CHECK-SAME: :: (store (s64)
+define void @missing_location(ptr %p) !dbg !30 {
+  store i32 0, ptr %p, align 4, !dbg !32
+  %q = getelementptr i32, ptr %p, i64 1
+  store i32 0, ptr %q, align 4
+  ret void
+}
+
+;; Only the first two candidates are merged. Do not include the third store's
+;; location when computing the wider store's location.
+; CHECK-LABEL: name: partial_merge
+; CHECK-DAG: MOV64mi32 {{.*}}, debug-location ![[PARTIAL]] :: (store (s64)
+; CHECK-DAG: MOV32mi
+define void @partial_merge(ptr %p) !dbg !40 {
+  store i32 0, ptr %p, align 4, !dbg !43
+  %q = getelementptr i32, ptr %p, i64 1
+  store i32 0, ptr %q, align 4, !dbg !43
+  %r = getelementptr i32, ptr %p, i64 2
+  store i32 0, ptr %r, align 4, !dbg !44
+  ret void
+}
+
+;; Extracted elements use the same merged-location policy as constants.
+; CHECK-LABEL: name: extract_siblings
+; CHECK: debug-location !DILocation(line: 0, scope: ![[EXTRACT]]) :: (store (s128)
+define void @extract_siblings(ptr %p, <4 x i32> %v) !dbg !50 {
+  %a = extractelement <4 x i32> %v, i64 0
+  %b = extractelement <4 x i32> %v, i64 1
+  %c = extractelement <4 x i32> %v, i64 2
+  %d = extractelement <4 x i32> %v, i64 3
+  store i32 %a, ptr %p, align 16, !dbg !53
+  %q = getelementptr i32, ptr %p, i64 1
+  store i32 %b, ptr %q, align 4, !dbg !53
+  %r = getelementptr i32, ptr %p, i64 2
+  store i32 %c, ptr %r, align 8, !dbg !54
+  %s = getelementptr i32, ptr %p, i64 3
+  store i32 %d, ptr %s, align 4, !dbg !54
+  ret void
+}
+
+;; Both the widened load and store must account for all merged operations.
+; CHECK-LABEL: name: copy_siblings
+; CHECK: MOV64rm {{.*}}, debug-location !DILocation(line: 0, scope: ![[COPY]]) :: (load (s64)
+; CHECK: MOV64mr {{.*}}, debug-location !DILocation(line: 0, scope: ![[COPY]]) :: (store (s64)
+define void @copy_siblings(ptr noalias %d, ptr noalias %s) !dbg !60 {
+  %s1 = getelementptr i32, ptr %s, i64 1
+  %d1 = getelementptr i32, ptr %d, i64 1
+  %a = load i32, ptr %s, align 4, !dbg !63
+  %b = load i32, ptr %s1, align 4, !dbg !64
+  store i32 %a, ptr %d, align 4, !dbg !63
+  store i32 %b, ptr %d1, align 4, !dbg !64
+  ret void
+}
+
+;; Load and store locations must not be mixed together.
+; CHECK-LABEL: name: copy_independent_locations
+; CHECK: MOV64rm {{.*}}, debug-location ![[LOAD]] :: (load (s64)
+; CHECK: MOV64mr {{.*}}, debug-location ![[STORE]] :: (store (s64)
+define void @copy_independent_locations(ptr noalias %d, ptr noalias %s) !dbg !70 {
+  %s1 = getelementptr i32, ptr %s, i64 1
+  %d1 = getelementptr i32, ptr %d, i64 1
+  %a = load i32, ptr %s, align 4, !dbg !73
+  %b = load i32, ptr %s1, align 4, !dbg !73
+  store i32 %a, ptr %d, align 4, !dbg !74
+  store i32 %b, ptr %d1, align 4, !dbg !74
+  ret void
+}
+
+;; The third copy is not merged and must not affect either widened operation.
+; CHECK-LABEL: name: copy_partial_merge
+; CHECK-DAG: MOV64rm {{.*}}, debug-location ![[COPY_PARTIAL]] :: (load (s64)
+; CHECK-DAG: MOV64mr {{.*}}, debug-location ![[COPY_PARTIAL]] :: (store (s64)
+; CHECK-DAG: MOV32rm
+; CHECK-DAG: MOV32mr
+define void @copy_partial_merge(ptr noalias %d, ptr noalias %s) !dbg !80 {
+  %s1 = getelementptr i32, ptr %s, i64 1
+  %s2 = getelementptr i32, ptr %s, i64 2
+  %d1 = getelementptr i32, ptr %d, i64 1
+  %d2 = getelementptr i32, ptr %d, i64 2
+  %a = load i32, ptr %s, align 4, !dbg !83
+  %b = load i32, ptr %s1, align 4, !dbg !83
+  %c = load i32, ptr %s2, align 4, !dbg !84
+  store i32 %a, ptr %d, align 4, !dbg !83
+  store i32 %b, ptr %d1, align 4, !dbg !83
+  store i32 %c, ptr %d2, align 4, !dbg !84
+  ret void
+}
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!3}
+!0 = distinct !DICompileUnit(language: DW_LANG_C, file: !1, producer: "test", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug)
+!1 = !DIFile(filename: "store-merge.c", directory: "/")
+!2 = !DISubroutineType(types: !4)
+!3 = !{i32 2, !"Debug Info Version", i32 3}
+!4 = !{}
+!5 = distinct !DISubprogram(name: "clear_first", scope: !1, file: !1, line: 3, type: !2, scopeLine: 3, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0)
+!6 = distinct !DISubprogram(name: "clear_second", scope: !1, file: !1, line: 6, type: !2, scopeLine: 6, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0)
+!10 = distinct !DISubprogram(name: "siblings", scope: !1, file: !1, line: 10, type: !2, scopeLine: 10, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0)
+!11 = !DILocation(line: 11, column: 3, scope: !10)
+!12 = !DILocation(line: 12, column: 3, scope: !10)
+!13 = !DILocation(line: 4, column: 3, scope: !5, inlinedAt: !11)
+!14 = !DILocation(line: 7, column: 3, scope: !6, inlinedAt: !12)
+!20 = distinct !DISubprogram(name: "same_location", scope: !1, file: !1, line: 20, type: !2, scopeLine: 20, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0)
+!21 = !DILocation(line: 21, column: 3, scope: !20)
+!22 = !DILocation(line: 4, column: 3, scope: !5, inlinedAt: !21)
+!30 = distinct !DISubprogram(name: "missing_location", scope: !1, file: !1, line: 30, type: !2, scopeLine: 30, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0)
+!31 = !DILocation(line: 31, column: 3, scope: !30)
+!32 = !DILocation(line: 4, column: 3, scope: !5, inlinedAt: !31)
+!40 = distinct !DISubprogram(name: "partial_merge", scope: !1, file: !1, line: 40, type: !2, scopeLine: 40, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0)
+!41 = !DILocation(line: 41, column: 3, scope: !40)
+!42 = !DILocation(line: 42, column: 3, scope: !40)
+!43 = !DILocation(line: 4, column: 3, scope: !5, inlinedAt: !41)
+!44 = !DILocation(line: 7, column: 3, scope: !6, inlinedAt: !42)
+!50 = distinct !DISubprogram(name: "extract_siblings", scope: !1, file: !1, line: 50, type: !2, scopeLine: 50, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0)
+!51 = !DILocation(line: 51, column: 3, scope: !50)
+!52 = !DILocation(line: 52, column: 3, scope: !50)
+!53 = !DILocation(line: 4, column: 3, scope: !5, inlinedAt: !51)
+!54 = !DILocation(line: 7, column: 3, scope: !6, inlinedAt: !52)
+!60 = distinct !DISubprogram(name: "copy_siblings", scope: !1, file: !1, line: 60, type: !2, scopeLine: 60, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0)
+!61 = !DILocation(line: 61, column: 3, scope: !60)
+!62 = !DILocation(line: 62, column: 3, scope: !60)
+!63 = !DILocation(line: 4, column: 3, scope: !5, inlinedAt: !61)
+!64 = !DILocation(line: 7, column: 3, scope: !6, inlinedAt: !62)
+!70 = distinct !DISubprogram(name: "copy_independent_locations", scope: !1, file: !1, line: 70, type: !2, scopeLine: 70, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0)
+!71 = !DILocation(line: 71, column: 3, scope: !70)
+!72 = !DILocation(line: 72, column: 3, scope: !70)
+!73 = !DILocation(line: 4, column: 3, scope: !5, inlinedAt: !71)
+!74 = !DILocation(line: 7, column: 3, scope: !6, inlinedAt: !72)
+!80 = distinct !DISubprogram(name: "copy_partial_merge", scope: !1, file: !1, line: 80, type: !2, scopeLine: 80, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0)
+!81 = !DILocation(line: 81, column: 3, scope: !80)
+!82 = !DILocation(line: 82, column: 3, scope: !80)
+!83 = !DILocation(line: 4, column: 3, scope: !5, inlinedAt: !81)
+!84 = !DILocation(line: 7, column: 3, scope: !6, inlinedAt: !82)

>From d3b1c0ff698d2c818cb5af487cb436b1eb46c01c Mon Sep 17 00:00:00 2001
From: Longjun Luo <luolongjuna at gmail.com>
Date: Mon, 28 Sep 2026 12:13:11 +0000
Subject: [PATCH 2/2] [DAGCombiner] Clarify store-merge debug location comments
 (NFC)

Clarify which debug locations are merged and which IROrder values are
retained. Document the unchanged locations used for value construction.

Assisted-by: OpenAI Codex
---
 llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp b/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
index 71a36bdbb1924..b8e07033e3bdf 100644
--- a/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
@@ -23256,8 +23256,9 @@ bool DAGCombiner::mergeStoresOfConstantsOrVecElts(
   assert((!UseTrunc || !UseVector) &&
          "This optimization cannot emit a vector truncating store");
 
-  // Preserve the lowest-addressed store's IR order. Merge source locations for
-  // the new store only, leaving value construction unchanged.
+  // StoreDL merges the debug locations of the first NumStores stores for the
+  // new store. DL, used to build the stored value, is left unchanged. Both
+  // keep the IROrder of StoreNodes[0].
   SDLoc DL(StoreNodes[0].MemNode);
   SDLoc StoreDL = DL;
 
@@ -24184,11 +24185,12 @@ bool DAGCombiner::tryStoreMergeOfLoads(SmallVectorImpl<MemOpLink> &StoreNodes,
       continue;
     }
 
+    // Merge debug locations separately for the first NumElem loads and stores.
+    // LoadDL and StoreDL keep the IROrder of LoadNodes[0] and StoreNodes[0].
+    // ValueDL keeps LoadNodes[0]'s location for value nodes (the rotate below).
     SDLoc LoadDL(LoadNodes[0].MemNode);
     SDLoc StoreDL(StoreNodes[0].MemNode);
     SDLoc ValueDL = LoadDL;
-    // Merge loads and stores independently, preserving the IR order of each
-    // group's lowest-addressed operation. Exclude candidates left unmerged.
     for (unsigned I = 1; I != NumElem; ++I) {
       LoadDL.mergeDebugLoc(LoadNodes[I].MemNode->getDebugLoc());
       StoreDL.mergeDebugLoc(StoreNodes[I].MemNode->getDebugLoc());



More information about the llvm-commits mailing list