[llvm] [RFC] Emit dwarf data for signature-changed or new functions (PR #157349)

via llvm-commits llvm-commits at lists.llvm.org
Sat Oct 4 09:37:09 PDT 2025


https://github.com/yonghong-song updated https://github.com/llvm/llvm-project/pull/157349

>From 6b72b03ea2c6fca18a536c49be875a16b9abcf4d Mon Sep 17 00:00:00 2001
From: Yonghong Song <yonghong.song at linux.dev>
Date: Sat, 6 Sep 2025 23:09:01 -0700
Subject: [PATCH 1/3] [ArgPromotion] Add DW_CC_nocall to DISubprogram

ArgumentPromotion pass may change function signatures. If this happens
and debuginfo is enabled, let us add DW_CC_nocall to debuginfo so it is
clear that the function signature has changed.
DeadArgumentElimination ([1]) has similar implementation.

Also fix an ArgumentPromotion test due to adding DW_CC_nocall to
debuginfo.

  [1] https://github.com/llvm/llvm-project/commit/340b0ca90095d838f095271aaa1098fa1bd5ecbe
---
 llvm/lib/Transforms/IPO/ArgumentPromotion.cpp | 11 +++++++++++
 llvm/test/Transforms/ArgumentPromotion/dbg.ll |  6 +++++-
 2 files changed, 16 insertions(+), 1 deletion(-)

diff --git a/llvm/lib/Transforms/IPO/ArgumentPromotion.cpp b/llvm/lib/Transforms/IPO/ArgumentPromotion.cpp
index 262c902d40d2d..87b0d069ec04e 100644
--- a/llvm/lib/Transforms/IPO/ArgumentPromotion.cpp
+++ b/llvm/lib/Transforms/IPO/ArgumentPromotion.cpp
@@ -50,6 +50,7 @@
 #include "llvm/IR/BasicBlock.h"
 #include "llvm/IR/CFG.h"
 #include "llvm/IR/Constants.h"
+#include "llvm/IR/DIBuilder.h"
 #include "llvm/IR/DataLayout.h"
 #include "llvm/IR/DerivedTypes.h"
 #include "llvm/IR/Dominators.h"
@@ -432,6 +433,16 @@ doPromotion(Function *F, FunctionAnalysisManager &FAM,
     PromoteMemToReg(Allocas, DT, &AC);
   }
 
+  // If argument(s) are dead (hence removed) or promoted, probably the function
+  // does not follow standard calling convention anymore. Add DW_CC_nocall to
+  // DISubroutineType to inform debugger that it may not be safe to call this
+  // function.
+  DISubprogram *SP = NF->getSubprogram();
+  if (SP) {
+    auto Temp = SP->getType()->cloneWithCC(llvm::dwarf::DW_CC_nocall);
+    SP->replaceType(MDNode::replaceWithPermanent(std::move(Temp)));
+  }
+
   return NF;
 }
 
diff --git a/llvm/test/Transforms/ArgumentPromotion/dbg.ll b/llvm/test/Transforms/ArgumentPromotion/dbg.ll
index 6a14facfb36a2..ce86aaa3884de 100644
--- a/llvm/test/Transforms/ArgumentPromotion/dbg.ll
+++ b/llvm/test/Transforms/ArgumentPromotion/dbg.ll
@@ -53,7 +53,11 @@ define void @caller(ptr %Y, ptr %P) {
 
 !0 = !{i32 2, !"Debug Info Version", i32 3}
 !1 = !DILocation(line: 8, scope: !2)
-!2 = distinct !DISubprogram(name: "test", file: !5, line: 3, isLocal: true, isDefinition: true, virtualIndex: 6, flags: DIFlagPrototyped, isOptimized: false, unit: !3, scopeLine: 3, scope: null)
+!2 = distinct !DISubprogram(name: "test", file: !5, line: 3, type: !7, isLocal: true, isDefinition: true, flags: DIFlagPrototyped, isOptimized: false, unit: !3, scopeLine: 3, scope: null)
 !3 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus, producer: "clang version 3.5.0 ", isOptimized: false, emissionKind: LineTablesOnly, file: !5)
 !5 = !DIFile(filename: "test.c", directory: "")
 !6 = !DILocation(line: 9, scope: !2)
+!7 = !DISubroutineType(types: !8)
+!8 = !{null, !9}
+!9 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !10)
+!10 = !DIBasicType(name: "int",  size: 32, encoding: DW_ATE_signed)

>From 0a87115173804d3f2eed94986b4fa0010ac68d8a Mon Sep 17 00:00:00 2001
From: Yonghong Song <yonghong.song at linux.dev>
Date: Thu, 18 Sep 2025 12:37:15 -0700
Subject: [PATCH 2/3] [SampleProfile][Test] Fix DISubroutineType debuginfo for
 two tests

During development of emitting dwarf data for signature-changed or new
functions, I found two test failures
  llvm/test/Transforms/SampleProfile/ctxsplit.ll
  llvm/test/Transforms/SampleProfile/flattened.ll
due to incorrect DISubroutineType(s). This patch fixed the issue with
proper types.
---
 llvm/test/Transforms/SampleProfile/ctxsplit.ll  | 8 +++++---
 llvm/test/Transforms/SampleProfile/flattened.ll | 5 ++++-
 2 files changed, 9 insertions(+), 4 deletions(-)

diff --git a/llvm/test/Transforms/SampleProfile/ctxsplit.ll b/llvm/test/Transforms/SampleProfile/ctxsplit.ll
index 46e088a63e941..4acd8b5861c14 100644
--- a/llvm/test/Transforms/SampleProfile/ctxsplit.ll
+++ b/llvm/test/Transforms/SampleProfile/ctxsplit.ll
@@ -51,9 +51,11 @@ attributes #0 = { "use-sample-profile" }
 !4 = !{i32 2, !"Debug Info Version", i32 3}
 !5 = !{i32 1, !"wchar_size", i32 4}
 !6 = !{!"clang version 8.0.0 (trunk 345241)"}
-!7 = distinct !DISubprogram(name: "foo", scope: !1, file: !1, line: 1, type: !8, isLocal: false, isDefinition: true, scopeLine: 1, isOptimized: true, unit: !0, retainedNodes: !2)
+!7 = distinct !DISubprogram(name: "foo", scope: !1, file: !1, line: 1, type: !12, isLocal: false, isDefinition: true, scopeLine: 1, isOptimized: true, unit: !0, retainedNodes: !2)
 !8 = !DISubroutineType(types: !2)
 !9 = !DILocation(line: 2, column: 3, scope: !7)
-!10 = distinct !DISubprogram(name: "goo", scope: !1, file: !1, line: 8, type: !8, isLocal: false, isDefinition: true, scopeLine: 8, isOptimized: true, unit: !0, retainedNodes: !2)
+!10 = distinct !DISubprogram(name: "goo", scope: !1, file: !1, line: 8, type: !12, isLocal: false, isDefinition: true, scopeLine: 8, isOptimized: true, unit: !0, retainedNodes: !2)
 !11 = !DILocation(line: 10, column: 3, scope: !10)
-
+!12 = !DISubroutineType(types: !13)
+!13 = !{!14}
+!14 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
diff --git a/llvm/test/Transforms/SampleProfile/flattened.ll b/llvm/test/Transforms/SampleProfile/flattened.ll
index acc6459c99a33..e92264f693bb8 100644
--- a/llvm/test/Transforms/SampleProfile/flattened.ll
+++ b/llvm/test/Transforms/SampleProfile/flattened.ll
@@ -31,6 +31,9 @@ entry:
 !4 = !{i32 2, !"Debug Info Version", i32 3}
 !5 = !{i32 1, !"wchar_size", i32 4}
 !6 = !{!"clang version 8.0.0 (trunk 345241)"}
-!7 = distinct !DISubprogram(name: "foo", scope: !1, file: !1, line: 1, type: !8, isLocal: false, isDefinition: true, scopeLine: 1, isOptimized: true, unit: !0, retainedNodes: !2)
+!7 = distinct !DISubprogram(name: "foo", scope: !1, file: !1, line: 1, type: !10, isLocal: false, isDefinition: true, scopeLine: 1, isOptimized: true, unit: !0, retainedNodes: !2)
 !8 = !DISubroutineType(types: !2)
 !9 = !DILocation(line: 2, column: 3, scope: !7)
+!10 = !DISubroutineType(types: !11)
+!11 = !{!12}
+!12 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)

>From e8844f5e4912d58188b48ced361e820260f03cef Mon Sep 17 00:00:00 2001
From: Yonghong Song <yonghong.song at linux.dev>
Date: Tue, 29 Jul 2025 16:29:44 -0700
Subject: [PATCH 3/3] [LLVM] Emit dwarf data for changed-signature and new
 functions

Add a new pass EmitChangedFuncDebugInfo which will add dwarf for
additional functions including functions with signature change
and new functions.

The previous approach in [1] tries to add debuginfo for those
optimization passes which cause signature changes. Based on
discussion in [1], it is preferred to have a specific pass to
add debuginfo and later on dwarf generation can include those
new debuginfo.

The following is an example:
Source:
  $ cat test.c
  struct t { int a; };
  char *tar(struct t *a, struct t *d);
  __attribute__((noinline)) static char * foo(struct t *a, struct t *d, int b)
  {
    return tar(a, d);
  }
  char *bar(struct t *a, struct t *d)
  {
    return foo(a, d, 1);
  }
Compiled and dump dwarf with:
  clang -O2 -c -g test.c
  llvm-dwarfdump test.o

0x0000005c:   DW_TAG_subprogram
                DW_AT_low_pc    (0x0000000000000010)
                DW_AT_high_pc   (0x0000000000000015)
                DW_AT_frame_base        (DW_OP_reg7 RSP)
                DW_AT_linkage_name      ("foo")
                DW_AT_name      ("foo")
                DW_AT_decl_file ("/home/yhs/tests/sig-change/deadarg/test.c")
                DW_AT_decl_line (3)
                DW_AT_type      (0x000000bb "char *")
                DW_AT_artificial        (true)
                DW_AT_external  (true)

0x0000006c:     DW_TAG_formal_parameter
                  DW_AT_location        (DW_OP_reg5 RDI)
                  DW_AT_decl_file       ("/home/yhs/tests/sig-change/deadarg/test.c")
                  DW_AT_decl_line       (3)
                  DW_AT_type    (0x000000c4 "t *")

0x00000075:     DW_TAG_formal_parameter
                  DW_AT_location        (DW_OP_reg4 RSI)
                  DW_AT_decl_file       ("/home/yhs/tests/sig-change/deadarg/test.c")
                  DW_AT_decl_line       (3)
                  DW_AT_type    (0x000000c4 "t *")

0x0000007e:     DW_TAG_inlined_subroutine
                  DW_AT_abstract_origin (0x0000009a "foo")
                  DW_AT_low_pc  (0x0000000000000010)
                  DW_AT_high_pc (0x0000000000000015)
                  DW_AT_call_file       ("/home/yhs/tests/sig-change/deadarg/test.c")
                  DW_AT_call_line       (0)

0x0000008a:       DW_TAG_formal_parameter
                    DW_AT_location      (DW_OP_reg5 RDI)
                    DW_AT_abstract_origin       (0x000000a2 "a")

0x00000091:       DW_TAG_formal_parameter
                    DW_AT_location      (DW_OP_reg4 RSI)
                    DW_AT_abstract_origin       (0x000000aa "d")

0x00000098:       NULL

0x00000099:     NULL

0x0000009a:   DW_TAG_subprogram
                DW_AT_name      ("foo")
                DW_AT_decl_file ("/home/yhs/tests/sig-change/deadarg/test.c")
                DW_AT_decl_line (3)
                DW_AT_prototyped        (true)
                DW_AT_type      (0x000000bb "char *")
                DW_AT_inline    (DW_INL_inlined)

0x000000a2:     DW_TAG_formal_parameter
                  DW_AT_name    ("a")
                  DW_AT_decl_file       ("/home/yhs/tests/sig-change/deadarg/test.c")
                  DW_AT_decl_line       (3)
                  DW_AT_type    (0x000000c4 "t *")

0x000000aa:     DW_TAG_formal_parameter
                  DW_AT_name    ("d")
                  DW_AT_decl_file       ("/home/yhs/tests/sig-change/deadarg/test.c")
                  DW_AT_decl_line       (3)
                  DW_AT_type    (0x000000c4 "t *")

0x000000b2:     DW_TAG_formal_parameter
                  DW_AT_name    ("b")
                  DW_AT_decl_file       ("/home/yhs/tests/sig-change/deadarg/test.c")
                  DW_AT_decl_line       (3)
                  DW_AT_type    (0x000000d8 "int")

0x000000ba:     NULL

There are some restrictions in the current implementation:
  - Only C language is supported
  - BPF target is excluded as one of main goals for this pull request
    is to generate proper vmlinux BTF for arch's like x86_64/arm64 etc.
  - Function must not be a intrinsic, decl only, return value size more
    than arch register size and func with variable arguments.

I have tested this patch set by building latest bpf-next linux kernel.
For no-lto case:
  65341 original number of functions
  1085  new functions with this patch
For thin-lto case:
  65595 original number of functions
  2492  new functions with this patch

  [1] https://github.com/llvm/llvm-project/pull/127855
---
 .../Utils/EmitChangedFuncDebugInfo.h          |  33 ++
 llvm/lib/Passes/PassBuilder.cpp               |   1 +
 llvm/lib/Passes/PassBuilderPipelines.cpp      |   8 +-
 llvm/lib/Passes/PassRegistry.def              |   1 +
 llvm/lib/Transforms/Utils/CMakeLists.txt      |   1 +
 .../Utils/EmitChangedFuncDebugInfo.cpp        | 505 ++++++++++++++++++
 llvm/test/Other/new-pm-defaults.ll            |   2 +
 .../Other/new-pm-thinlto-postlink-defaults.ll |   1 +
 .../new-pm-thinlto-postlink-pgo-defaults.ll   |   1 +
 ...-pm-thinlto-postlink-samplepgo-defaults.ll |   1 +
 ...mit-changed-func-debuginfo-argpromotion.ll | 104 ++++
 .../emit-changed-func-debuginfo-deadarg.ll    | 133 +++++
 .../emit-changed-func-debuginfo-struct-16B.ll |  81 +++
 ...mit-changed-func-debuginfo-struct-large.ll | 102 ++++
 14 files changed, 972 insertions(+), 2 deletions(-)
 create mode 100644 llvm/include/llvm/Transforms/Utils/EmitChangedFuncDebugInfo.h
 create mode 100644 llvm/lib/Transforms/Utils/EmitChangedFuncDebugInfo.cpp
 create mode 100644 llvm/test/Transforms/Util/emit-changed-func-debuginfo-argpromotion.ll
 create mode 100644 llvm/test/Transforms/Util/emit-changed-func-debuginfo-deadarg.ll
 create mode 100644 llvm/test/Transforms/Util/emit-changed-func-debuginfo-struct-16B.ll
 create mode 100644 llvm/test/Transforms/Util/emit-changed-func-debuginfo-struct-large.ll

diff --git a/llvm/include/llvm/Transforms/Utils/EmitChangedFuncDebugInfo.h b/llvm/include/llvm/Transforms/Utils/EmitChangedFuncDebugInfo.h
new file mode 100644
index 0000000000000..8d569cd95d7f7
--- /dev/null
+++ b/llvm/include/llvm/Transforms/Utils/EmitChangedFuncDebugInfo.h
@@ -0,0 +1,33 @@
+//===- EmitChangedFuncDebugInfo.h - Emit Additional Debug Info -*- C++ --*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+/// \file
+/// Emit debug info for changed or new funcs.
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_TRANSFORMS_UTILS_EMITCHANGEDFUNCDEBUGINFO_H
+#define LLVM_TRANSFORMS_UTILS_EMITCHANGEDFUNCDEBUGINFO_H
+
+#include "llvm/IR/PassManager.h"
+
+namespace llvm {
+
+class Module;
+
+// Pass that emits late dwarf.
+class EmitChangedFuncDebugInfoPass
+    : public PassInfoMixin<EmitChangedFuncDebugInfoPass> {
+public:
+  EmitChangedFuncDebugInfoPass() = default;
+
+  PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM);
+};
+
+} // end namespace llvm
+
+#endif // LLVM_TRANSFORMS_UTILS_EMITCHANGEDFUNCDEBUGINFO_H
diff --git a/llvm/lib/Passes/PassBuilder.cpp b/llvm/lib/Passes/PassBuilder.cpp
index c234623caecf9..a4013d88c7ad1 100644
--- a/llvm/lib/Passes/PassBuilder.cpp
+++ b/llvm/lib/Passes/PassBuilder.cpp
@@ -350,6 +350,7 @@
 #include "llvm/Transforms/Utils/DXILUpgrade.h"
 #include "llvm/Transforms/Utils/Debugify.h"
 #include "llvm/Transforms/Utils/DeclareRuntimeLibcalls.h"
+#include "llvm/Transforms/Utils/EmitChangedFuncDebugInfo.h"
 #include "llvm/Transforms/Utils/EntryExitInstrumenter.h"
 #include "llvm/Transforms/Utils/FixIrreducible.h"
 #include "llvm/Transforms/Utils/HelloWorld.h"
diff --git a/llvm/lib/Passes/PassBuilderPipelines.cpp b/llvm/lib/Passes/PassBuilderPipelines.cpp
index 7069e8d67c2f1..1c15e497001dc 100644
--- a/llvm/lib/Passes/PassBuilderPipelines.cpp
+++ b/llvm/lib/Passes/PassBuilderPipelines.cpp
@@ -135,6 +135,7 @@
 #include "llvm/Transforms/Utils/AssumeBundleBuilder.h"
 #include "llvm/Transforms/Utils/CanonicalizeAliases.h"
 #include "llvm/Transforms/Utils/CountVisits.h"
+#include "llvm/Transforms/Utils/EmitChangedFuncDebugInfo.h"
 #include "llvm/Transforms/Utils/EntryExitInstrumenter.h"
 #include "llvm/Transforms/Utils/ExtraPassManager.h"
 #include "llvm/Transforms/Utils/InjectTLIMappings.h"
@@ -1640,9 +1641,12 @@ PassBuilder::buildModuleOptimizationPipeline(OptimizationLevel Level,
   if (PTO.CallGraphProfile && !LTOPreLink)
     MPM.addPass(CGProfilePass(isLTOPostLink(LTOPhase)));
 
-  // RelLookupTableConverterPass runs later in LTO post-link pipeline.
-  if (!LTOPreLink)
+  // RelLookupTableConverterPass and EmitChangedFuncDebugInfoPass run later in
+  // LTO post-link pipeline.
+  if (!LTOPreLink) {
     MPM.addPass(RelLookupTableConverterPass());
+    MPM.addPass(EmitChangedFuncDebugInfoPass());
+  }
 
   return MPM;
 }
diff --git a/llvm/lib/Passes/PassRegistry.def b/llvm/lib/Passes/PassRegistry.def
index f0e7d36f78aab..9d2ef460c8164 100644
--- a/llvm/lib/Passes/PassRegistry.def
+++ b/llvm/lib/Passes/PassRegistry.def
@@ -75,6 +75,7 @@ MODULE_PASS("dfsan", DataFlowSanitizerPass())
 MODULE_PASS("dot-callgraph", CallGraphDOTPrinterPass())
 MODULE_PASS("dxil-upgrade", DXILUpgradePass())
 MODULE_PASS("elim-avail-extern", EliminateAvailableExternallyPass())
+MODULE_PASS("emit-changed-func-debuginfo", EmitChangedFuncDebugInfoPass())
 MODULE_PASS("extract-blocks", BlockExtractorPass({}, false))
 MODULE_PASS("expand-variadics",
             ExpandVariadicsPass(ExpandVariadicsMode::Disable))
diff --git a/llvm/lib/Transforms/Utils/CMakeLists.txt b/llvm/lib/Transforms/Utils/CMakeLists.txt
index f367ca2fdf56b..72291a0c7d8b0 100644
--- a/llvm/lib/Transforms/Utils/CMakeLists.txt
+++ b/llvm/lib/Transforms/Utils/CMakeLists.txt
@@ -23,6 +23,7 @@ add_llvm_component_library(LLVMTransformUtils
   DebugSSAUpdater.cpp
   DeclareRuntimeLibcalls.cpp
   DemoteRegToStack.cpp
+  EmitChangedFuncDebugInfo.cpp
   DXILUpgrade.cpp
   EntryExitInstrumenter.cpp
   EscapeEnumerator.cpp
diff --git a/llvm/lib/Transforms/Utils/EmitChangedFuncDebugInfo.cpp b/llvm/lib/Transforms/Utils/EmitChangedFuncDebugInfo.cpp
new file mode 100644
index 0000000000000..0bef66f97d276
--- /dev/null
+++ b/llvm/lib/Transforms/Utils/EmitChangedFuncDebugInfo.cpp
@@ -0,0 +1,505 @@
+//==- EmitChangedFuncDebugInfoPass - Emit Additional Debug Info -*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file implements emitting debug info for functions with changed
+// signatures or new functions.
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Transforms/Utils/EmitChangedFuncDebugInfo.h"
+#include "llvm/IR/DIBuilder.h"
+#include "llvm/IR/IRBuilder.h"
+#include "llvm/IR/IntrinsicInst.h"
+#include "llvm/IR/Module.h"
+#include "llvm/TargetParser/Triple.h"
+
+using namespace llvm;
+
+static cl::opt<bool> DisableChangedFuncDBInfo(
+    "disable-changed-func-dbinfo", cl::Hidden, cl::init(false),
+    cl::desc("Disable debuginfo emission for changed func signatures"));
+
+// A struct param breaks into two actual arguments like
+//    static int count(struct user_arg_ptr argv, int max)
+// and the actual func signature:
+//    i32 @count(i8 range(i8 0, 2) %argv.coerce0, ptr %argv.coerce1)
+//    {
+//      #dbg_value(i8 %argv.coerce0, !14759,
+//      !DIExpression(DW_OP_LLVM_fragment, 0, 8), !14768)
+//      #dbg_value(ptr %argv.coerce1, !14759,
+//      !DIExpression(DW_OP_LLVM_fragment, 64, 64), !14768)
+//      ...
+//    }
+static DIType *getTypeFromExpr(DIBuilder &DIB, DIExpression *Expr,
+                               DICompositeType *DTy) {
+  for (auto Op : Expr->expr_ops()) {
+    if (Op.getOp() != dwarf::DW_OP_LLVM_fragment)
+      continue;
+
+    uint64_t BitOffset = Op.getArg(0);
+    uint64_t BitSize = Op.getArg(1);
+
+    for (auto *Element : DTy->getElements()) {
+      auto Elem = cast<DIDerivedType>(Element);
+      if (Elem->getSizeInBits() == BitSize &&
+          Elem->getOffsetInBits() == BitOffset)
+        return Elem->getBaseType();
+      else
+        // Create a new int type. For example, original debuginfo is an array.
+        return DIB.createBasicType("int" + std::to_string(BitSize), BitSize,
+                                   dwarf::DW_ATE_signed);
+    }
+  }
+  return nullptr;
+}
+
+static bool getArg(Module &M, unsigned Idx, BasicBlock &FirstBB, DIBuilder &DIB,
+                   Function *F, DISubprogram *OldSP, DISubprogram *NewSP,
+                   SmallVector<Metadata *, 5> &TypeList,
+                   SmallVector<Metadata *, 5> &ArgList,
+                   unsigned PointerBitWidth) {
+  for (Instruction &I : FirstBB) {
+    for (DbgRecord &DR : I.getDbgRecordRange()) {
+      auto *DVR = dyn_cast<DbgVariableRecord>(&DR);
+      if (!DVR)
+        continue;
+      // All of DbgVariableRecord::LocationType::{Value,Assign,Declare}
+      // are covered.
+      Metadata *Loc = DVR->getRawLocation();
+      auto *ValueMDN = dyn_cast<ValueAsMetadata>(Loc);
+      if (!ValueMDN)
+        continue;
+
+      Value *MDNValue = ValueMDN->getValue();
+      if (!MDNValue)
+        continue;
+
+      Type *Ty = ValueMDN->getType();
+      auto *Var = DVR->getVariable();
+      if (!Var->getArg())
+        continue;
+
+      // Strip modifiers (const, volatile, etc.)
+      DIType *DITy = Var->getType();
+      while (auto *DTy = dyn_cast<DIDerivedType>(DITy)) {
+        if (DTy->getTag() == dwarf::DW_TAG_pointer_type) {
+          DITy = DTy;
+          break;
+        }
+        DITy = DTy->getBaseType();
+      }
+
+      if (dyn_cast<AllocaInst>(MDNValue)) {
+        // A struct turned into a pointer to struct.
+        //   @rhashtable_lookup_fast(ptr noundef %key,
+        //        ptr noundef readonly byval(%struct.rhashtable_params)
+        //        align 8 captures(none) %params) {
+        //      ...
+        //      %MyAlloca = alloca [160 x i8], align 32
+        //      %0 = ptrtoint ptr %MyAlloca to i64
+        //      %1 = add i64 %0, 32
+        //      %2 = inttoptr i64 %1 to ptr
+        //      ...
+        //      call void @llvm.memcpy.p0.p0.i64(ptr align 8 %2, ptr align 8
+        //                                       %params, i64 40, i1 false)
+        //        #dbg_value(ptr @offdevs, !15308, !DIExpression(), !15312)
+        //        #dbg_value(ptr %key, !15309, !DIExpression(), !15312)
+        //        #dbg_declare(ptr %MyAlloca, !15310,
+        //                     !DIExpression(DW_OP_plus_uconst, 32), !15313)
+        //      tail call void @__rcu_read_lock() #14, !dbg !15314
+        //   }
+        if (Var->getName() != F->getArg(Idx)->getName())
+          continue;
+      } else if (MDNValue != F->getArg(Idx) &&
+                 !(Ty->isIntegerTy() && dyn_cast<DIDerivedType>(DITy))) {
+        // Ty->isIntegerTy() && dyn_cast<DIDerivedType>(DITy) means that
+        // actual type is integer and debug info is a pointer, so it is
+        // likely to be caused by argument promotion and will be processed
+        // later.
+        //
+        // Handle the following pattern:
+        //   ... @vgacon_do_font_op(..., i32 noundef, i1 noundef zeroext %ch512)
+        //   ... {
+        //     ...
+        //       #dbg_value(i32 %set, !8568, !DIExpression(), !8589)
+        //     %storedv = zext i1 %ch512 to i8
+        //       #dbg_value(i8 %storedv, !8569, !DIExpression(), !8589)
+        //     ...
+        //   }
+        Instruction *PrevI = I.getPrevNode();
+        if (!PrevI)
+          continue;
+        if (MDNValue != PrevI)
+          continue;
+        auto *ZExt = dyn_cast<ZExtInst>(PrevI);
+        if (!ZExt)
+          continue;
+        if (ZExt->getOperand(0) != F->getArg(Idx))
+          continue;
+      }
+
+      auto *Expr = DVR->getExpression();
+      DIType *ParamType = Var->getType();
+      bool NeedSuffix = false;
+      if (Ty->isIntegerTy()) {
+        if (auto *DTy = dyn_cast<DICompositeType>(DITy)) {
+          if (!Ty->isIntegerTy(DTy->getSizeInBits())) {
+            ParamType = getTypeFromExpr(DIB, Expr, DTy);
+            if (!ParamType)
+              return false;
+            NeedSuffix = true;
+          }
+        } else if (dyn_cast<DIDerivedType>(DITy)) {
+          // For argument promotion case where a pointer argument becomes an
+          // int.
+          ParamType = DIB.createBasicType(
+              "int" + std::to_string(Ty->getIntegerBitWidth()),
+              Ty->getIntegerBitWidth(), dwarf::DW_ATE_signed);
+        }
+      } else if (Ty->isPointerTy()) {
+        if (dyn_cast<DICompositeType>(DITy)) {
+          ParamType = DIB.createPointerType(DITy, PointerBitWidth);
+          NeedSuffix = true;
+        } else {
+          auto *DTy = dyn_cast<DIDerivedType>(DITy);
+          if (!DTy)
+            continue;
+          if (DTy->getTag() != dwarf::DW_TAG_pointer_type)
+            continue;
+        }
+      }
+
+      TypeList.push_back(ParamType);
+
+      std::string ArgName = F->getArg(Idx)->getName().str();
+      if (ArgName.empty()) {
+        ArgName = Var->getName().str();
+        if (NeedSuffix)
+          ArgName += std::string("__") + std::to_string(Idx);
+      }
+      Var = DIB.createParameterVariable(NewSP, StringRef(ArgName), Idx + 1,
+                                        OldSP->getUnit()->getFile(),
+                                        OldSP->getLine(), ParamType);
+      ArgList.push_back(Var);
+      return true;
+    }
+  }
+
+  /* The parameter is not handled due to poison value. There are two cases here:
+   *  - the parameter is not used at all. Somehow the parameter is not removed
+   *    with dead argument elimination pass.
+   *  - the parameter type changed, e.g., from a pointer type to an integer type
+   *    due to argument promotion pass.
+   * just create a new int type for the argument.
+   */
+  Type *Ty = F->getArg(Idx)->getType();
+  unsigned IntBitWidth = 32;
+  if (Ty->isIntegerTy())
+    IntBitWidth = cast<IntegerType>(Ty)->getBitWidth();
+
+  DIType *ParamType = DIB.createBasicType("int" + std::to_string(IntBitWidth),
+                                          IntBitWidth, dwarf::DW_ATE_signed);
+  if (!Ty->isIntegerTy())
+    ParamType = DIB.createPointerType(ParamType, PointerBitWidth);
+
+  std::string ArgName = F->getArg(Idx)->getName().str();
+  if (ArgName.empty())
+    ArgName += std::string("__") + std::to_string(Idx);
+  DILocalVariable *Var = DIB.createParameterVariable(
+      NewSP, StringRef(ArgName), Idx + 1, OldSP->getUnit()->getFile(),
+      OldSP->getLine(), ParamType);
+  TypeList.push_back(ParamType);
+  ArgList.push_back(Var);
+  return true;
+}
+
+static bool getTypeArgList(Module &M, DIBuilder &DIB, Function *F,
+                           DISubprogram *OldSP, DISubprogram *NewSP,
+                           SmallVector<Metadata *, 5> &TypeList,
+                           SmallVector<Metadata *, 5> &ArgList,
+                           unsigned PointerBitWidth) {
+  FunctionType *FTy = F->getFunctionType();
+  Type *RetTy = FTy->getReturnType();
+  if (RetTy->isVoidTy()) {
+    // Void return type may be due to optimization.
+    TypeList.push_back(nullptr);
+  } else {
+    // Optimization does not change return type from one
+    // non-void type to another non-void type.
+    DITypeRefArray TyArray = OldSP->getType()->getTypeArray();
+    TypeList.push_back(TyArray[0]);
+  }
+
+  unsigned NumArgs = FTy->getNumParams();
+  if (!NumArgs)
+    return true;
+
+  BasicBlock &FirstBB = F->getEntryBlock();
+  for (unsigned i = 0; i < NumArgs; ++i) {
+    if (!getArg(M, i, FirstBB, DIB, F, OldSP, NewSP, TypeList, ArgList,
+                PointerBitWidth))
+      return false;
+  }
+
+  return true;
+}
+
+static Metadata *
+mapAllDILocs(Metadata *M,
+             std::function<const DILocation *(const DILocation *)> X,
+             LLVMContext &Ctx) {
+  if (!M)
+    return nullptr;
+
+  if (auto *DL = dyn_cast<DILocation>(M))
+    return const_cast<DILocation *>(X(DL));
+
+  if (auto *N = dyn_cast<MDNode>(M)) {
+    SmallVector<Metadata *, 8> NewOps;
+    NewOps.reserve(N->getNumOperands());
+    for (const MDOperand &Op : N->operands())
+      NewOps.push_back(mapAllDILocs(Op.get(), X, Ctx));
+    // Tag nodes need not be distinct.
+    return MDNode::get(Ctx, NewOps);
+  }
+
+  // MDString / ConstantAsMetadata / etc.
+  return M;
+}
+
+static MDNode *cloneLoopIDReplacingAllDILocs(
+    MDNode *OldLoopID, std::function<const DILocation *(const DILocation *)> X,
+    LLVMContext &Ctx) {
+  SmallVector<Metadata *, 8> Ops;
+  Ops.reserve(OldLoopID->getNumOperands());
+  Ops.push_back(nullptr); // placeholder for self
+
+  // Copy/transform operands 1..N (operand 0 is always the self reference)
+  for (unsigned i = 1, e = OldLoopID->getNumOperands(); i < e; ++i) {
+    Metadata *Old = OldLoopID->getOperand(i).get();
+    Ops.push_back(mapAllDILocs(Old, X, Ctx));
+  }
+
+  MDNode *NewLoopID = MDNode::getDistinct(Ctx, Ops);
+  NewLoopID->replaceOperandWith(0, NewLoopID); // self reference
+  return NewLoopID;
+}
+
+// For a particular function, we do the following three steps:
+// 1. Collect new signatures for the function.
+// 2. Go through all function body for all DILocations
+//    add inlinedAt() for the new function.
+// 3. At the beginning of the function, add dbg_value
+//    for all actual arguments.
+static void generateDebugInfo(Module &M, Function *F,
+                              unsigned PointerBitWidth) {
+  DISubprogram *OldSP = F->getSubprogram();
+  DICompileUnit *CU = OldSP->getUnit();
+  DIBuilder DIB(M, /*AllowUnresolved=*/false, CU);
+
+  SmallVector<Metadata *, 5> TypeList;
+  SmallVector<Metadata *, 5> ArgList;
+
+  // Collect new signatures for the function.
+  DISubprogram *NewSP =
+      DIB.createFunction(OldSP->getScope(),     // Scope
+                         F->getName(),          // Name
+                         F->getName(),          // Linkage name
+                         CU->getFile(),         // File
+                         OldSP->getLine(),      // Line
+                         nullptr,               // DISubroutineType
+                         OldSP->getScopeLine(), // ScopeLine
+                         DINode::FlagZero, DISubprogram::SPFlagDefinition);
+  NewSP = DIB.createArtificialSubprogram(NewSP);
+
+  bool Success = getTypeArgList(M, DIB, F, OldSP, NewSP, TypeList, ArgList,
+                                PointerBitWidth);
+  if (!Success) {
+    DIB.finalize();
+    return;
+  }
+
+  DITypeRefArray DITypeArray = DIB.getOrCreateTypeArray(TypeList);
+  auto *SubroutineType = DIB.createSubroutineType(DITypeArray);
+  DINodeArray ArgArray = DIB.getOrCreateArray(ArgList);
+
+  NewSP->replaceType(SubroutineType);
+  NewSP->replaceRetainedNodes(ArgArray);
+
+  F->setSubprogram(NewSP);
+
+  // Go through the function itself to replace DILocations.
+  LLVMContext &Ctx = M.getContext();
+  DILocation *DL2 = DILocation::get(Ctx, 0, 0, NewSP, nullptr, 0, 0);
+  for (BasicBlock &BB : *F) {
+    for (Instruction &I : BB) {
+      for (DbgRecord &DR : I.getDbgRecordRange()) {
+        DebugLoc DL = DR.getDebugLoc();
+        auto *OldDL = DL.get();
+        SmallVector<DILocation *, 5> DLlist;
+
+        DLlist.push_back(OldDL);
+        while (OldDL->getInlinedAt()) {
+          OldDL = OldDL->getInlinedAt();
+          DLlist.push_back(OldDL);
+        }
+        DILocation *PrevLoc = DL2;
+        for (int i = DLlist.size() - 1; i >= 0; i--) {
+          OldDL = DLlist[i];
+          PrevLoc = DILocation::get(
+              Ctx, OldDL->getLine(), OldDL->getColumn(), OldDL->getScope(),
+              PrevLoc, OldDL->isImplicitCode(), OldDL->getAtomGroup(),
+              OldDL->getAtomRank());
+        }
+        DR.setDebugLoc(DebugLoc(const_cast<DILocation *>(PrevLoc)));
+      }
+      if (DebugLoc DL = I.getDebugLoc()) {
+        auto *OldDL = DL.get();
+        SmallVector<DILocation *, 5> DLlist;
+
+        DLlist.push_back(OldDL);
+        while (OldDL->getInlinedAt()) {
+          OldDL = OldDL->getInlinedAt();
+          DLlist.push_back(OldDL);
+        }
+        DILocation *PrevLoc = DL2;
+        for (int i = DLlist.size() - 1; i >= 0; i--) {
+          OldDL = DLlist[i];
+          PrevLoc = DILocation::get(
+              Ctx, OldDL->getLine(), OldDL->getColumn(), OldDL->getScope(),
+              PrevLoc, OldDL->isImplicitCode(), OldDL->getAtomGroup(),
+              OldDL->getAtomRank());
+        }
+        I.setDebugLoc(DebugLoc(PrevLoc));
+      }
+      if (MDNode *LoopID = I.getMetadata(LLVMContext::MD_loop)) {
+        auto X = [&](const DILocation *OldDL) -> const DILocation * {
+          return DILocation::get(Ctx, OldDL->getLine(), OldDL->getColumn(),
+                                 OldDL->getScope(), DL2,
+                                 OldDL->isImplicitCode(), OldDL->getAtomGroup(),
+                                 OldDL->getAtomRank());
+        };
+        MDNode *New = cloneLoopIDReplacingAllDILocs(LoopID, X, Ctx);
+        I.setMetadata(LLVMContext::MD_loop, New);
+      }
+    }
+  }
+
+  // At the beginning of the function, add dbg_values for true func signatures.
+  unsigned NumArgs = F->getFunctionType()->getNumParams();
+  if (NumArgs) {
+    BasicBlock::iterator InsertPt = F->getEntryBlock().getFirstInsertionPt();
+    for (int i = NumArgs - 1; i >= 0; --i) {
+      DILocalVariable *Var = cast<DILocalVariable>(ArgList[i]);
+      DIB.insertDbgValueIntrinsic(F->getArg(i), Var, DIB.createExpression(),
+                                  DL2, InsertPt);
+    }
+  }
+
+  DIB.finalize();
+}
+
+PreservedAnalyses EmitChangedFuncDebugInfoPass::run(Module &M,
+                                                    ModuleAnalysisManager &AM) {
+  if (DisableChangedFuncDBInfo)
+    return PreservedAnalyses::all();
+
+  // For C only
+  for (DICompileUnit *CU : M.debug_compile_units()) {
+    auto L = static_cast<llvm::dwarf::SourceLanguage>(CU->getSourceLanguage());
+    if (L != dwarf::DW_LANG_C && L != dwarf::DW_LANG_C89 &&
+        L != dwarf::DW_LANG_C99 && L != dwarf::DW_LANG_C11 &&
+        L != dwarf::DW_LANG_C17)
+      return PreservedAnalyses::all();
+  }
+
+  llvm::Triple T(M.getTargetTriple());
+
+  // FIXME: Skip if BPF target. Unlike other architectures, BPF target will
+  // generate BTF in LLVM. We can tune BPF target later.
+  if (T.isBPF())
+    return PreservedAnalyses::all();
+
+  unsigned PointerBitWidth = T.getArchPointerBitWidth();
+
+  SmallVector<Function *> ChangedFuncs;
+  for (auto &F : M) {
+    // Function must already have DebugInfo.
+    DISubprogram *SP = F.getSubprogram();
+    if (!SP)
+      continue;
+
+    // Ignore all intrinsics/declare-only functions.
+    if (F.isIntrinsic() || F.isDeclaration())
+      continue;
+
+    // Skip if the return value is a DICompositeType and its size is greater
+    // than PointerBitWidth.
+    DITypeRefArray TyArray = SP->getType()->getTypeArray();
+    if (TyArray.size() == 0)
+      continue;
+    DIType *DITy = TyArray[0];
+    while (auto *DTy = dyn_cast_or_null<DIDerivedType>(DITy)) {
+      if (DTy->getTag() == dwarf::DW_TAG_pointer_type) {
+        DITy = DTy;
+        break;
+      }
+      DITy = DTy->getBaseType();
+    }
+    if (auto *DTy = dyn_cast_or_null<DICompositeType>(DITy)) {
+      if (DTy->getSizeInBits() > PointerBitWidth)
+        continue;
+    }
+
+    // Skip if the func has variable number of arguments
+    if (TyArray.size() > 1 && TyArray[TyArray.size() - 1] == nullptr)
+      continue;
+
+    // For original functions with struct/union as the argument and
+    // if the argument size is greater than 8 bytes, consider this
+    // function as signature changed.
+    StringRef FName = F.getName();
+    if (!FName.contains('.')) {
+      uint8_t cc = SP->getType()->getCC();
+      if (cc != llvm::dwarf::DW_CC_nocall) {
+        bool SigChanged = false;
+        for (unsigned i = 1; i < TyArray.size(); ++i) {
+          DITy = TyArray[i];
+          while (auto *DTy = dyn_cast<DIDerivedType>(DITy)) {
+            if (DTy->getTag() == dwarf::DW_TAG_pointer_type) {
+              DITy = DTy;
+              break;
+            }
+            DITy = DTy->getBaseType();
+          }
+          if (auto *DTy = dyn_cast<DICompositeType>(DITy)) {
+            if (DTy->getSizeInBits() <= PointerBitWidth)
+              continue;
+            SigChanged = true;
+            break;
+          }
+        }
+        if (!SigChanged)
+          continue;
+      }
+    }
+
+    // Reset calling convention to DW_CC_normal as later the function will
+    // be marked as Artificial.
+    auto Temp = SP->getType()->cloneWithCC(llvm::dwarf::DW_CC_normal);
+    SP->replaceType(MDNode::replaceWithPermanent(std::move(Temp)));
+
+    ChangedFuncs.push_back(&F);
+  }
+
+  bool Changed = ChangedFuncs.size() != 0;
+  for (auto *F : ChangedFuncs)
+    generateDebugInfo(M, F, PointerBitWidth);
+
+  return Changed ? PreservedAnalyses::none() : PreservedAnalyses::all();
+}
diff --git a/llvm/test/Other/new-pm-defaults.ll b/llvm/test/Other/new-pm-defaults.ll
index 94e860b8ce304..650bcd6f0f98d 100644
--- a/llvm/test/Other/new-pm-defaults.ll
+++ b/llvm/test/Other/new-pm-defaults.ll
@@ -295,6 +295,8 @@
 ; CHECK-DEFAULT-NEXT: Running pass: CGProfilePass
 ; CHECK-DEFAULT-NEXT: Running pass: RelLookupTableConverterPass
 ; CHECK-LTO-NOT: Running pass: RelLookupTableConverterPass
+; CHECK-DEFAULT-NEXT: Running pass: EmitChangedFuncDebugInfoPass
+; CHECK-LTO-NOT: Running pass: EmitChangedFuncDebugInfoPass
 ; CHECK-O-NEXT: Running pass: AnnotationRemarksPass on foo
 ; CHECK-LTO-NEXT: Running pass: CanonicalizeAliasesPass
 ; CHECK-LTO-NEXT: Running pass: NameAnonGlobalPass
diff --git a/llvm/test/Other/new-pm-thinlto-postlink-defaults.ll b/llvm/test/Other/new-pm-thinlto-postlink-defaults.ll
index a08a140a35166..b913cd3e2d90b 100644
--- a/llvm/test/Other/new-pm-thinlto-postlink-defaults.ll
+++ b/llvm/test/Other/new-pm-thinlto-postlink-defaults.ll
@@ -208,6 +208,7 @@
 ; CHECK-POSTLINK-O-NEXT: Running pass: ConstantMergePass
 ; CHECK-POSTLINK-O-NEXT: Running pass: CGProfilePass
 ; CHECK-POSTLINK-O-NEXT: Running pass: RelLookupTableConverterPass
+; CHECK-POSTLINK-O-NEXT: Running pass: EmitChangedFuncDebugInfoPass
 ; CHECK-EP-OPT-EARLY-NEXT: Running pass: NoOpModulePass
 ; CHECK-EP-OPT-LAST-NEXT: Running pass: NoOpModulePass
 ; CHECK-O-NEXT:          Running pass: AnnotationRemarksPass on foo
diff --git a/llvm/test/Other/new-pm-thinlto-postlink-pgo-defaults.ll b/llvm/test/Other/new-pm-thinlto-postlink-pgo-defaults.ll
index d9e2dd37a7985..38e3238b3c170 100644
--- a/llvm/test/Other/new-pm-thinlto-postlink-pgo-defaults.ll
+++ b/llvm/test/Other/new-pm-thinlto-postlink-pgo-defaults.ll
@@ -192,6 +192,7 @@
 ; CHECK-O-NEXT: Running pass: ConstantMergePass
 ; CHECK-O-NEXT: Running pass: CGProfilePass
 ; CHECK-O-NEXT: Running pass: RelLookupTableConverterPass
+; CHECK-O-NEXT: Running pass: EmitChangedFuncDebugInfoPass
 ; CHECK-O-NEXT: Running pass: AnnotationRemarksPass on foo
 ; CHECK-O-NEXT: Running pass: PrintModulePass
 
diff --git a/llvm/test/Other/new-pm-thinlto-postlink-samplepgo-defaults.ll b/llvm/test/Other/new-pm-thinlto-postlink-samplepgo-defaults.ll
index 2f6fa4b27d354..2ec67d01424e7 100644
--- a/llvm/test/Other/new-pm-thinlto-postlink-samplepgo-defaults.ll
+++ b/llvm/test/Other/new-pm-thinlto-postlink-samplepgo-defaults.ll
@@ -201,6 +201,7 @@
 ; CHECK-O-NEXT: Running pass: ConstantMergePass
 ; CHECK-O-NEXT: Running pass: CGProfilePass
 ; CHECK-O-NEXT: Running pass: RelLookupTableConverterPass
+; CHECK-O-NEXT: Running pass: EmitChangedFuncDebugInfoPass
 ; CHECK-O-NEXT: Running pass: AnnotationRemarksPass on foo
 ; CHECK-O-NEXT: Running pass: PrintModulePass
 
diff --git a/llvm/test/Transforms/Util/emit-changed-func-debuginfo-argpromotion.ll b/llvm/test/Transforms/Util/emit-changed-func-debuginfo-argpromotion.ll
new file mode 100644
index 0000000000000..ae9b15efa8c93
--- /dev/null
+++ b/llvm/test/Transforms/Util/emit-changed-func-debuginfo-argpromotion.ll
@@ -0,0 +1,104 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -S -mtriple=x86_64-unknown-unknown -passes=emit-changed-func-debuginfo < %s | FileCheck %s
+
+; Source code:
+;   // clang -S -emit-llvm -O3 -g test.c -mllvm -disable-changed-func-dbinfo
+;   __attribute__((noinline)) static int callee(const int *p) { return *p + 42; }
+;   int caller(void) {
+;     int x = 100;
+;     return callee(&x);
+;   }
+
+; Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(none) uwtable
+define dso_local range(i32 -2147483606, -2147483648) i32 @caller() local_unnamed_addr #0 !dbg !10 {
+; CHECK-LABEL: define dso_local range(i32 -2147483606, -2147483648) i32 @caller(
+; CHECK-SAME: ) local_unnamed_addr #[[ATTR0:[0-9]+]] !dbg [[DBG10:![0-9]+]] {
+; CHECK-NEXT:      #dbg_value(i32 100, [[META15:![0-9]+]], !DIExpression(), [[META16:![0-9]+]])
+; CHECK-NEXT:    [[TMP1:%.*]] = tail call fastcc i32 @callee(i32 100), !dbg [[DBG17:![0-9]+]]
+; CHECK-NEXT:    ret i32 [[TMP1]], !dbg [[DBG18:![0-9]+]]
+;
+    #dbg_value(i32 100, !15, !DIExpression(), !16)
+  %1 = tail call fastcc i32 @callee(i32 100), !dbg !17
+  ret i32 %1, !dbg !18
+}
+
+; Function Attrs: mustprogress nofree noinline norecurse nosync nounwind willreturn memory(none) uwtable
+define internal fastcc range(i32 -2147483606, -2147483648) i32 @callee(i32 %0) unnamed_addr #1 !dbg !19 {
+; CHECK-LABEL: define internal fastcc range(i32 -2147483606, -2147483648) i32 @callee(
+; CHECK-SAME: i32 [[TMP0:%.*]]) unnamed_addr #[[ATTR1:[0-9]+]] !dbg [[DBG19:![0-9]+]] {
+; CHECK-NEXT:      #dbg_value(i32 [[TMP0]], [[META24:![0-9]+]], !DIExpression(), [[META25:![0-9]+]])
+; CHECK-NEXT:      #dbg_value(ptr poison, [[META26:![0-9]+]], !DIExpression(), [[META33:![0-9]+]])
+; CHECK-NEXT:    [[TMP2:%.*]] = add nsw i32 [[TMP0]], 42, !dbg [[DBG34:![0-9]+]]
+; CHECK-NEXT:    ret i32 [[TMP2]], !dbg [[DBG35:![0-9]+]]
+;
+    #dbg_value(ptr poison, !25, !DIExpression(), !26)
+  %2 = add nsw i32 %0, 42, !dbg !27
+  ret i32 %2, !dbg !28
+}
+
+attributes #0 = { mustprogress nofree norecurse nosync nounwind willreturn memory(none) uwtable "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
+attributes #1 = { mustprogress nofree noinline norecurse nosync nounwind willreturn memory(none) uwtable "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
+
+!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 22.0.0git (git at github.com:yonghong-song/llvm-project.git 8e5d24efc7dac78e8ba568dfe2fc6cfbe9663b13)", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None)
+!1 = !DIFile(filename: "test.c", directory: "/tmp/home/yhs/tests/sig-change/prom", checksumkind: CSK_MD5, checksum: "f42f3fd1477418a2e17b444f656351ff")
+!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 22.0.0git (git at github.com:yonghong-song/llvm-project.git 8e5d24efc7dac78e8ba568dfe2fc6cfbe9663b13)"}
+!10 = distinct !DISubprogram(name: "caller", scope: !1, file: !1, line: 2, type: !11, scopeLine: 2, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !14, keyInstructions: true)
+!11 = !DISubroutineType(types: !12)
+!12 = !{!13}
+!13 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+!14 = !{!15}
+!15 = !DILocalVariable(name: "x", scope: !10, file: !1, line: 3, type: !13)
+!16 = !DILocation(line: 0, scope: !10)
+!17 = !DILocation(line: 4, column: 10, scope: !10, atomGroup: 3, atomRank: 2)
+!18 = !DILocation(line: 4, column: 3, scope: !10, atomGroup: 3, atomRank: 1)
+!19 = distinct !DISubprogram(name: "callee", scope: !1, file: !1, line: 1, type: !20, scopeLine: 1, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagLocalToUnit | DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !24, keyInstructions: true)
+!20 = !DISubroutineType(cc: DW_CC_nocall, types: !21)
+!21 = !{!13, !22}
+!22 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !23, size: 64)
+!23 = !DIDerivedType(tag: DW_TAG_const_type, baseType: !13)
+!24 = !{!25}
+!25 = !DILocalVariable(name: "p", arg: 1, scope: !19, file: !1, line: 1, type: !22)
+!26 = !DILocation(line: 0, scope: !19)
+!27 = !DILocation(line: 1, column: 71, scope: !19, atomGroup: 1, atomRank: 2)
+!28 = !DILocation(line: 1, column: 61, scope: !19, atomGroup: 1, atomRank: 1)
+;.
+; CHECK: [[META0:![0-9]+]] = distinct !DICompileUnit(language: DW_LANG_C11, file: [[META1:![0-9]+]], producer: "{{.*}}clang version {{.*}}", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None)
+; CHECK: [[META1]] = !DIFile(filename: "{{.*}}test.c", directory: {{.*}})
+; CHECK: [[DBG10]] = distinct !DISubprogram(name: "caller", scope: [[META1]], file: [[META1]], line: 2, type: [[META11:![0-9]+]], scopeLine: 2, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: [[META0]], retainedNodes: [[META14:![0-9]+]], keyInstructions: true)
+; CHECK: [[META11]] = !DISubroutineType(types: [[META12:![0-9]+]])
+; CHECK: [[META12]] = !{[[META13:![0-9]+]]}
+; CHECK: [[META13]] = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+; CHECK: [[META14]] = !{[[META15]]}
+; CHECK: [[META15]] = !DILocalVariable(name: "x", scope: [[DBG10]], file: [[META1]], line: 3, type: [[META13]])
+; CHECK: [[META16]] = !DILocation(line: 0, scope: [[DBG10]])
+; CHECK: [[DBG17]] = !DILocation(line: 4, column: 10, scope: [[DBG10]], atomGroup: 3, atomRank: 2)
+; CHECK: [[DBG18]] = !DILocation(line: 4, column: 3, scope: [[DBG10]], atomGroup: 3, atomRank: 1)
+; CHECK: [[DBG19]] = distinct !DISubprogram(name: "callee", linkageName: "callee", scope: [[META1]], file: [[META1]], line: 1, type: [[META20:![0-9]+]], scopeLine: 1, flags: DIFlagArtificial, spFlags: DISPFlagDefinition, unit: [[META0]], retainedNodes: [[META23:![0-9]+]])
+; CHECK: [[META20]] = !DISubroutineType(types: [[META21:![0-9]+]])
+; CHECK: [[META21]] = !{[[META13]], [[META22:![0-9]+]]}
+; CHECK: [[META22]] = !DIBasicType(name: "int32", size: 32, encoding: DW_ATE_signed)
+; CHECK: [[META23]] = !{[[META24]]}
+; CHECK: [[META24]] = !DILocalVariable(name: "__0", arg: 1, scope: [[DBG19]], file: [[META1]], line: 1, type: [[META22]])
+; CHECK: [[META25]] = !DILocation(line: 0, scope: [[DBG19]])
+; CHECK: [[META26]] = !DILocalVariable(name: "p", arg: 1, scope: [[META27:![0-9]+]], file: [[META1]], line: 1, type: [[META30:![0-9]+]])
+; CHECK: [[META27]] = distinct !DISubprogram(name: "callee", scope: [[META1]], file: [[META1]], line: 1, type: [[META28:![0-9]+]], scopeLine: 1, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagLocalToUnit | DISPFlagDefinition | DISPFlagOptimized, unit: [[META0]], retainedNodes: [[META32:![0-9]+]], keyInstructions: true)
+; CHECK: [[META28]] = !DISubroutineType(cc: DW_CC_normal, types: [[META29:![0-9]+]])
+; CHECK: [[META29]] = !{[[META13]], [[META30]]}
+; CHECK: [[META30]] = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: [[META31:![0-9]+]], size: 64)
+; CHECK: [[META31]] = !DIDerivedType(tag: DW_TAG_const_type, baseType: [[META13]])
+; CHECK: [[META32]] = !{[[META26]]}
+; CHECK: [[META33]] = !DILocation(line: 0, scope: [[META27]], inlinedAt: [[META25]])
+; CHECK: [[DBG34]] = !DILocation(line: 1, column: 71, scope: [[META27]], inlinedAt: [[META25]], atomGroup: 1, atomRank: 2)
+; CHECK: [[DBG35]] = !DILocation(line: 1, column: 61, scope: [[META27]], inlinedAt: [[META25]], atomGroup: 1, atomRank: 1)
+;.
diff --git a/llvm/test/Transforms/Util/emit-changed-func-debuginfo-deadarg.ll b/llvm/test/Transforms/Util/emit-changed-func-debuginfo-deadarg.ll
new file mode 100644
index 0000000000000..8acb4a1a4104e
--- /dev/null
+++ b/llvm/test/Transforms/Util/emit-changed-func-debuginfo-deadarg.ll
@@ -0,0 +1,133 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -S -mtriple=x86_64-unknown-unknown -passes=emit-changed-func-debuginfo < %s | FileCheck %s
+
+; Source code:
+;  // clang -O2 -S -emit-llvm -g test.c -mllvm -disable-changed-func-dbinfo
+;  struct t { int a; };
+;  char *tar(struct t *a, struct t *d);
+;  __attribute__((noinline)) static char * foo(struct t *a, struct t *d, int b)
+;  {
+;    return tar(a, d);
+;  }
+;  char *bar(struct t *a, struct t *d)
+;  {
+;    return foo(a, d, 1);
+;  }
+
+; Function Attrs: nounwind uwtable
+define dso_local ptr @bar(ptr noundef %0, ptr noundef %1) local_unnamed_addr #0 !dbg !10 {
+; CHECK-LABEL: define dso_local ptr @bar(
+; CHECK-SAME: ptr noundef [[TMP0:%.*]], ptr noundef [[TMP1:%.*]]) local_unnamed_addr #[[ATTR0:[0-9]+]] !dbg [[DBG10:![0-9]+]] {
+; CHECK-NEXT:      #dbg_value(ptr [[TMP0]], [[META21:![0-9]+]], !DIExpression(), [[META23:![0-9]+]])
+; CHECK-NEXT:      #dbg_value(ptr [[TMP1]], [[META22:![0-9]+]], !DIExpression(), [[META23]])
+; CHECK-NEXT:    [[TMP3:%.*]] = tail call fastcc ptr @foo(ptr noundef [[TMP0]], ptr noundef [[TMP1]]), !dbg [[DBG24:![0-9]+]]
+; CHECK-NEXT:    ret ptr [[TMP3]], !dbg [[DBG25:![0-9]+]]
+;
+    #dbg_value(ptr %0, !21, !DIExpression(), !23)
+    #dbg_value(ptr %1, !22, !DIExpression(), !23)
+  %3 = tail call fastcc ptr @foo(ptr noundef %0, ptr noundef %1), !dbg !24
+  ret ptr %3, !dbg !25
+}
+
+; Function Attrs: noinline nounwind uwtable
+define internal fastcc ptr @foo(ptr noundef %0, ptr noundef %1) unnamed_addr #1 !dbg !26 {
+; CHECK-LABEL: define internal fastcc ptr @foo(
+; CHECK-SAME: ptr noundef [[TMP0:%.*]], ptr noundef [[TMP1:%.*]]) unnamed_addr #[[ATTR1:[0-9]+]] !dbg [[DBG26:![0-9]+]] {
+; CHECK-NEXT:      #dbg_value(ptr [[TMP0]], [[META28:![0-9]+]], !DIExpression(), [[META30:![0-9]+]])
+; CHECK-NEXT:      #dbg_value(ptr [[TMP1]], [[META29:![0-9]+]], !DIExpression(), [[META30]])
+; CHECK-NEXT:      #dbg_value(ptr [[TMP0]], [[META31:![0-9]+]], !DIExpression(), [[META38:![0-9]+]])
+; CHECK-NEXT:      #dbg_value(ptr [[TMP1]], [[META36:![0-9]+]], !DIExpression(), [[META38]])
+; CHECK-NEXT:      #dbg_value(i32 poison, [[META37:![0-9]+]], !DIExpression(), [[META38]])
+; CHECK-NEXT:    [[TMP3:%.*]] = tail call ptr @tar(ptr noundef [[TMP0]], ptr noundef [[TMP1]]) #[[ATTR3:[0-9]+]], !dbg [[DBG39:![0-9]+]]
+; CHECK-NEXT:    ret ptr [[TMP3]], !dbg [[DBG40:![0-9]+]]
+;
+    #dbg_value(ptr %0, !30, !DIExpression(), !33)
+    #dbg_value(ptr %1, !31, !DIExpression(), !33)
+    #dbg_value(i32 poison, !32, !DIExpression(), !33)
+  %3 = tail call ptr @tar(ptr noundef %0, ptr noundef %1) #3, !dbg !34
+  ret ptr %3, !dbg !35
+}
+
+declare !dbg !36 ptr @tar(ptr noundef, ptr noundef) local_unnamed_addr #2
+
+attributes #0 = { nounwind uwtable "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
+attributes #1 = { noinline nounwind uwtable "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
+attributes #2 = { "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
+attributes #3 = { nounwind }
+
+!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 22.0.0git (git at github.com:yonghong-song/llvm-project.git 8e5d24efc7dac78e8ba568dfe2fc6cfbe9663b13)", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None)
+!1 = !DIFile(filename: "test.c", directory: "/tmp/home/yhs/tests/sig-change/deadarg", checksumkind: CSK_MD5, checksum: "54bc89245cb23f69a8eb94fe2fb50a09")
+!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 22.0.0git (git at github.com:yonghong-song/llvm-project.git 8e5d24efc7dac78e8ba568dfe2fc6cfbe9663b13)"}
+!10 = distinct !DISubprogram(name: "bar", scope: !1, file: !1, line: 7, type: !11, scopeLine: 8, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !20, keyInstructions: true)
+!11 = !DISubroutineType(types: !12)
+!12 = !{!13, !15, !15}
+!13 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !14, size: 64)
+!14 = !DIBasicType(name: "char", size: 8, encoding: DW_ATE_signed_char)
+!15 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !16, size: 64)
+!16 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "t", file: !1, line: 1, size: 32, elements: !17)
+!17 = !{!18}
+!18 = !DIDerivedType(tag: DW_TAG_member, name: "a", scope: !16, file: !1, line: 1, baseType: !19, size: 32)
+!19 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+!20 = !{!21, !22}
+!21 = !DILocalVariable(name: "a", arg: 1, scope: !10, file: !1, line: 7, type: !15)
+!22 = !DILocalVariable(name: "d", arg: 2, scope: !10, file: !1, line: 7, type: !15)
+!23 = !DILocation(line: 0, scope: !10)
+!24 = !DILocation(line: 9, column: 10, scope: !10, atomGroup: 1, atomRank: 2)
+!25 = !DILocation(line: 9, column: 3, scope: !10, atomGroup: 1, atomRank: 1)
+!26 = distinct !DISubprogram(name: "foo", scope: !1, file: !1, line: 3, type: !27, scopeLine: 4, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagLocalToUnit | DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !29, keyInstructions: true)
+!27 = !DISubroutineType(cc: DW_CC_nocall, types: !28)
+!28 = !{!13, !15, !15, !19}
+!29 = !{!30, !31, !32}
+!30 = !DILocalVariable(name: "a", arg: 1, scope: !26, file: !1, line: 3, type: !15)
+!31 = !DILocalVariable(name: "d", arg: 2, scope: !26, file: !1, line: 3, type: !15)
+!32 = !DILocalVariable(name: "b", arg: 3, scope: !26, file: !1, line: 3, type: !19)
+!33 = !DILocation(line: 0, scope: !26)
+!34 = !DILocation(line: 5, column: 10, scope: !26, atomGroup: 1, atomRank: 2)
+!35 = !DILocation(line: 5, column: 3, scope: !26, atomGroup: 1, atomRank: 1)
+!36 = !DISubprogram(name: "tar", scope: !1, file: !1, line: 2, type: !11, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized)
+;.
+; CHECK: [[META0:![0-9]+]] = distinct !DICompileUnit(language: DW_LANG_C11, file: [[META1:![0-9]+]], producer: "{{.*}}clang version {{.*}}", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None)
+; CHECK: [[META1]] = !DIFile(filename: "{{.*}}test.c", directory: {{.*}})
+; CHECK: [[DBG10]] = distinct !DISubprogram(name: "bar", scope: [[META1]], file: [[META1]], line: 7, type: [[META11:![0-9]+]], scopeLine: 8, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: [[META0]], retainedNodes: [[META20:![0-9]+]], keyInstructions: true)
+; CHECK: [[META11]] = !DISubroutineType(types: [[META12:![0-9]+]])
+; CHECK: [[META12]] = !{[[META13:![0-9]+]], [[META15:![0-9]+]], [[META15]]}
+; CHECK: [[META13]] = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: [[META14:![0-9]+]], size: 64)
+; CHECK: [[META14]] = !DIBasicType(name: "char", size: 8, encoding: DW_ATE_signed_char)
+; CHECK: [[META15]] = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: [[META16:![0-9]+]], size: 64)
+; CHECK: [[META16]] = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "t", file: [[META1]], line: 1, size: 32, elements: [[META17:![0-9]+]])
+; CHECK: [[META17]] = !{[[META18:![0-9]+]]}
+; CHECK: [[META18]] = !DIDerivedType(tag: DW_TAG_member, name: "a", scope: [[META16]], file: [[META1]], line: 1, baseType: [[META19:![0-9]+]], size: 32)
+; CHECK: [[META19]] = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+; CHECK: [[META20]] = !{[[META21]], [[META22]]}
+; CHECK: [[META21]] = !DILocalVariable(name: "a", arg: 1, scope: [[DBG10]], file: [[META1]], line: 7, type: [[META15]])
+; CHECK: [[META22]] = !DILocalVariable(name: "d", arg: 2, scope: [[DBG10]], file: [[META1]], line: 7, type: [[META15]])
+; CHECK: [[META23]] = !DILocation(line: 0, scope: [[DBG10]])
+; CHECK: [[DBG24]] = !DILocation(line: 9, column: 10, scope: [[DBG10]], atomGroup: 1, atomRank: 2)
+; CHECK: [[DBG25]] = !DILocation(line: 9, column: 3, scope: [[DBG10]], atomGroup: 1, atomRank: 1)
+; CHECK: [[DBG26]] = distinct !DISubprogram(name: "foo", linkageName: "foo", scope: [[META1]], file: [[META1]], line: 3, type: [[META11]], scopeLine: 4, flags: DIFlagArtificial, spFlags: DISPFlagDefinition, unit: [[META0]], retainedNodes: [[META27:![0-9]+]])
+; CHECK: [[META27]] = !{[[META28]], [[META29]]}
+; CHECK: [[META28]] = !DILocalVariable(name: "a", arg: 1, scope: [[DBG26]], file: [[META1]], line: 3, type: [[META15]])
+; CHECK: [[META29]] = !DILocalVariable(name: "d", arg: 2, scope: [[DBG26]], file: [[META1]], line: 3, type: [[META15]])
+; CHECK: [[META30]] = !DILocation(line: 0, scope: [[DBG26]])
+; CHECK: [[META31]] = !DILocalVariable(name: "a", arg: 1, scope: [[META32:![0-9]+]], file: [[META1]], line: 3, type: [[META15]])
+; CHECK: [[META32]] = distinct !DISubprogram(name: "foo", scope: [[META1]], file: [[META1]], line: 3, type: [[META33:![0-9]+]], scopeLine: 4, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagLocalToUnit | DISPFlagDefinition | DISPFlagOptimized, unit: [[META0]], retainedNodes: [[META35:![0-9]+]], keyInstructions: true)
+; CHECK: [[META33]] = !DISubroutineType(cc: DW_CC_normal, types: [[META34:![0-9]+]])
+; CHECK: [[META34]] = !{[[META13]], [[META15]], [[META15]], [[META19]]}
+; CHECK: [[META35]] = !{[[META31]], [[META36]], [[META37]]}
+; CHECK: [[META36]] = !DILocalVariable(name: "d", arg: 2, scope: [[META32]], file: [[META1]], line: 3, type: [[META15]])
+; CHECK: [[META37]] = !DILocalVariable(name: "b", arg: 3, scope: [[META32]], file: [[META1]], line: 3, type: [[META19]])
+; CHECK: [[META38]] = !DILocation(line: 0, scope: [[META32]], inlinedAt: [[META30]])
+; CHECK: [[DBG39]] = !DILocation(line: 5, column: 10, scope: [[META32]], inlinedAt: [[META30]], atomGroup: 1, atomRank: 2)
+; CHECK: [[DBG40]] = !DILocation(line: 5, column: 3, scope: [[META32]], inlinedAt: [[META30]], atomGroup: 1, atomRank: 1)
+;.
diff --git a/llvm/test/Transforms/Util/emit-changed-func-debuginfo-struct-16B.ll b/llvm/test/Transforms/Util/emit-changed-func-debuginfo-struct-16B.ll
new file mode 100644
index 0000000000000..7ed431bf7c8c0
--- /dev/null
+++ b/llvm/test/Transforms/Util/emit-changed-func-debuginfo-struct-16B.ll
@@ -0,0 +1,81 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -S -mtriple=x86_64-unknown-unknown -passes=emit-changed-func-debuginfo < %s | FileCheck %s
+
+; Source code:
+;   // clang -O2 -S -emit-llvm -g test1.c -mllvm -disable-changed-func-dbinfo
+;   struct t { long a; long b; };
+;   long foo(struct t arg) {
+;     return arg.a * arg.b;
+;   }
+
+; Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(none) uwtable
+define dso_local i64 @foo(i64 %0, i64 %1) local_unnamed_addr #0 !dbg !10 {
+; CHECK-LABEL: define dso_local i64 @foo(
+; CHECK-SAME: i64 [[TMP0:%.*]], i64 [[TMP1:%.*]]) local_unnamed_addr #[[ATTR0:[0-9]+]] !dbg [[DBG10:![0-9]+]] {
+; CHECK-NEXT:      #dbg_value(i64 [[TMP0]], [[META16:![0-9]+]], !DIExpression(), [[META18:![0-9]+]])
+; CHECK-NEXT:      #dbg_value(i64 [[TMP1]], [[META17:![0-9]+]], !DIExpression(), [[META18]])
+; CHECK-NEXT:      #dbg_value(i64 [[TMP0]], [[META19:![0-9]+]], !DIExpression(DW_OP_LLVM_fragment, 0, 64), [[META28:![0-9]+]])
+; CHECK-NEXT:      #dbg_value(i64 [[TMP1]], [[META19]], !DIExpression(DW_OP_LLVM_fragment, 64, 64), [[META28]])
+; CHECK-NEXT:    [[TMP3:%.*]] = mul nsw i64 [[TMP1]], [[TMP0]], !dbg [[DBG29:![0-9]+]]
+; CHECK-NEXT:    ret i64 [[TMP3]], !dbg [[DBG30:![0-9]+]]
+;
+    #dbg_value(i64 %0, !19, !DIExpression(DW_OP_LLVM_fragment, 0, 64), !20)
+    #dbg_value(i64 %1, !19, !DIExpression(DW_OP_LLVM_fragment, 64, 64), !20)
+  %3 = mul nsw i64 %1, %0, !dbg !21
+  ret i64 %3, !dbg !22
+}
+
+attributes #0 = { mustprogress nofree norecurse nosync nounwind willreturn memory(none) uwtable "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
+
+!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 22.0.0git (git at github.com:yonghong-song/llvm-project.git 8e5d24efc7dac78e8ba568dfe2fc6cfbe9663b13)", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None)
+!1 = !DIFile(filename: "test1.c", directory: "/tmp/home/yhs/tests/sig-change/struct", checksumkind: CSK_MD5, checksum: "bd0d0ce5cc67e004962a79d888a27468")
+!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 22.0.0git (git at github.com:yonghong-song/llvm-project.git 8e5d24efc7dac78e8ba568dfe2fc6cfbe9663b13)"}
+!10 = distinct !DISubprogram(name: "foo", scope: !1, file: !1, line: 2, type: !11, scopeLine: 2, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !18, keyInstructions: true)
+!11 = !DISubroutineType(types: !12)
+!12 = !{!13, !14}
+!13 = !DIBasicType(name: "long", size: 64, encoding: DW_ATE_signed)
+!14 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "t", file: !1, line: 1, size: 128, elements: !15)
+!15 = !{!16, !17}
+!16 = !DIDerivedType(tag: DW_TAG_member, name: "a", scope: !14, file: !1, line: 1, baseType: !13, size: 64)
+!17 = !DIDerivedType(tag: DW_TAG_member, name: "b", scope: !14, file: !1, line: 1, baseType: !13, size: 64, offset: 64)
+!18 = !{!19}
+!19 = !DILocalVariable(name: "arg", arg: 1, scope: !10, file: !1, line: 2, type: !14)
+!20 = !DILocation(line: 0, scope: !10)
+!21 = !DILocation(line: 3, column: 16, scope: !10, atomGroup: 1, atomRank: 2)
+!22 = !DILocation(line: 3, column: 3, scope: !10, atomGroup: 1, atomRank: 1)
+;.
+; CHECK: [[META0:![0-9]+]] = distinct !DICompileUnit(language: DW_LANG_C11, file: [[META1:![0-9]+]], producer: "{{.*}}clang version {{.*}}", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None)
+; CHECK: [[META1]] = !DIFile(filename: "{{.*}}test1.c", directory: {{.*}})
+; CHECK: [[DBG10]] = distinct !DISubprogram(name: "foo", linkageName: "foo", scope: [[META1]], file: [[META1]], line: 2, type: [[META11:![0-9]+]], scopeLine: 2, flags: DIFlagArtificial, spFlags: DISPFlagDefinition, unit: [[META0]], retainedNodes: [[META15:![0-9]+]])
+; CHECK: [[META11]] = !DISubroutineType(types: [[META12:![0-9]+]])
+; CHECK: [[META12]] = !{[[META13:![0-9]+]], [[META13]], [[META14:![0-9]+]]}
+; CHECK: [[META13]] = !DIBasicType(name: "long", size: 64, encoding: DW_ATE_signed)
+; CHECK: [[META14]] = !DIBasicType(name: "int64", size: 64, encoding: DW_ATE_signed)
+; CHECK: [[META15]] = !{[[META16]], [[META17]]}
+; CHECK: [[META16]] = !DILocalVariable(name: "arg__0", arg: 1, scope: [[DBG10]], file: [[META1]], line: 2, type: [[META13]])
+; CHECK: [[META17]] = !DILocalVariable(name: "arg__1", arg: 2, scope: [[DBG10]], file: [[META1]], line: 2, type: [[META14]])
+; CHECK: [[META18]] = !DILocation(line: 0, scope: [[DBG10]])
+; CHECK: [[META19]] = !DILocalVariable(name: "arg", arg: 1, scope: [[META20:![0-9]+]], file: [[META1]], line: 2, type: [[META23:![0-9]+]])
+; CHECK: [[META20]] = distinct !DISubprogram(name: "foo", scope: [[META1]], file: [[META1]], line: 2, type: [[META21:![0-9]+]], scopeLine: 2, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: [[META0]], retainedNodes: [[META27:![0-9]+]], keyInstructions: true)
+; CHECK: [[META21]] = !DISubroutineType(cc: DW_CC_normal, types: [[META22:![0-9]+]])
+; CHECK: [[META22]] = !{[[META13]], [[META23]]}
+; CHECK: [[META23]] = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "t", file: [[META1]], line: 1, size: 128, elements: [[META24:![0-9]+]])
+; CHECK: [[META24]] = !{[[META25:![0-9]+]], [[META26:![0-9]+]]}
+; CHECK: [[META25]] = !DIDerivedType(tag: DW_TAG_member, name: "a", scope: [[META23]], file: [[META1]], line: 1, baseType: [[META13]], size: 64)
+; CHECK: [[META26]] = !DIDerivedType(tag: DW_TAG_member, name: "b", scope: [[META23]], file: [[META1]], line: 1, baseType: [[META13]], size: 64, offset: 64)
+; CHECK: [[META27]] = !{[[META19]]}
+; CHECK: [[META28]] = !DILocation(line: 0, scope: [[META20]], inlinedAt: [[META18]])
+; CHECK: [[DBG29]] = !DILocation(line: 3, column: 16, scope: [[META20]], inlinedAt: [[META18]], atomGroup: 1, atomRank: 2)
+; CHECK: [[DBG30]] = !DILocation(line: 3, column: 3, scope: [[META20]], inlinedAt: [[META18]], atomGroup: 1, atomRank: 1)
+;.
diff --git a/llvm/test/Transforms/Util/emit-changed-func-debuginfo-struct-large.ll b/llvm/test/Transforms/Util/emit-changed-func-debuginfo-struct-large.ll
new file mode 100644
index 0000000000000..758faebfb9f58
--- /dev/null
+++ b/llvm/test/Transforms/Util/emit-changed-func-debuginfo-struct-large.ll
@@ -0,0 +1,102 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -S -mtriple=x86_64-unknown-unknown -passes=emit-changed-func-debuginfo < %s | FileCheck %s
+
+; Source code:
+;   // clang -O2 -S -emit-llvm -g test2.c -mllvm -disable-changed-func-dbinfo
+;   struct t { long a; long b; long c;};
+;   long foo(struct t arg) {
+;     return arg.a * arg.c;
+;   }
+
+%struct.t = type { i64, i64, i64 }
+
+; Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(argmem: read) uwtable
+define dso_local i64 @foo(ptr noundef readonly byval(%struct.t) align 8 captures(none) %0) local_unnamed_addr #0 !dbg !9 {
+; CHECK-LABEL: define dso_local i64 @foo(
+; CHECK-SAME: ptr noundef readonly byval([[STRUCT_T:%.*]]) align 8 captures(none) [[TMP0:%.*]]) local_unnamed_addr #[[ATTR0:[0-9]+]] !dbg [[DBG9:![0-9]+]] {
+; CHECK-NEXT:      #dbg_value(ptr [[TMP0]], [[META20:![0-9]+]], !DIExpression(), [[META21:![0-9]+]])
+; CHECK-NEXT:      #dbg_declare(ptr [[TMP0]], [[META22:![0-9]+]], !DIExpression(), [[META27:![0-9]+]])
+; CHECK-NEXT:    [[TMP2:%.*]] = load i64, ptr [[TMP0]], align 8, !dbg [[DBG28:![0-9]+]], !tbaa [[LONG_TBAA29:![0-9]+]]
+; CHECK-NEXT:    [[TMP3:%.*]] = getelementptr inbounds nuw i8, ptr [[TMP0]], i64 16, !dbg [[DBG34:![0-9]+]]
+; CHECK-NEXT:    [[TMP4:%.*]] = load i64, ptr [[TMP3]], align 8, !dbg [[DBG34]], !tbaa [[LONG_TBAA35:![0-9]+]]
+; CHECK-NEXT:    [[TMP5:%.*]] = mul nsw i64 [[TMP4]], [[TMP2]], !dbg [[DBG36:![0-9]+]]
+; CHECK-NEXT:    ret i64 [[TMP5]], !dbg [[DBG37:![0-9]+]]
+;
+    #dbg_declare(ptr %0, !19, !DIExpression(), !20)
+  %2 = load i64, ptr %0, align 8, !dbg !21, !tbaa !22
+  %3 = getelementptr inbounds nuw i8, ptr %0, i64 16, !dbg !27
+  %4 = load i64, ptr %3, align 8, !dbg !27, !tbaa !28
+  %5 = mul nsw i64 %4, %2, !dbg !29
+  ret i64 %5, !dbg !30
+}
+
+attributes #0 = { mustprogress nofree norecurse nosync nounwind willreturn memory(argmem: read) uwtable "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!2, !3, !4, !5, !6, !7}
+!llvm.ident = !{!8}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C11, file: !1, producer: "clang version 22.0.0git (git at github.com:yonghong-song/llvm-project.git 8e5d24efc7dac78e8ba568dfe2fc6cfbe9663b13)", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None)
+!1 = !DIFile(filename: "test2.c", directory: "/tmp/home/yhs/tests/sig-change/struct", checksumkind: CSK_MD5, checksum: "d58648f18d3fa35e3d95b364b4a95c4c")
+!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 = !{!"clang version 22.0.0git (git at github.com:yonghong-song/llvm-project.git 8e5d24efc7dac78e8ba568dfe2fc6cfbe9663b13)"}
+!9 = distinct !DISubprogram(name: "foo", scope: !1, file: !1, line: 2, type: !10, scopeLine: 2, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !18, keyInstructions: true)
+!10 = !DISubroutineType(types: !11)
+!11 = !{!12, !13}
+!12 = !DIBasicType(name: "long", size: 64, encoding: DW_ATE_signed)
+!13 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "t", file: !1, line: 1, size: 192, elements: !14)
+!14 = !{!15, !16, !17}
+!15 = !DIDerivedType(tag: DW_TAG_member, name: "a", scope: !13, file: !1, line: 1, baseType: !12, size: 64)
+!16 = !DIDerivedType(tag: DW_TAG_member, name: "b", scope: !13, file: !1, line: 1, baseType: !12, size: 64, offset: 64)
+!17 = !DIDerivedType(tag: DW_TAG_member, name: "c", scope: !13, file: !1, line: 1, baseType: !12, size: 64, offset: 128)
+!18 = !{!19}
+!19 = !DILocalVariable(name: "arg", arg: 1, scope: !9, file: !1, line: 2, type: !13)
+!20 = !DILocation(line: 2, column: 19, scope: !9)
+!21 = !DILocation(line: 3, column: 14, scope: !9)
+!22 = !{!23, !24, i64 0}
+!23 = !{!"t", !24, i64 0, !24, i64 8, !24, i64 16}
+!24 = !{!"long", !25, i64 0}
+!25 = !{!"omnipotent char", !26, i64 0}
+!26 = !{!"Simple C/C++ TBAA"}
+!27 = !DILocation(line: 3, column: 22, scope: !9)
+!28 = !{!23, !24, i64 16}
+!29 = !DILocation(line: 3, column: 16, scope: !9, atomGroup: 1, atomRank: 2)
+!30 = !DILocation(line: 3, column: 3, scope: !9, atomGroup: 1, atomRank: 1)
+;.
+; CHECK: [[META0:![0-9]+]] = distinct !DICompileUnit(language: DW_LANG_C11, file: [[META1:![0-9]+]], producer: "{{.*}}clang version {{.*}}", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None)
+; CHECK: [[META1]] = !DIFile(filename: "{{.*}}test2.c", directory: {{.*}})
+; CHECK: [[DBG9]] = distinct !DISubprogram(name: "foo", linkageName: "foo", scope: [[META1]], file: [[META1]], line: 2, type: [[META10:![0-9]+]], scopeLine: 2, flags: DIFlagArtificial, spFlags: DISPFlagDefinition, unit: [[META0]], retainedNodes: [[META19:![0-9]+]])
+; CHECK: [[META10]] = !DISubroutineType(types: [[META11:![0-9]+]])
+; CHECK: [[META11]] = !{[[META12:![0-9]+]], [[META13:![0-9]+]]}
+; CHECK: [[META12]] = !DIBasicType(name: "long", size: 64, encoding: DW_ATE_signed)
+; CHECK: [[META13]] = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: [[META14:![0-9]+]], size: 64)
+; CHECK: [[META14]] = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "t", file: [[META1]], line: 1, size: 192, elements: [[META15:![0-9]+]])
+; CHECK: [[META15]] = !{[[META16:![0-9]+]], [[META17:![0-9]+]], [[META18:![0-9]+]]}
+; CHECK: [[META16]] = !DIDerivedType(tag: DW_TAG_member, name: "a", scope: [[META14]], file: [[META1]], line: 1, baseType: [[META12]], size: 64)
+; CHECK: [[META17]] = !DIDerivedType(tag: DW_TAG_member, name: "b", scope: [[META14]], file: [[META1]], line: 1, baseType: [[META12]], size: 64, offset: 64)
+; CHECK: [[META18]] = !DIDerivedType(tag: DW_TAG_member, name: "c", scope: [[META14]], file: [[META1]], line: 1, baseType: [[META12]], size: 64, offset: 128)
+; CHECK: [[META19]] = !{[[META20]]}
+; CHECK: [[META20]] = !DILocalVariable(name: "arg__0", arg: 1, scope: [[DBG9]], file: [[META1]], line: 2, type: [[META13]])
+; CHECK: [[META21]] = !DILocation(line: 0, scope: [[DBG9]])
+; CHECK: [[META22]] = !DILocalVariable(name: "arg", arg: 1, scope: [[META23:![0-9]+]], file: [[META1]], line: 2, type: [[META14]])
+; CHECK: [[META23]] = distinct !DISubprogram(name: "foo", scope: [[META1]], file: [[META1]], line: 2, type: [[META24:![0-9]+]], scopeLine: 2, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: [[META0]], retainedNodes: [[META26:![0-9]+]], keyInstructions: true)
+; CHECK: [[META24]] = !DISubroutineType(cc: DW_CC_normal, types: [[META25:![0-9]+]])
+; CHECK: [[META25]] = !{[[META12]], [[META14]]}
+; CHECK: [[META26]] = !{[[META22]]}
+; CHECK: [[META27]] = !DILocation(line: 2, column: 19, scope: [[META23]], inlinedAt: [[META21]])
+; CHECK: [[DBG28]] = !DILocation(line: 3, column: 14, scope: [[META23]], inlinedAt: [[META21]])
+; CHECK: [[LONG_TBAA29]] = !{[[META30:![0-9]+]], [[META31:![0-9]+]], i64 0}
+; CHECK: [[META30]] = !{!"t", [[META31]], i64 0, [[META31]], i64 8, [[META31]], i64 16}
+; CHECK: [[META31]] = !{!"long", [[META32:![0-9]+]], i64 0}
+; CHECK: [[META32]] = !{!"omnipotent char", [[META33:![0-9]+]], i64 0}
+; CHECK: [[META33]] = !{!"Simple C/C++ TBAA"}
+; CHECK: [[DBG34]] = !DILocation(line: 3, column: 22, scope: [[META23]], inlinedAt: [[META21]])
+; CHECK: [[LONG_TBAA35]] = !{[[META30]], [[META31]], i64 16}
+; CHECK: [[DBG36]] = !DILocation(line: 3, column: 16, scope: [[META23]], inlinedAt: [[META21]], atomGroup: 1, atomRank: 2)
+; CHECK: [[DBG37]] = !DILocation(line: 3, column: 3, scope: [[META23]], inlinedAt: [[META21]], atomGroup: 1, atomRank: 1)
+;.



More information about the llvm-commits mailing list