[llvm] [ArgumentPromotion] Emit DW_OP_LLVM_implicit_pointer for promoted pointer args (PR #187642)
Shivam Kunwar via llvm-commits
llvm-commits at lists.llvm.org
Tue Mar 24 00:51:07 PDT 2026
https://github.com/phyBrackets updated https://github.com/llvm/llvm-project/pull/187642
>From b11da11b7b07d67fe10d62c1556b0a5907619321 Mon Sep 17 00:00:00 2001
From: Shivam Kunwar <physhivam at gmail.com>
Date: Mon, 16 Mar 2026 15:17:11 +0530
Subject: [PATCH 1/5] [DebugInfo] Lower DW_OP_LLVM_implicit_pointer to DWARF
---
.../CodeGen/AsmPrinter/DwarfCompileUnit.cpp | 121 ++++++++++++++++++
.../lib/CodeGen/AsmPrinter/DwarfCompileUnit.h | 13 ++
.../DebugInfo/X86/implicit-pointer-dwarf5.ll | 70 ++++++++++
3 files changed, 204 insertions(+)
create mode 100644 llvm/test/DebugInfo/X86/implicit-pointer-dwarf5.ll
diff --git a/llvm/lib/CodeGen/AsmPrinter/DwarfCompileUnit.cpp b/llvm/lib/CodeGen/AsmPrinter/DwarfCompileUnit.cpp
index 840f2637d9564..0077a1fd08b2f 100644
--- a/llvm/lib/CodeGen/AsmPrinter/DwarfCompileUnit.cpp
+++ b/llvm/lib/CodeGen/AsmPrinter/DwarfCompileUnit.cpp
@@ -776,8 +776,129 @@ DIE *DwarfCompileUnit::constructVariableDIE(DbgVariable &DV, bool Abstract) {
return VariableDie;
}
+static const DIType *resolveTypeQualifiers(const DIType *Ty) {
+ while (const auto *DT = dyn_cast_or_null<DIDerivedType>(Ty)) {
+ switch (DT->getTag()) {
+ case dwarf::DW_TAG_typedef:
+ case dwarf::DW_TAG_const_type:
+ case dwarf::DW_TAG_volatile_type:
+ case dwarf::DW_TAG_restrict_type:
+ case dwarf::DW_TAG_atomic_type:
+ Ty = DT->getBaseType();
+ continue;
+ default:
+ return Ty;
+ }
+ }
+ return Ty;
+}
+
+bool DwarfCompileUnit::emitImplicitPointerLocation(const Loc::Single &Single,
+ const DbgVariable &DV,
+ DIE &VariableDie) {
+ const auto *Expr = Single.getExpr();
+ if (!Expr)
+ return false;
+
+ // Only handle the simple case where DW_OP_LLVM_implicit_pointer is the
+ // sole operation (or followed only by DW_OP_LLVM_fragment).
+ //
+ // Multi-level implicit pointers (e.g., int **pp where both levels are
+ // optimized away) are expressed as chains of separate #dbg_value records
+ // at the IR level, each with a single DW_OP_LLVM_implicit_pointer,
+ // rather than stacking multiple ops in one expression. The backend handles
+ // each level independently, and chaining emerges naturally in the DWARF
+ // output when the referenced artificial DIE itself has an implicit pointer
+ // location.
+ auto ExprOps = Expr->expr_ops();
+ auto FirstOp = ExprOps.begin();
+ if (FirstOp == ExprOps.end() ||
+ FirstOp->getOp() != dwarf::DW_OP_LLVM_implicit_pointer)
+ return false;
+
+ if (DD->getDwarfVersion() < 4)
+ return false;
+
+ const auto &DVal = Single.getValueLoc();
+ if (DVal.isVariadic())
+ return false;
+
+ const auto &Entry = DVal.getLocEntries()[0];
+
+ // Resolve the variable's type, stripping qualifiers and typedefs,
+ // to find the pointer or reference type.
+ const auto *PtrTy =
+ dyn_cast_or_null<DIDerivedType>(resolveTypeQualifiers(DV.getType()));
+ if (!PtrTy)
+ return false;
+
+ if (PtrTy->getTag() != dwarf::DW_TAG_pointer_type &&
+ PtrTy->getTag() != dwarf::DW_TAG_reference_type &&
+ PtrTy->getTag() != dwarf::DW_TAG_rvalue_reference_type)
+ return false;
+
+ const auto *PointeeTy = PtrTy->getBaseType();
+
+ // Try to reuse an existing artificial DIE for constant integer values.
+ // This avoids duplicate DIEs when multiple pointer variables reference
+ // the same constant (e.g., after ArgumentPromotion promotes the same
+ // struct member for two different pointer parameters).
+ DIE *ArtificialDIEPtr = nullptr;
+ if (Entry.isInt() && PointeeTy) {
+ auto Key = std::make_pair(PointeeTy, Entry.getInt());
+ auto It = ImplicitPointerDIEs.find(Key);
+ if (It != ImplicitPointerDIEs.end())
+ ArtificialDIEPtr = It->second;
+ }
+
+ if (!ArtificialDIEPtr) {
+ DIE &ArtificialDIE = createAndAddDIE(dwarf::DW_TAG_variable, getUnitDie());
+ addFlag(ArtificialDIE, dwarf::DW_AT_artificial);
+
+ if (PointeeTy)
+ addType(ArtificialDIE, PointeeTy);
+
+ if (Entry.isLocation()) {
+ addAddress(ArtificialDIE, dwarf::DW_AT_location, Entry.getLoc());
+ } else if (Entry.isInt()) {
+ if (PointeeTy)
+ addConstantValue(ArtificialDIE, Entry.getInt(), PointeeTy);
+ } else if (Entry.isConstantFP()) {
+ addConstantFPValue(ArtificialDIE, Entry.getConstantFP());
+ } else {
+ return false;
+ }
+
+ ArtificialDIEPtr = &ArtificialDIE;
+
+ // Cache constant entries for de-duplication.
+ if (Entry.isInt() && PointeeTy)
+ ImplicitPointerDIEs[std::make_pair(PointeeTy, Entry.getInt())] =
+ ArtificialDIEPtr;
+ }
+
+ auto *Loc = new (DIEValueAllocator) DIELoc;
+
+ const unsigned ImplicitPtrOp = DD->getDwarfVersion() >= 5
+ ? dwarf::DW_OP_implicit_pointer
+ : dwarf::DW_OP_GNU_implicit_pointer;
+ addUInt(*Loc, dwarf::DW_FORM_data1, ImplicitPtrOp);
+
+ Loc->addValue(DIEValueAllocator, static_cast<dwarf::Attribute>(0),
+ dwarf::DW_FORM_ref_addr, DIEEntry(*ArtificialDIEPtr));
+
+ addSInt(*Loc, dwarf::DW_FORM_sdata, 0);
+
+ addBlock(VariableDie, dwarf::DW_AT_location, Loc);
+ return true;
+}
+
void DwarfCompileUnit::applyConcreteDbgVariableAttributes(
const Loc::Single &Single, const DbgVariable &DV, DIE &VariableDie) {
+ // Handle DW_OP_LLVM_implicit_pointer before normal location emission.
+ if (emitImplicitPointerLocation(Single, DV, VariableDie))
+ return;
+
const DbgValueLoc *DVal = &Single.getValueLoc();
if (!Single.getExpr())
DD->addTargetVariableAttributes(*this, VariableDie, std::nullopt,
diff --git a/llvm/lib/CodeGen/AsmPrinter/DwarfCompileUnit.h b/llvm/lib/CodeGen/AsmPrinter/DwarfCompileUnit.h
index ede02c169bffd..5e3a2f4b5f75f 100644
--- a/llvm/lib/CodeGen/AsmPrinter/DwarfCompileUnit.h
+++ b/llvm/lib/CodeGen/AsmPrinter/DwarfCompileUnit.h
@@ -89,6 +89,11 @@ class DwarfCompileUnit final : public DwarfUnit {
DenseMap<const DINode *, std::unique_ptr<DbgEntity>> AbstractEntities;
+ /// Cache of artificial DIEs created for DW_OP_LLVM_implicit_pointer
+ /// lowering, keyed by (pointee type, constant value). Enables reuse when
+ /// multiple pointer variables reference the same constant.
+ DenseMap<std::pair<const DIType *, int64_t>, DIE *> ImplicitPointerDIEs;
+
/// DWO ID for correlating skeleton and split units.
uint64_t DWOId = 0;
@@ -124,6 +129,14 @@ class DwarfCompileUnit final : public DwarfUnit {
///@}
+ /// Lower DW_OP_LLVM_implicit_pointer by creating an artificial variable DIE
+ /// for the dereferenced value and emitting DW_OP_implicit_pointer (DWARF 5)
+ /// or DW_OP_GNU_implicit_pointer (DWARF 4) for the pointer's location.
+ ///
+ /// \returns true if the implicit pointer was handled successfully.
+ bool emitImplicitPointerLocation(const Loc::Single &Single,
+ const DbgVariable &DV, DIE &VariableDie);
+
bool isDwoUnit() const override;
DenseMap<const DILocalScope *, DIE *> &getAbstractScopeDIEs() {
diff --git a/llvm/test/DebugInfo/X86/implicit-pointer-dwarf5.ll b/llvm/test/DebugInfo/X86/implicit-pointer-dwarf5.ll
new file mode 100644
index 0000000000000..1f6afaa95e7b9
--- /dev/null
+++ b/llvm/test/DebugInfo/X86/implicit-pointer-dwarf5.ll
@@ -0,0 +1,70 @@
+; RUN: llc -mtriple=x86_64-linux-gnu -filetype=obj -o %t %s
+; RUN: llvm-dwarfdump --debug-info %t | FileCheck %s
+
+; Test that DW_OP_LLVM_implicit_pointer is correctly lowered to
+; DW_OP_implicit_pointer in DWARF 5 output, with an artificial variable
+; DIE describing the dereferenced value.
+
+; CHECK: DW_TAG_subprogram
+; CHECK: DW_AT_name ("foo")
+; CHECK: DW_TAG_formal_parameter
+; CHECK: DW_AT_location (DW_OP_implicit_pointer
+; CHECK: DW_AT_name ("p")
+; CHECK: DW_AT_type {{.*}} "int *"
+
+; CHECK: DW_TAG_variable
+; CHECK-NEXT: DW_AT_artificial (true)
+; CHECK-NEXT: DW_AT_type {{.*}} "int"
+; CHECK-NEXT: DW_AT_location (DW_OP_reg5 RDI)
+
+; CHECK: DW_TAG_subprogram
+; CHECK: DW_AT_name ("bar")
+; CHECK: DW_TAG_formal_parameter
+; CHECK: DW_AT_location (DW_OP_implicit_pointer
+; CHECK: DW_AT_name ("q")
+
+; CHECK: DW_TAG_variable
+; CHECK-NEXT: DW_AT_artificial (true)
+; CHECK-NEXT: DW_AT_type {{.*}} "int"
+; CHECK-NEXT: DW_AT_const_value (42)
+
+define internal i32 @foo(i32 noundef %p.0.val) !dbg !7 {
+entry:
+ #dbg_value(i32 %p.0.val, !12, !DIExpression(DW_OP_LLVM_implicit_pointer), !14)
+ %add = add nsw i32 %p.0.val, 5, !dbg !15
+ ret i32 %add, !dbg !16
+}
+
+define internal i32 @bar() !dbg !17 {
+entry:
+ #dbg_value(i32 42, !20, !DIExpression(DW_OP_LLVM_implicit_pointer), !21)
+ ret i32 47, !dbg !22
+}
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!3, !4, !5}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, enums: !2)
+!1 = !DIFile(filename: "test.c", directory: "/tmp")
+!2 = !{}
+!3 = !{i32 7, !"Dwarf Version", i32 5}
+!4 = !{i32 2, !"Debug Info Version", i32 3}
+!5 = !{i32 1, !"wchar_size", i32 4}
+!6 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+
+!7 = distinct !DISubprogram(name: "foo", scope: !1, file: !1, line: 2, type: !8, scopeLine: 2, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !11)
+!8 = !DISubroutineType(types: !9)
+!9 = !{!6, !10}
+!10 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !6, size: 64)
+!11 = !{!12}
+!12 = !DILocalVariable(name: "p", arg: 1, scope: !7, file: !1, line: 2, type: !10)
+!14 = !DILocation(line: 2, scope: !7)
+!15 = !DILocation(line: 2, column: 20, scope: !7)
+!16 = !DILocation(line: 2, column: 10, scope: !7)
+
+!17 = distinct !DISubprogram(name: "bar", scope: !1, file: !1, line: 5, type: !18, scopeLine: 5, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !19)
+!18 = !DISubroutineType(types: !9)
+!19 = !{!20}
+!20 = !DILocalVariable(name: "q", arg: 1, scope: !17, file: !1, line: 5, type: !10)
+!21 = !DILocation(line: 5, scope: !17)
+!22 = !DILocation(line: 5, column: 10, scope: !17)
\ No newline at end of file
>From 23b5f5ddf7155b041a96ae31ebdd6c5733f7d071 Mon Sep 17 00:00:00 2001
From: Shivam Kunwar <phyBrackets at users.noreply.github.com>
Date: Wed, 18 Mar 2026 14:02:39 +0530
Subject: [PATCH 2/5] Address review feedbacks
---
.../CodeGen/AsmPrinter/DwarfCompileUnit.cpp | 28 ++++----
.../DebugInfo/X86/implicit-pointer-dwarf5.ll | 70 ++++++++++++++++---
2 files changed, 71 insertions(+), 27 deletions(-)
diff --git a/llvm/lib/CodeGen/AsmPrinter/DwarfCompileUnit.cpp b/llvm/lib/CodeGen/AsmPrinter/DwarfCompileUnit.cpp
index 0077a1fd08b2f..a69ebde393973 100644
--- a/llvm/lib/CodeGen/AsmPrinter/DwarfCompileUnit.cpp
+++ b/llvm/lib/CodeGen/AsmPrinter/DwarfCompileUnit.cpp
@@ -804,12 +804,11 @@ bool DwarfCompileUnit::emitImplicitPointerLocation(const Loc::Single &Single,
// sole operation (or followed only by DW_OP_LLVM_fragment).
//
// Multi-level implicit pointers (e.g., int **pp where both levels are
- // optimized away) are expressed as chains of separate #dbg_value records
- // at the IR level, each with a single DW_OP_LLVM_implicit_pointer,
- // rather than stacking multiple ops in one expression. The backend handles
- // each level independently, and chaining emerges naturally in the DWARF
- // output when the referenced artificial DIE itself has an implicit pointer
- // location.
+ // optimized away) would require stacking multiple implicit_pointer ops
+ // in one expression and unwinding them into a chain of artificial DIEs.
+ // This is left for future work.
+ //
+ // Location list support (Loc::Multi) is not yet handled.
auto ExprOps = Expr->expr_ops();
auto FirstOp = ExprOps.begin();
if (FirstOp == ExprOps.end() ||
@@ -826,7 +825,8 @@ bool DwarfCompileUnit::emitImplicitPointerLocation(const Loc::Single &Single,
const auto &Entry = DVal.getLocEntries()[0];
// Resolve the variable's type, stripping qualifiers and typedefs,
- // to find the pointer or reference type.
+ // to find the pointer or reference type underneath.
+ // The verifier rejects cyclic type references, so this loop terminates.
const auto *PtrTy =
dyn_cast_or_null<DIDerivedType>(resolveTypeQualifiers(DV.getType()));
if (!PtrTy)
@@ -852,24 +852,20 @@ bool DwarfCompileUnit::emitImplicitPointerLocation(const Loc::Single &Single,
}
if (!ArtificialDIEPtr) {
- DIE &ArtificialDIE = createAndAddDIE(dwarf::DW_TAG_variable, getUnitDie());
- addFlag(ArtificialDIE, dwarf::DW_AT_artificial);
-
- if (PointeeTy)
- addType(ArtificialDIE, PointeeTy);
+ DIE &ProcDIE = createAndAddDIE(dwarf::DW_TAG_dwarf_procedure, getUnitDie());
if (Entry.isLocation()) {
- addAddress(ArtificialDIE, dwarf::DW_AT_location, Entry.getLoc());
+ addAddress(ProcDIE, dwarf::DW_AT_location, Entry.getLoc());
} else if (Entry.isInt()) {
if (PointeeTy)
- addConstantValue(ArtificialDIE, Entry.getInt(), PointeeTy);
+ addConstantValue(ProcDIE, Entry.getInt(), PointeeTy);
} else if (Entry.isConstantFP()) {
- addConstantFPValue(ArtificialDIE, Entry.getConstantFP());
+ addConstantFPValue(ProcDIE, Entry.getConstantFP());
} else {
return false;
}
- ArtificialDIEPtr = &ArtificialDIE;
+ ArtificialDIEPtr = &ProcDIE;
// Cache constant entries for de-duplication.
if (Entry.isInt() && PointeeTy)
diff --git a/llvm/test/DebugInfo/X86/implicit-pointer-dwarf5.ll b/llvm/test/DebugInfo/X86/implicit-pointer-dwarf5.ll
index 1f6afaa95e7b9..6692c12997e2f 100644
--- a/llvm/test/DebugInfo/X86/implicit-pointer-dwarf5.ll
+++ b/llvm/test/DebugInfo/X86/implicit-pointer-dwarf5.ll
@@ -1,20 +1,16 @@
; RUN: llc -mtriple=x86_64-linux-gnu -filetype=obj -o %t %s
; RUN: llvm-dwarfdump --debug-info %t | FileCheck %s
-; Test that DW_OP_LLVM_implicit_pointer is correctly lowered to
-; DW_OP_implicit_pointer in DWARF 5 output, with an artificial variable
-; DIE describing the dereferenced value.
+; Test DW_OP_LLVM_implicit_pointer lowering to DWARF 5, using
+; DW_TAG_dwarf_procedure for the artificial DIEs.
; CHECK: DW_TAG_subprogram
; CHECK: DW_AT_name ("foo")
; CHECK: DW_TAG_formal_parameter
; CHECK: DW_AT_location (DW_OP_implicit_pointer
; CHECK: DW_AT_name ("p")
-; CHECK: DW_AT_type {{.*}} "int *"
-; CHECK: DW_TAG_variable
-; CHECK-NEXT: DW_AT_artificial (true)
-; CHECK-NEXT: DW_AT_type {{.*}} "int"
+; CHECK: DW_TAG_dwarf_procedure
; CHECK-NEXT: DW_AT_location (DW_OP_reg5 RDI)
; CHECK: DW_TAG_subprogram
@@ -23,11 +19,31 @@
; CHECK: DW_AT_location (DW_OP_implicit_pointer
; CHECK: DW_AT_name ("q")
-; CHECK: DW_TAG_variable
-; CHECK-NEXT: DW_AT_artificial (true)
-; CHECK-NEXT: DW_AT_type {{.*}} "int"
+; CHECK: DW_TAG_dwarf_procedure
; CHECK-NEXT: DW_AT_const_value (42)
+; CHECK: DW_TAG_subprogram
+; CHECK: DW_AT_name ("baz")
+; CHECK: DW_TAG_formal_parameter
+; CHECK: DW_AT_location (DW_OP_implicit_pointer
+; CHECK: DW_AT_name ("r")
+
+; CHECK: DW_TAG_dwarf_procedure
+; CHECK-NEXT: DW_AT_const_value
+
+; CHECK: DW_TAG_subprogram
+; CHECK: DW_AT_name ("dedup")
+; CHECK: DW_TAG_formal_parameter
+; CHECK: DW_AT_location (DW_OP_implicit_pointer
+; CHECK: DW_AT_name ("s")
+; CHECK: DW_TAG_formal_parameter
+; CHECK: DW_AT_location (DW_OP_implicit_pointer
+; CHECK: DW_AT_name ("t")
+
+; CHECK: DW_TAG_dwarf_procedure
+; CHECK-NEXT: DW_AT_const_value (99)
+; CHECK-NOT: DW_AT_const_value (99)
+
define internal i32 @foo(i32 noundef %p.0.val) !dbg !7 {
entry:
#dbg_value(i32 %p.0.val, !12, !DIExpression(DW_OP_LLVM_implicit_pointer), !14)
@@ -41,6 +57,19 @@ entry:
ret i32 47, !dbg !22
}
+define internal float @baz() !dbg !23 {
+entry:
+ #dbg_value(float 0x400921CAC0000000, !26, !DIExpression(DW_OP_LLVM_implicit_pointer), !27)
+ ret float 0x400921CAC0000000, !dbg !28
+}
+
+define internal i32 @dedup() !dbg !29 {
+entry:
+ #dbg_value(i32 99, !32, !DIExpression(DW_OP_LLVM_implicit_pointer), !34)
+ #dbg_value(i32 99, !33, !DIExpression(DW_OP_LLVM_implicit_pointer), !34)
+ ret i32 198, !dbg !35
+}
+
!llvm.dbg.cu = !{!0}
!llvm.module.flags = !{!3, !4, !5}
@@ -67,4 +96,23 @@ entry:
!19 = !{!20}
!20 = !DILocalVariable(name: "q", arg: 1, scope: !17, file: !1, line: 5, type: !10)
!21 = !DILocation(line: 5, scope: !17)
-!22 = !DILocation(line: 5, column: 10, scope: !17)
\ No newline at end of file
+!22 = !DILocation(line: 5, column: 10, scope: !17)
+
+!23 = distinct !DISubprogram(name: "baz", scope: !1, file: !1, line: 8, type: !24, scopeLine: 8, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !25)
+!24 = !DISubroutineType(types: !38)
+!25 = !{!26}
+!26 = !DILocalVariable(name: "r", arg: 1, scope: !23, file: !1, line: 8, type: !36)
+!27 = !DILocation(line: 8, scope: !23)
+!28 = !DILocation(line: 8, column: 10, scope: !23)
+
+!29 = distinct !DISubprogram(name: "dedup", scope: !1, file: !1, line: 11, type: !30, scopeLine: 11, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !31)
+!30 = !DISubroutineType(types: !9)
+!31 = !{!32, !33}
+!32 = !DILocalVariable(name: "s", arg: 1, scope: !29, file: !1, line: 11, type: !10)
+!33 = !DILocalVariable(name: "t", arg: 2, scope: !29, file: !1, line: 11, type: !10)
+!34 = !DILocation(line: 11, scope: !29)
+!35 = !DILocation(line: 11, column: 10, scope: !29)
+
+!36 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !37, size: 64)
+!37 = !DIBasicType(name: "float", size: 32, encoding: DW_ATE_float)
+!38 = !{!37, !36}
\ No newline at end of file
>From 2244c73513cfb047b0c88474ccf9d206751547f6 Mon Sep 17 00:00:00 2001
From: Shivam Kunwar <physhivam at gmail.com>
Date: Fri, 20 Mar 2026 12:00:08 +0530
Subject: [PATCH 3/5] [DebugInfo] Verify DW_OP_LLVM_implicit_pointer survives
ISel
---
.../DebugInfo/X86/implicit-pointer-isel.ll | 32 +++++++++++++++++++
1 file changed, 32 insertions(+)
create mode 100644 llvm/test/DebugInfo/X86/implicit-pointer-isel.ll
diff --git a/llvm/test/DebugInfo/X86/implicit-pointer-isel.ll b/llvm/test/DebugInfo/X86/implicit-pointer-isel.ll
new file mode 100644
index 0000000000000..7d067453680c9
--- /dev/null
+++ b/llvm/test/DebugInfo/X86/implicit-pointer-isel.ll
@@ -0,0 +1,32 @@
+; RUN: llc -mtriple=x86_64-linux-gnu -stop-after=finalize-isel -o - %s | FileCheck %s
+
+; Test that DW_OP_LLVM_implicit_pointer survives ISel.
+
+; CHECK: DBG_VALUE $edi, $noreg, ![[VAR:[0-9]+]], !DIExpression(DW_OP_LLVM_implicit_pointer)
+
+define internal i32 @foo(i32 noundef %p.0.val) !dbg !7 {
+entry:
+ #dbg_value(i32 %p.0.val, !12, !DIExpression(DW_OP_LLVM_implicit_pointer), !14)
+ %add = add nsw i32 %p.0.val, 5, !dbg !15
+ ret i32 %add, !dbg !16
+}
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!3, !4, !5}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, enums: !2)
+!1 = !DIFile(filename: "test.c", directory: "/tmp")
+!2 = !{}
+!3 = !{i32 7, !"Dwarf Version", i32 5}
+!4 = !{i32 2, !"Debug Info Version", i32 3}
+!5 = !{i32 1, !"wchar_size", i32 4}
+!6 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+!7 = distinct !DISubprogram(name: "foo", scope: !1, file: !1, line: 2, type: !8, scopeLine: 2, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !11)
+!8 = !DISubroutineType(types: !9)
+!9 = !{!6, !10}
+!10 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !6, size: 64)
+!11 = !{!12}
+!12 = !DILocalVariable(name: "p", arg: 1, scope: !7, file: !1, line: 2, type: !10)
+!14 = !DILocation(line: 2, scope: !7)
+!15 = !DILocation(line: 2, column: 20, scope: !7)
+!16 = !DILocation(line: 2, column: 10, scope: !7)
\ No newline at end of file
>From f957840e54ba8c1fffa52ab50428868ac1ef4d2a Mon Sep 17 00:00:00 2001
From: Shivam Kunwar <phyBrackets at users.noreply.github.com>
Date: Wed, 18 Mar 2026 14:02:39 +0530
Subject: [PATCH 4/5] Address review feedbacks
---
llvm/lib/CodeGen/AsmPrinter/DwarfExpression.cpp | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/llvm/lib/CodeGen/AsmPrinter/DwarfExpression.cpp b/llvm/lib/CodeGen/AsmPrinter/DwarfExpression.cpp
index 79a5bc208f1fb..e1e31ff628e31 100644
--- a/llvm/lib/CodeGen/AsmPrinter/DwarfExpression.cpp
+++ b/llvm/lib/CodeGen/AsmPrinter/DwarfExpression.cpp
@@ -723,6 +723,12 @@ bool DwarfExpression::addExpression(
emitUnsigned(Op->getArg(0));
emitSigned(Op->getArg(1));
break;
+ case dwarf::DW_OP_LLVM_implicit_pointer:
+ // Handled in DwarfCompileUnit::emitImplicitPointerLocation for
+ // Loc::Single variables. If we reach here, the variable has a
+ // location list or other unsupported path. Drop the
+ // location rather than crashing.
+ return false;
default:
llvm_unreachable("unhandled opcode found in expression");
}
>From bf1e26d922f15c66fd6827da0101b9d9d2579cf1 Mon Sep 17 00:00:00 2001
From: Shivam Kunwar <phyBrackets at users.noreply.github.com>
Date: Fri, 20 Mar 2026 12:03:52 +0530
Subject: [PATCH 5/5] [ArgumentPromotion] Emit DW_OP_LLVM_implicit_pointer for
promoted pointer args
---
llvm/lib/Transforms/IPO/ArgumentPromotion.cpp | 26 ++++++++-
.../X86/implicit-pointer-argpromotion.ll | 53 +++++++++++++++++++
.../implicit-pointer-dbginfo.ll | 50 +++++++++++++++++
3 files changed, 127 insertions(+), 2 deletions(-)
create mode 100644 llvm/test/DebugInfo/X86/implicit-pointer-argpromotion.ll
create mode 100644 llvm/test/Transforms/ArgumentPromotion/implicit-pointer-dbginfo.ll
diff --git a/llvm/lib/Transforms/IPO/ArgumentPromotion.cpp b/llvm/lib/Transforms/IPO/ArgumentPromotion.cpp
index cfbf9dec01619..b2712718b3dd3 100644
--- a/llvm/lib/Transforms/IPO/ArgumentPromotion.cpp
+++ b/llvm/lib/Transforms/IPO/ArgumentPromotion.cpp
@@ -52,6 +52,7 @@
#include "llvm/IR/Constants.h"
#include "llvm/IR/DIBuilder.h"
#include "llvm/IR/DataLayout.h"
+#include "llvm/IR/DebugInfo.h"
#include "llvm/IR/DerivedTypes.h"
#include "llvm/IR/Dominators.h"
#include "llvm/IR/Function.h"
@@ -335,8 +336,29 @@ doPromotion(Function *F, FunctionAnalysisManager &FAM,
continue;
}
- // There potentially are metadata uses for things like llvm.dbg.value.
- // Replace them with poison, after handling the other regular uses.
+ const auto &ArgParts = ArgsToPromote.find(&Arg)->second;
+
+ // For single-part promoted pointer arguments, preserve debug info by
+ // emitting DW_OP_LLVM_implicit_pointer referencing the promoted scalar
+ // value.
+ // Multi-part promotions and dead arguments fall back to poisoning
+ // (multi-part would need DW_OP_LLVM_fragment combined with implicit
+ // pointer, which is future work).
+ if (ArgParts.size() == 1 && !Arg.use_empty()) {
+ Argument *PromotedNewArg = &*I2;
+ SmallVector<DbgVariableRecord *> DVRs;
+ findDbgUsers(&Arg, DVRs);
+ if (!DVRs.empty()) {
+ auto *ImplicitPtrExpr = DIExpression::get(
+ Arg.getContext(), {dwarf::DW_OP_LLVM_implicit_pointer});
+ for (auto *DVR : DVRs) {
+ DVR->replaceVariableLocationOp(&Arg, PromotedNewArg);
+ DVR->setExpression(ImplicitPtrExpr);
+ }
+ }
+ }
+
+ // Replace any remaining metadata uses with poison.
llvm::scope_exit RauwPoisonMetadata(
[&]() { Arg.replaceAllUsesWith(PoisonValue::get(Arg.getType())); });
diff --git a/llvm/test/DebugInfo/X86/implicit-pointer-argpromotion.ll b/llvm/test/DebugInfo/X86/implicit-pointer-argpromotion.ll
new file mode 100644
index 0000000000000..54bcdea34801a
--- /dev/null
+++ b/llvm/test/DebugInfo/X86/implicit-pointer-argpromotion.ll
@@ -0,0 +1,53 @@
+; RUN: opt -passes=argpromotion -S < %s | \
+; RUN: llc -mtriple=x86_64-linux-gnu -filetype=obj -o %t
+; RUN: llvm-dwarfdump --debug-info %t | FileCheck %s
+
+; End-to-end test: ArgumentPromotion preserves debug info via
+; DW_OP_implicit_pointer, which the backend lowers to DWARF.
+
+; CHECK: DW_TAG_formal_parameter
+; CHECK: DW_AT_location (DW_OP_implicit_pointer
+; CHECK: DW_AT_name ("p")
+; CHECK: DW_TAG_dwarf_procedure
+
+define internal i32 @foo(ptr %p) !dbg !7 {
+entry:
+ #dbg_value(ptr %p, !12, !DIExpression(), !14)
+ %val = load i32, ptr %p, align 4, !dbg !15
+ %add = add nsw i32 %val, 5, !dbg !15
+ ret i32 %add, !dbg !16
+}
+
+define i32 @bar(i32 %a) !dbg !17 {
+entry:
+ %x = alloca i32, align 4, !dbg !20
+ store i32 %a, ptr %x, align 4, !dbg !20
+ %result = call i32 @foo(ptr %x), !dbg !20
+ ret i32 %result, !dbg !20
+}
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!3, !4, !5}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, enums: !2)
+!1 = !DIFile(filename: "test.c", directory: "/tmp")
+!2 = !{}
+!3 = !{i32 7, !"Dwarf Version", i32 5}
+!4 = !{i32 2, !"Debug Info Version", i32 3}
+!5 = !{i32 1, !"wchar_size", i32 4}
+!6 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+
+!7 = distinct !DISubprogram(name: "foo", scope: !1, file: !1, line: 2, type: !8, scopeLine: 2, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !11)
+!8 = !DISubroutineType(types: !9)
+!9 = !{!6, !10}
+!10 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !6, size: 64)
+!11 = !{!12}
+!12 = !DILocalVariable(name: "p", arg: 1, scope: !7, file: !1, line: 2, type: !10)
+!14 = !DILocation(line: 2, scope: !7)
+!15 = !DILocation(line: 2, column: 20, scope: !7)
+!16 = !DILocation(line: 2, column: 10, scope: !7)
+
+!17 = distinct !DISubprogram(name: "bar", scope: !1, file: !1, line: 5, type: !18, scopeLine: 5, spFlags: DISPFlagDefinition, unit: !0)
+!18 = !DISubroutineType(types: !19)
+!19 = !{!6, !6}
+!20 = !DILocation(line: 6, scope: !17)
\ No newline at end of file
diff --git a/llvm/test/Transforms/ArgumentPromotion/implicit-pointer-dbginfo.ll b/llvm/test/Transforms/ArgumentPromotion/implicit-pointer-dbginfo.ll
new file mode 100644
index 0000000000000..f962bfdedebcb
--- /dev/null
+++ b/llvm/test/Transforms/ArgumentPromotion/implicit-pointer-dbginfo.ll
@@ -0,0 +1,50 @@
+; RUN: opt -passes=argpromotion -S < %s | FileCheck %s
+
+; Test that ArgumentPromotion emits DW_OP_LLVM_implicit_pointer for
+; promoted single-part pointer arguments instead of poisoning debug info.
+
+; CHECK: define internal i32 @foo(i32 %p.0.val)
+; CHECK: #dbg_value(i32 %p.0.val, ![[VAR:[0-9]+]], !DIExpression(DW_OP_LLVM_implicit_pointer),
+; CHECK: ![[VAR]] = !DILocalVariable(name: "p"
+
+define internal i32 @foo(ptr %p) !dbg !7 {
+entry:
+ #dbg_value(ptr %p, !12, !DIExpression(), !14)
+ %val = load i32, ptr %p, align 4, !dbg !15
+ %add = add nsw i32 %val, 5, !dbg !15
+ ret i32 %add, !dbg !16
+}
+
+define i32 @bar(i32 %a) !dbg !17 {
+entry:
+ %x = alloca i32, align 4, !dbg !20
+ store i32 %a, ptr %x, align 4, !dbg !20
+ %result = call i32 @foo(ptr %x), !dbg !20
+ ret i32 %result, !dbg !20
+}
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!3, !4, !5}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, enums: !2)
+!1 = !DIFile(filename: "test.c", directory: "/tmp")
+!2 = !{}
+!3 = !{i32 7, !"Dwarf Version", i32 5}
+!4 = !{i32 2, !"Debug Info Version", i32 3}
+!5 = !{i32 1, !"wchar_size", i32 4}
+!6 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+
+!7 = distinct !DISubprogram(name: "foo", scope: !1, file: !1, line: 2, type: !8, scopeLine: 2, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !11)
+!8 = !DISubroutineType(types: !9)
+!9 = !{!6, !10}
+!10 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !6, size: 64)
+!11 = !{!12}
+!12 = !DILocalVariable(name: "p", arg: 1, scope: !7, file: !1, line: 2, type: !10)
+!14 = !DILocation(line: 2, scope: !7)
+!15 = !DILocation(line: 2, column: 20, scope: !7)
+!16 = !DILocation(line: 2, column: 10, scope: !7)
+
+!17 = distinct !DISubprogram(name: "bar", scope: !1, file: !1, line: 5, type: !18, scopeLine: 5, spFlags: DISPFlagDefinition, unit: !0)
+!18 = !DISubroutineType(types: !19)
+!19 = !{!6, !6}
+!20 = !DILocation(line: 6, scope: !17)
\ No newline at end of file
More information about the llvm-commits
mailing list