[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
Mon Mar 30 21:35:16 PDT 2026


https://github.com/phyBrackets updated https://github.com/llvm/llvm-project/pull/187642

>From 5ca363aadf3ee1a1ffde7868faa59520592066f2 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/3] [DebugInfo] Lower DW_OP_LLVM_implicit_pointer to DWARF

---
 .../CodeGen/AsmPrinter/DwarfCompileUnit.cpp   | 47 +++++++------
 .../DebugInfo/X86/implicit-pointer-dwarf5.ll  | 68 +++----------------
 2 files changed, 35 insertions(+), 80 deletions(-)

diff --git a/llvm/lib/CodeGen/AsmPrinter/DwarfCompileUnit.cpp b/llvm/lib/CodeGen/AsmPrinter/DwarfCompileUnit.cpp
index 630c056739c4a..3acfb08a4f509 100644
--- a/llvm/lib/CodeGen/AsmPrinter/DwarfCompileUnit.cpp
+++ b/llvm/lib/CodeGen/AsmPrinter/DwarfCompileUnit.cpp
@@ -796,7 +796,7 @@ static const DIType *resolveTypeQualifiers(const DIType *Ty) {
 bool DwarfCompileUnit::emitImplicitPointerLocation(const Loc::Single &Single,
                                                    const DbgVariable &DV,
                                                    DIE &VariableDie) {
-  const DIExpression *Expr = Single.getExpr();
+  const auto *Expr = Single.getExpr();
   if (!Expr)
     return false;
 
@@ -804,11 +804,12 @@ 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) 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.
+  // 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() ||
@@ -818,18 +819,15 @@ bool DwarfCompileUnit::emitImplicitPointerLocation(const Loc::Single &Single,
   if (DD->getDwarfVersion() < 4)
     return false;
 
-  const DbgValueLoc &DVal = Single.getValueLoc();
+  const auto &DVal = Single.getValueLoc();
   if (DVal.isVariadic())
     return false;
 
-  assert(!DVal.getLocEntries().empty() &&
-         "Non-variadic value must have one entry");
-  const DbgValueLocEntry &Entry = DVal.getLocEntries()[0];
+  const auto &Entry = DVal.getLocEntries()[0];
 
   // Resolve the variable's type, stripping qualifiers and typedefs,
-  // to find the pointer or reference type underneath.
-  // The verifier rejects cyclic type references, so this loop terminates.
-  const DIDerivedType *PtrTy =
+  // to find the pointer or reference type.
+  const auto *PtrTy =
       dyn_cast_or_null<DIDerivedType>(resolveTypeQualifiers(DV.getType()));
   if (!PtrTy)
     return false;
@@ -839,7 +837,7 @@ bool DwarfCompileUnit::emitImplicitPointerLocation(const Loc::Single &Single,
       PtrTy->getTag() != dwarf::DW_TAG_rvalue_reference_type)
     return false;
 
-  const DIType *PointeeTy = PtrTy->getBaseType();
+  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
@@ -847,31 +845,36 @@ bool DwarfCompileUnit::emitImplicitPointerLocation(const Loc::Single &Single,
   // struct member for two different pointer parameters).
   DIE *ArtificialDIEPtr = nullptr;
   if (Entry.isInt() && PointeeTy) {
-    auto It = ImplicitPointerDIEs.find({PointeeTy, Entry.getInt()});
+    auto Key = std::make_pair(PointeeTy, Entry.getInt());
+    auto It = ImplicitPointerDIEs.find(Key);
     if (It != ImplicitPointerDIEs.end())
       ArtificialDIEPtr = It->second;
   }
 
   if (!ArtificialDIEPtr) {
-    DIE &ProcDIE = createAndAddDIE(dwarf::DW_TAG_dwarf_procedure, getUnitDie());
+    DIE &ArtificialDIE = createAndAddDIE(dwarf::DW_TAG_variable, getUnitDie());
+    addFlag(ArtificialDIE, dwarf::DW_AT_artificial);
+
+    if (PointeeTy)
+      addType(ArtificialDIE, PointeeTy);
 
     if (Entry.isLocation()) {
-      addAddress(ProcDIE, dwarf::DW_AT_location, Entry.getLoc());
+      addAddress(ArtificialDIE, dwarf::DW_AT_location, Entry.getLoc());
     } else if (Entry.isInt()) {
       if (PointeeTy)
-        addConstantValue(ProcDIE, Entry.getInt(), PointeeTy);
+        addConstantValue(ArtificialDIE, Entry.getInt(), PointeeTy);
     } else if (Entry.isConstantFP()) {
-      addConstantFPValue(ProcDIE, Entry.getConstantFP());
+      addConstantFPValue(ArtificialDIE, Entry.getConstantFP());
     } else {
       return false;
     }
 
-    ArtificialDIEPtr = &ProcDIE;
+    ArtificialDIEPtr = &ArtificialDIE;
 
     // Cache constant entries for de-duplication.
     if (Entry.isInt() && PointeeTy)
-      ImplicitPointerDIEs.insert(
-          {{PointeeTy, Entry.getInt()}, ArtificialDIEPtr});
+      ImplicitPointerDIEs[std::make_pair(PointeeTy, Entry.getInt())] =
+          ArtificialDIEPtr;
   }
 
   auto *Loc = new (DIEValueAllocator) DIELoc;
diff --git a/llvm/test/DebugInfo/X86/implicit-pointer-dwarf5.ll b/llvm/test/DebugInfo/X86/implicit-pointer-dwarf5.ll
index 6692c12997e2f..2895715a9b03a 100644
--- a/llvm/test/DebugInfo/X86/implicit-pointer-dwarf5.ll
+++ b/llvm/test/DebugInfo/X86/implicit-pointer-dwarf5.ll
@@ -1,16 +1,20 @@
 ; RUN: llc -mtriple=x86_64-linux-gnu -filetype=obj -o %t %s
 ; RUN: llvm-dwarfdump --debug-info %t | FileCheck %s
 
-; Test DW_OP_LLVM_implicit_pointer lowering to DWARF 5, using
-; DW_TAG_dwarf_procedure for the artificial DIEs.
+; 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_dwarf_procedure
+; 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
@@ -19,31 +23,11 @@
 ; CHECK:        DW_AT_location (DW_OP_implicit_pointer
 ; CHECK:        DW_AT_name ("q")
 
-; CHECK:      DW_TAG_dwarf_procedure
+; CHECK:      DW_TAG_variable
+; CHECK-NEXT:   DW_AT_artificial (true)
+; CHECK-NEXT:   DW_AT_type {{.*}} "int"
 ; 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)
@@ -57,19 +41,6 @@ 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}
 
@@ -97,22 +68,3 @@ entry:
 !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)
-
-!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 1da58040206b7fa5d2b0f051e4ef2268de45baba 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/3] Address review feedbacks

---
 .../CodeGen/AsmPrinter/DwarfCompileUnit.cpp   | 47 ++++++-------
 .../DebugInfo/X86/implicit-pointer-dwarf5.ll  | 68 ++++++++++++++++---
 2 files changed, 80 insertions(+), 35 deletions(-)

diff --git a/llvm/lib/CodeGen/AsmPrinter/DwarfCompileUnit.cpp b/llvm/lib/CodeGen/AsmPrinter/DwarfCompileUnit.cpp
index 3acfb08a4f509..630c056739c4a 100644
--- a/llvm/lib/CodeGen/AsmPrinter/DwarfCompileUnit.cpp
+++ b/llvm/lib/CodeGen/AsmPrinter/DwarfCompileUnit.cpp
@@ -796,7 +796,7 @@ static const DIType *resolveTypeQualifiers(const DIType *Ty) {
 bool DwarfCompileUnit::emitImplicitPointerLocation(const Loc::Single &Single,
                                                    const DbgVariable &DV,
                                                    DIE &VariableDie) {
-  const auto *Expr = Single.getExpr();
+  const DIExpression *Expr = Single.getExpr();
   if (!Expr)
     return false;
 
@@ -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() ||
@@ -819,15 +818,18 @@ bool DwarfCompileUnit::emitImplicitPointerLocation(const Loc::Single &Single,
   if (DD->getDwarfVersion() < 4)
     return false;
 
-  const auto &DVal = Single.getValueLoc();
+  const DbgValueLoc &DVal = Single.getValueLoc();
   if (DVal.isVariadic())
     return false;
 
-  const auto &Entry = DVal.getLocEntries()[0];
+  assert(!DVal.getLocEntries().empty() &&
+         "Non-variadic value must have one entry");
+  const DbgValueLocEntry &Entry = DVal.getLocEntries()[0];
 
   // Resolve the variable's type, stripping qualifiers and typedefs,
-  // to find the pointer or reference type.
-  const auto *PtrTy =
+  // to find the pointer or reference type underneath.
+  // The verifier rejects cyclic type references, so this loop terminates.
+  const DIDerivedType *PtrTy =
       dyn_cast_or_null<DIDerivedType>(resolveTypeQualifiers(DV.getType()));
   if (!PtrTy)
     return false;
@@ -837,7 +839,7 @@ bool DwarfCompileUnit::emitImplicitPointerLocation(const Loc::Single &Single,
       PtrTy->getTag() != dwarf::DW_TAG_rvalue_reference_type)
     return false;
 
-  const auto *PointeeTy = PtrTy->getBaseType();
+  const DIType *PointeeTy = PtrTy->getBaseType();
 
   // Try to reuse an existing artificial DIE for constant integer values.
   // This avoids duplicate DIEs when multiple pointer variables reference
@@ -845,36 +847,31 @@ bool DwarfCompileUnit::emitImplicitPointerLocation(const Loc::Single &Single,
   // 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);
+    auto It = ImplicitPointerDIEs.find({PointeeTy, Entry.getInt()});
     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);
+    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)
-      ImplicitPointerDIEs[std::make_pair(PointeeTy, Entry.getInt())] =
-          ArtificialDIEPtr;
+      ImplicitPointerDIEs.insert(
+          {{PointeeTy, Entry.getInt()}, ArtificialDIEPtr});
   }
 
   auto *Loc = new (DIEValueAllocator) DIELoc;
diff --git a/llvm/test/DebugInfo/X86/implicit-pointer-dwarf5.ll b/llvm/test/DebugInfo/X86/implicit-pointer-dwarf5.ll
index 2895715a9b03a..b02e3fa60b76b 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}
 
@@ -68,3 +97,22 @@ entry:
 !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)
+
+!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}

>From 0f80d8fad4c63a296557e526eeb3456147c327c8 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 3/3] [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