[llvm] [DebugInfo][MemCpyOpt] Update assignment IDs during call-slot forwarding (PR #224244)
via llvm-commits
llvm-commits at lists.llvm.org
Thu Sep 17 02:30:18 PDT 2026
https://github.com/MrEven132 created https://github.com/llvm/llvm-project/pull/224244
Call-slot forwarding can redirect a memory intrinsic to write into the destination of a following copy and then delete that copy. The redirected intrinsic kept its old `!DIAssignID`, so the surviving write ended up linked to the assignment for the temporary source rather than its new destination.
Replace the redirected intrinsic's assignment ID with the ID from the eliminated copy.
When the previous clobber is itself a memcpy, `processMemCpyMemCpyDependence` already preserves DIAssignID correctly via `copyMetadata`, because the surviving instruction is the inner copy whose destination never changes. Reordering dispatch in `processMemCpy` to prefer memcpy-memcpy forwarding over call-slot for that case would avoid this fix, but it is a behavioral change: today call-slot wins and the earlier call survives with its argument rewritten; routing through memcpy-memcpy would instead keep the inner copy with its source rewritten. Call-slot also handles non-memcpy callers (regular calls, memsets) that memcpy-memcpy does not, so it does not subsume this path. The patch therefore fills the DIAssignID gap in `performCallSlotOptzn` itself, guarded by an `IntrinsicInst` check since ordinary calls cannot carry the metadata. Matches the preservation pattern used elsewhere in the file (lines 675, 792, 1257).
Add an assignment-tracking regression test for a forwarded memcpy chain.
Fixes #213642
>From 383bbbd40582ad4824170b26393337216922c678 Mon Sep 17 00:00:00 2001
From: MrEven132 <13554966229 at 163.com>
Date: Thu, 17 Sep 2026 17:02:39 +0800
Subject: [PATCH] [DebugInfo][MemCpyOpt] Update assignment IDs during call-slot
forwarding
---
.../lib/Transforms/Scalar/MemCpyOptimizer.cpp | 8 +++
.../memcpyopt/call-slot.ll | 58 +++++++++++++++++++
2 files changed, 66 insertions(+)
create mode 100644 llvm/test/DebugInfo/Generic/assignment-tracking/memcpyopt/call-slot.ll
diff --git a/llvm/lib/Transforms/Scalar/MemCpyOptimizer.cpp b/llvm/lib/Transforms/Scalar/MemCpyOptimizer.cpp
index 24313e895215a..c2f690f8875cf 100644
--- a/llvm/lib/Transforms/Scalar/MemCpyOptimizer.cpp
+++ b/llvm/lib/Transforms/Scalar/MemCpyOptimizer.cpp
@@ -1064,6 +1064,14 @@ bool MemCpyOptPass::performCallSlotOptzn(Instruction *cpyLoad,
if (!changedArgument)
return false;
+ // The intrinsic now performs the assignment that was previously performed
+ // by the copy. Replace its old assignment link, which described the
+ // temporary source, with the copy's link for the new destination. Ordinary
+ // calls cannot carry DIAssignID metadata.
+ if (isa<IntrinsicInst>(C))
+ C->setMetadata(LLVMContext::MD_DIAssignID,
+ cpyStore->getMetadata(LLVMContext::MD_DIAssignID));
+
// If the destination wasn't sufficiently aligned then increase its alignment.
if (!isDestSufficientlyAligned) {
assert(isa<AllocaInst>(cpyDest) && "Can only increase alloca alignment!");
diff --git a/llvm/test/DebugInfo/Generic/assignment-tracking/memcpyopt/call-slot.ll b/llvm/test/DebugInfo/Generic/assignment-tracking/memcpyopt/call-slot.ll
new file mode 100644
index 0000000000000..27ba440be3362
--- /dev/null
+++ b/llvm/test/DebugInfo/Generic/assignment-tracking/memcpyopt/call-slot.ll
@@ -0,0 +1,58 @@
+; RUN: opt -passes=memcpyopt -S %s | FileCheck %s
+
+ at constant = private constant [4 x i8] c"abc\00"
+
+define i8 @memcpy_chain() !dbg !5 {
+; CHECK-LABEL: define i8 @memcpy_chain()
+; CHECK: [[A:%.*]] = alloca [4 x i8], align 1
+; CHECK-NEXT: #dbg_assign(i1 poison, ![[VAR_A:[0-9]+]], !DIExpression(), ![[ID_ALLOCA_A:[0-9]+]], ptr [[A]], !DIExpression(), ![[LOC:[0-9]+]])
+; CHECK-NEXT: [[B:%.*]] = alloca [4 x i8], align 1
+; CHECK-NEXT: #dbg_assign(i1 poison, ![[VAR_B:[0-9]+]], !DIExpression(), ![[ID_ALLOCA_B:[0-9]+]], ptr [[B]], !DIExpression(), ![[LOC]])
+; CHECK-NEXT: [[C:%.*]] = alloca [4 x i8], align 1
+; CHECK-NEXT: #dbg_assign(i1 poison, ![[VAR_C:[0-9]+]], !DIExpression(), ![[ID_ALLOCA_C:[0-9]+]], ptr [[C]], !DIExpression(), ![[LOC]])
+; CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 1 [[C]], ptr align 1 @constant, i64 4, i1 false), !DIAssignID ![[ID_C:[0-9]+]]
+; CHECK-NEXT: #dbg_assign(i1 poison, ![[VAR_A]], !DIExpression(), ![[ID_A:[0-9]+]], ptr [[A]], !DIExpression(), ![[LOC]])
+; CHECK-NEXT: #dbg_assign(i1 poison, ![[VAR_B]], !DIExpression(), ![[ID_B:[0-9]+]], ptr [[B]], !DIExpression(), ![[LOC]])
+; CHECK-NEXT: #dbg_assign(i1 poison, ![[VAR_C]], !DIExpression(), ![[ID_C]], ptr [[C]], !DIExpression(), ![[LOC]])
+; CHECK-NEXT: [[VALUE:%.*]] = load i8, ptr [[C]], align 1
+; CHECK-NEXT: ret i8 [[VALUE]]
+ %a = alloca [4 x i8], align 1, !DIAssignID !10
+ #dbg_assign(i1 poison, !11, !DIExpression(), !10, ptr %a, !DIExpression(), !9)
+ %b = alloca [4 x i8], align 1, !DIAssignID !12
+ #dbg_assign(i1 poison, !13, !DIExpression(), !12, ptr %b, !DIExpression(), !9)
+ %c = alloca [4 x i8], align 1, !DIAssignID !14
+ #dbg_assign(i1 poison, !15, !DIExpression(), !14, ptr %c, !DIExpression(), !9)
+ call void @llvm.memcpy.p0.p0.i64(ptr align 1 %a, ptr align 1 @constant, i64 4, i1 false), !DIAssignID !16
+ #dbg_assign(i1 poison, !11, !DIExpression(), !16, ptr %a, !DIExpression(), !9)
+ call void @llvm.memcpy.p0.p0.i64(ptr align 1 %b, ptr align 1 %a, i64 4, i1 false), !DIAssignID !17
+ #dbg_assign(i1 poison, !13, !DIExpression(), !17, ptr %b, !DIExpression(), !9)
+ call void @llvm.memcpy.p0.p0.i64(ptr align 1 %c, ptr align 1 %b, i64 4, i1 false), !DIAssignID !18
+ #dbg_assign(i1 poison, !15, !DIExpression(), !18, ptr %c, !DIExpression(), !9)
+ %value = load i8, ptr %c, align 1
+ ret i8 %value
+}
+
+declare void @llvm.memcpy.p0.p0.i64(ptr noalias writeonly captures(none), ptr noalias readonly captures(none), i64, i1 immarg)
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!2, !3}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C11, file: !1, producer: "clang", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug)
+!1 = !DIFile(filename: "call-slot.c", directory: "/")
+!2 = !{i32 7, !"Dwarf Version", i32 5}
+!3 = !{i32 2, !"Debug Info Version", i32 3}
+!4 = !DISubroutineType(types: !8)
+!5 = distinct !DISubprogram(name: "memcpy_chain", scope: !1, file: !1, line: 1, type: !4, scopeLine: 1, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !8)
+!6 = !DIBasicType(name: "char", size: 8, encoding: DW_ATE_signed_char)
+!7 = !DICompositeType(tag: DW_TAG_array_type, baseType: !6, size: 32, elements: !8)
+!8 = !{}
+!9 = !DILocation(line: 0, scope: !5)
+!10 = distinct !DIAssignID()
+!11 = !DILocalVariable(name: "a", scope: !5, file: !1, line: 2, type: !7)
+!12 = distinct !DIAssignID()
+!13 = !DILocalVariable(name: "b", scope: !5, file: !1, line: 2, type: !7)
+!14 = distinct !DIAssignID()
+!15 = !DILocalVariable(name: "c", scope: !5, file: !1, line: 2, type: !7)
+!16 = distinct !DIAssignID()
+!17 = distinct !DIAssignID()
+!18 = distinct !DIAssignID()
More information about the llvm-commits
mailing list