[llvm] [RFC][LLVM] Emit dwarf data for changed-signature and new functions (PR #165310)
via llvm-commits
llvm-commits at lists.llvm.org
Fri Nov 28 20:41:28 PST 2025
https://github.com/yonghong-song updated https://github.com/llvm/llvm-project/pull/165310
>From 84bf3a0b7c676ad2aca6e8a53fe180551ea1b212 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/2] [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 ecbd5cafbe949d50248ce94ba6c698ae383c0ba9 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 2/2] [LLVM] Emit dwarf data for changed-signature and new
functions
Add a new pass EmitChangedFuncDebugInfo which will add dwarf for
additional functions whose signatures are changed during compiler
transformations.
The original intention is for bpf-based linux kernel tracing.
The function signature is available in vmlinux BTF generated
from pahole/dwarf. Such signature is generated from dwarf
at the source level. But this is not ideal since some function
may have signatures changed. If user still used the source
level signature, users may not get correct results and may
need some efforts to workaround the issue.
So we want to encode the true signature (different
from the source one) in dwarf. With such additional information,
dwarf users can get these signature changed functions.
For example, pahole is able to process these signature
changed functions and encode them into vmlinux BTF properly.
History of multiple attempts
============================
Previously I have attempted a few tries ([1], [2] and [3]).
Initially I tried to modify debuginfo in passes like
ArgPromotion and DeadArgElim, but later on it is suggested
to have a central place to handle new signatures ([1]).
Later, I have another version of patch similar to this
one, but the recommendation is to modify debuginfo to
encode new signature within the same function,
either through inlinedAt or new signature overwriting
the old one. This seems working but it has some
side effect on lldb, some lldb output (e.g. back trace)
will be different from the previous one. The recommendation
is to avoid any behavior change for lldb ([2] and [3]).
So now, I came back to the solution discussed at the
end of [1]. Basically a special dwarf entry will be generated
to encode the new signature. The new signature will have
a reference to the old source-level signature.
So the tool can inspect dwarf to retrieve the related
info.
Examples and dwarf output
=========================
In below, a few examples will show how changed signatures
represented in dwarf:
Example 1
---------
Source:
$ cat test.c
struct t { int a; };
char *tar(struct t *a, struct t *d);
__attribute__((noinline)) static char * foo(struct t *a, int b, struct t *d)
{
return tar(a, d);
}
char *bar(struct t *a, struct t *d)
{
return foo(a, 1, d);
}
Compiled and dump dwarf with:
$ clang -O2 -c -g test.c -mllvm -enable-changed-func-dbinfo
$ llvm-dwarfdump test.o
0x0000000c: DW_TAG_compile_unit
...
0x0000005c: DW_TAG_subprogram
DW_AT_low_pc (0x0000000000000010)
DW_AT_high_pc (0x0000000000000015)
DW_AT_frame_base (DW_OP_reg7 RSP)
DW_AT_call_all_calls (true)
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_calling_convention (DW_CC_nocall)
DW_AT_type (0x000000b1 "char *")
0x0000006c: DW_TAG_formal_parameter
DW_AT_location (DW_OP_reg5 RDI)
DW_AT_name ("a")
DW_AT_decl_file ("/home/yhs/tests/sig-change/deadarg/test.c")
DW_AT_decl_line (3)
DW_AT_type (0x000000ba "t *")
0x00000076: 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 (0x000000ce "int")
0x0000007e: DW_TAG_formal_parameter
DW_AT_location (DW_OP_reg4 RSI)
DW_AT_name ("d")
DW_AT_decl_file ("/home/yhs/tests/sig-change/deadarg/test.c")
DW_AT_decl_line (3)
DW_AT_type (0x000000ba "t *")
0x00000088: DW_TAG_call_site
...
0x0000009d: NULL
...
0x000000d2: DW_TAG_inlined_subroutine
DW_AT_name ("foo")
DW_AT_type (0x000000b1 "char *")
DW_AT_artificial (true)
DW_AT_specification (0x0000005c "foo")
0x000000dc: DW_TAG_formal_parameter
DW_AT_name ("a")
DW_AT_type (0x000000ba "t *")
0x000000e2: DW_TAG_formal_parameter
DW_AT_name ("d")
DW_AT_type (0x000000ba "t *")
0x000000e8: NULL
In the above, the DISubprogram 'foo' has the original signature but
since parameter 'b' does not have DW_AT_location, it is clear that
parameter will not be used. The actual function signature is represented
in DW_TAG_inlined_subroutine.
For the above case, it looks like DW_TAG_inlined_subroutine is not
necessary. Let us try a few other examples below.
Example 2
---------
Source:
$ cat test.c
struct t { long a; long b;};
__attribute__((noinline)) static long foo(struct t arg) {
return arg.b * 5;
}
long bar(struct t arg) {
return foo(arg);
}
Compiled and dump dwarf with:
$ clang -O2 -c -g test.c -mllvm -enable-changed-func-dbinfo
$ llvm-dwarfdump test.o
...
0x0000004e: DW_TAG_subprogram
DW_AT_low_pc (0x0000000000000010)
DW_AT_high_pc (0x0000000000000015)
DW_AT_frame_base (DW_OP_reg7 RSP)
DW_AT_call_all_calls (true)
DW_AT_name ("foo")
DW_AT_decl_file ("/home/yhs/tests/sig-change/struct/test.c")
DW_AT_decl_line (2)
DW_AT_prototyped (true)
DW_AT_calling_convention (DW_CC_nocall)
DW_AT_type (0x0000006d "long")
0x0000005e: DW_TAG_formal_parameter
DW_AT_location (DW_OP_piece 0x8, DW_OP_reg5 RDI, DW_OP_piece 0x8)
DW_AT_name ("arg")
DW_AT_decl_file ("/home/yhs/tests/sig-change/struct/test.c")
DW_AT_decl_line (2)
DW_AT_type (0x00000099 "t")
0x0000006c: NULL
...
0x00000088: DW_TAG_inlined_subroutine
DW_AT_name ("foo")
DW_AT_type (0x0000006d "long")
DW_AT_artificial (true)
DW_AT_specification (0x0000004e "foo")
0x00000092: DW_TAG_formal_parameter
DW_AT_name ("b")
DW_AT_type (0x0000006d "long")
0x00000098: NULL
In the above case for function foo(), the original argument is 'struct t',
but the final actual argument is a 'long' type. DW_TAG_inlined_subroutine
can clearly represent the signature type instead of doing DW_AT_location
thing. Note that the name 'b' presents the second long type value of the
struct 't'.
Example 3
---------
Source:
$ cat test2.c
struct t { long a; long b; long c;};
__attribute__((noinline)) static long foo(struct t arg, int a) {
return arg.a * arg.c;
}
long bar(struct t arg) {
return foo(arg, 1);
}
Compiled and dump dwarf with:
$ clang -O2 -c -g test2.c -mllvm -enable-changed-func-dbinfo
$ llvm-dwarfdump test2.o
...
0x0000003e: DW_TAG_subprogram
DW_AT_low_pc (0x0000000000000010)
DW_AT_high_pc (0x0000000000000015)
DW_AT_frame_base (DW_OP_reg7 RSP)
DW_AT_call_all_calls (true)
DW_AT_name ("bar")
DW_AT_decl_file ("/home/yhs/tests/sig-change/struct/test2.c")
DW_AT_decl_line (5)
DW_AT_prototyped (true)
DW_AT_type (0x0000005f "long")
DW_AT_external (true)
0x0000004d: DW_TAG_formal_parameter
DW_AT_location (DW_OP_fbreg +8)
DW_AT_name ("arg")
DW_AT_decl_file ("/home/yhs/tests/sig-change/struct/test2.c")
DW_AT_decl_line (5)
DW_AT_type (0x00000079 "t")
0x00000058: DW_TAG_call_site
DW_AT_call_origin (0x00000023 "foo")
DW_AT_call_tail_call (true)
DW_AT_call_pc (0x0000000000000010)
0x0000005e: NULL
...
0x00000063: DW_TAG_inlined_subroutine
DW_AT_name ("foo")
DW_AT_type (0x0000005f "long")
DW_AT_artificial (true)
DW_AT_specification (0x00000023 "foo")
0x0000006d: DW_TAG_formal_parameter
DW_AT_name ("arg")
DW_AT_type (0x00000074 "t")
0x00000073: NULL
In the above example, from DW_TAG_subprogram, it is not clear what kind
of type the parameter should be. But DW_TAG_inlined_subroutine can
clearly show what the type should be.
Example 4
---------
Source:
$ cat test.c
__attribute__((noinline)) static int callee(const int *p) { return *p + 42; }
int caller(void) {
int x = 100;
return callee(&x);
}
Compiled and dump dwarf with:
$ clang -O3 -c -g test.c -mllvm -enable-changed-func-dbinfo
$ llvm-dwarfdump test.o
...
0x0000004a: DW_TAG_subprogram
DW_AT_low_pc (0x0000000000000010)
DW_AT_high_pc (0x0000000000000014)
DW_AT_frame_base (DW_OP_reg7 RSP)
DW_AT_call_all_calls (true)
DW_AT_name ("callee")
DW_AT_decl_file ("/home/yhs/tests/sig-change/prom/test.c")
DW_AT_decl_line (1)
DW_AT_prototyped (true)
DW_AT_calling_convention (DW_CC_nocall)
DW_AT_type (0x00000063 "int")
0x0000005a: DW_TAG_formal_parameter
DW_AT_name ("p")
DW_AT_decl_file ("/home/yhs/tests/sig-change/prom/test.c")
DW_AT_decl_line (1)
DW_AT_type (0x00000078 "const int *")
0x00000062: NULL
...
0x00000067: DW_TAG_inlined_subroutine
DW_AT_name ("callee")
DW_AT_type (0x00000063 "int")
DW_AT_artificial (true)
DW_AT_specification (0x0000004a "callee")
0x00000071: DW_TAG_formal_parameter
DW_AT_name ("__0")
DW_AT_type (0x00000063 "int")
0x00000077: NULL
In the above, the function
static int callee(const int *p) { return *p + 42; }
is transformed to
static int callee(int p) { return p + 42; }
But the new signature is not reflected in DW_TAG_subprogram.
The DW_TAG_inlined_subroutine can precisely capture the
signature. Note that the parameter name is "__0" and "0" means
the first argument. The reason is due to the following IR:
define internal ... i32 @callee(i32 %0) unnamed_addr #1 !dbg !23 {
#dbg_value(ptr poison, !29, !DIExpression(), !30)
%2 = add nsw i32 %0, 42, !dbg !31
ret i32 %2, !dbg !32
}
...
!29 = !DILocalVariable(name: "p", arg: 1, scope: !23, file: !1, line: 1, type: !26)
The reason is due to 'ptr poison' as 'ptr poison' mean the debug
value should not be used any more. This is also the reason that
the above DW_TAG_subprogram does not have location information.
DW_TAG_inlined_subroutine can provide correct signature though.
With additional option like
clang -O3 -c -g test.c -mllvm -enable-changed-func-dbinfo -fsave-optimization-record \
-foptimization-record-passes=emit-changed-func-debuginfo
a file test.opt.yaml is generated with the following remark:
$ cat test.opt.yaml
--- !Passed
Pass: emit-changed-func-debuginfo
Name: FindNoDIVariable
DebugLoc: { File: test.c, Line: 1, Column: 0 }
Function: callee
Args:
- String: 'create a new int type '
- ArgName: ''
- String: '('
- ArgIndex: '0'
- String: ')'
...
If we compile like below:
clang -O3 -c -g test.c -fno-discard-value-names -mllvm -enable-changed-func-dbinfo
The function argument name will be preserved
... i32 @callee(i32 %p.0.val) ...
and in such cases,
the DW_TAG_inlined_subroutine looks like below:
0x00000067: DW_TAG_inlined_subroutine
DW_AT_name ("callee")
DW_AT_type (0x00000063 "int")
DW_AT_artificial (true)
DW_AT_specification (0x0000004a "callee")
0x00000071: DW_TAG_formal_parameter
DW_AT_name ("p__0__val")
DW_AT_type (0x00000063 "int")
0x00000077: NULL
Note that the original argument name replaces '.' with "__"
so argument name has proper C standard.
Non-LTO vs. LTO
---------------
For thin-lto mode, we often see kernel symbols like
p9_req_cache.llvm.13472271643223911678
Even if this symbol has identical source level signature with p9_req_cache,
a special DW_TAG_inlined_subroutine will be generated with
name 'p9_req_cache.llvm.13472271643223911678'.
With this, some tool (e.g., pahole) may generate a BTF entry
for this name which could be used for bpf fentry/fexit tracing.
But if a symbol with "<foo>.llvm.<hash>" has different signatures
than the source level "<foo>", then a special DW_TAG_inlined_subroutine
will be generated like below:
0x10f0793f: DW_TAG_inlined_subroutine
DW_AT_name ("flow_offload_fill_route.llvm.14555965973926298225")
DW_AT_artificial (true)
DW_AT_specification (0x10ee9e54 "flow_offload_fill_route")
0x10f07949: DW_TAG_formal_parameter
DW_AT_name ("flow")
DW_AT_type (0x10ee837a "flow_offload *")
0x10f07951: DW_TAG_formal_parameter
DW_AT_name ("route")
DW_AT_type (0x10eea4ef "nf_flow_route *")
0x10f07959: DW_TAG_formal_parameter
DW_AT_name ("dir")
DW_AT_type (0x10ecef15 "flow_offload_tuple_dir")
0x10f07961: NULL
In the above, function "flow_offload_fill_route" has return type
"int" at source level, but optimization eventually made the return
type as "void".
Function specialization
-----------------------
LLVM has a pass FunctionSpecializer (FunctionSpecialization.cpp) which
is called by SCCP pass (Interprocedural Sparse Conditional Constant
Propagation). The FunctionSpecializer may clone functions and SCCP
pass is available for both non-LTO and LTO passes. For any function,
the default clones can be up to 3 and all these clones will have
different signatures than the source signature. This is rare but
it did happen. For example, for linux kernel thin lto mode, I found
the following in the kernel symbol table:
ffffffff812036d0 t print_cpu.specialized.1
In this particular case, after cloning, the original function
'print_cpu' is not used so it is removed. Here, the print_cpu()
call is a static function.
Basically, the compiler creates a specialized 'print_cpu.specialized.1'
function and the original funciton 'print_cpu' also exists. The dwarf
for the above two functions:
0x01484bea: DW_TAG_subprogram
DW_AT_low_pc (0xffffffff812036d0)
DW_AT_high_pc (0xffffffff8120400c)
DW_AT_frame_base (DW_OP_reg6 RBP)
DW_AT_call_all_calls (true)
DW_AT_name ("print_cpu")
DW_AT_decl_file ("/home/yhs/work/bpf-next/kernel/sched/debug.c")
DW_AT_decl_line (922)
DW_AT_prototyped (true)
DW_AT_calling_convention (DW_CC_nocall)
0x01484bfa: DW_TAG_formal_parameter
DW_AT_const_value (0)
DW_AT_name ("m")
DW_AT_decl_file ("/home/yhs/work/bpf-next/kernel/sched/debug.c")
DW_AT_decl_line (922)
DW_AT_type (0x0146fd21 "seq_file *")
0x01484c06: DW_TAG_formal_parameter
DW_AT_location (indexed (0x7ee) loclist = 0x0011ce6d:
[0xffffffff812036d5, 0xffffffff81203730): DW_OP_reg5 RDI
[0xffffffff81203730, 0xffffffff812039fa): DW_OP_reg3 RBX
[0xffffffff812039fa, 0xffffffff81203a89): DW_OP_entry_value(DW_OP_reg5 RDI), DW_OP_stack_value
[0xffffffff81203a89, 0xffffffff81203a8d): DW_OP_reg3 RBX
[0xffffffff81203a8d, 0xffffffff81203d58): DW_OP_breg7 RSP+12
[0xffffffff81203d7a, 0xffffffff81203ddd): DW_OP_breg7 RSP+12
[0xffffffff81203dfa, 0xffffffff81203f7b): DW_OP_breg7 RSP+12
[0xffffffff81203f7b, 0xffffffff81203f80): DW_OP_entry_value(DW_OP_reg5 RDI), DW_OP_stack_value
[0xffffffff81203f80, 0xffffffff8120400c): DW_OP_reg3 RBX)
DW_AT_name ("cpu")
DW_AT_decl_file ("/home/yhs/work/bpf-next/kernel/sched/debug.c")
DW_AT_decl_line (922)
DW_AT_type (0x01462560 "int")
......
0x014981fc: DW_TAG_inlined_subroutine
DW_AT_name ("print_cpu.specialized.1")
DW_AT_artificial (true)
DW_AT_specification (0x01484bea "print_cpu")
0x01498204: DW_TAG_formal_parameter
DW_AT_name ("cpu")
DW_AT_type (0x01462560 "int")
0x0149820c: NULL
The specailized function "print_cpu.specialized.1" has a signature different
from the original one "print_cpu" and its name directly encoded into
DW_AT_name.
Some restrictions
=================
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.
- For arguments, only int/ptr types are supported.
- Some union type arguments (e.g., 8B < union_size <= 16B) may
have issue to pick which member so the related functions may be skipped.
Remarks
=======
A few remarks are available for debugging purpose including
- cannot handle union arguments (greater than 8B but less/equal to 16B).
- cannot find corresponding DILocalVariable for the argument.
- certain cases of dbg fragment handling.
Some statistics with linux kernel
=================================
I have tested this patch set by building latest bpf-next linux kernel.
For no-lto case:
66051 original number of functions
894 signature changed or new with-dot functions with this patch
For thin-lto case:
66227 original number of functions
2990 signature changed or new with-dot functions with this patch
Next step
=========
With this llvm change, we will be able to do some work in pahole.
For pahole, currently we will see the warning:
die__process_unit: DW_TAG_inlined_subroutine (0x1d) @ <0xf2db986> not handled in a c11 CU!
Basically these DW_TAG_inlined_subroutine are not inside the DISubprogram.
[1] https://github.com/llvm/llvm-project/pull/127855
[2] https://github.com/llvm/llvm-project/pull/157349
[3] https://discourse.llvm.org/t/rfc-identify-func-signature-change-in-llvm-compiled-kernel-image/82609
---
.../Utils/EmitChangedFuncDebugInfo.h | 33 ++
llvm/lib/CodeGen/AsmPrinter/DwarfDebug.cpp | 66 +++
llvm/lib/CodeGen/AsmPrinter/DwarfDebug.h | 2 +
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 | 549 ++++++++++++++++++
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 +
.../changed-func-dbg-argpromotion-dwarf.ll | 81 +++
.../Util/changed-func-dbg-argpromotion.ll | 96 +++
.../Util/changed-func-dbg-deadarg-dwarf.ll | 106 ++++
.../Util/changed-func-dbg-deadarg.ll | 126 ++++
.../Util/changed-func-dbg-struct-16B-dwarf.ll | 100 ++++
.../Util/changed-func-dbg-struct-16B.ll | 71 +++
.../changed-func-dbg-struct-large-dwarf.ll | 112 ++++
.../Util/changed-func-dbg-struct-large.ll | 94 +++
20 files changed, 1450 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/changed-func-dbg-argpromotion-dwarf.ll
create mode 100644 llvm/test/Transforms/Util/changed-func-dbg-argpromotion.ll
create mode 100644 llvm/test/Transforms/Util/changed-func-dbg-deadarg-dwarf.ll
create mode 100644 llvm/test/Transforms/Util/changed-func-dbg-deadarg.ll
create mode 100644 llvm/test/Transforms/Util/changed-func-dbg-struct-16B-dwarf.ll
create mode 100644 llvm/test/Transforms/Util/changed-func-dbg-struct-16B.ll
create mode 100644 llvm/test/Transforms/Util/changed-func-dbg-struct-large-dwarf.ll
create mode 100644 llvm/test/Transforms/Util/changed-func-dbg-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/CodeGen/AsmPrinter/DwarfDebug.cpp b/llvm/lib/CodeGen/AsmPrinter/DwarfDebug.cpp
index 40bfea059c707..e007fe0488b40 100644
--- a/llvm/lib/CodeGen/AsmPrinter/DwarfDebug.cpp
+++ b/llvm/lib/CodeGen/AsmPrinter/DwarfDebug.cpp
@@ -1280,11 +1280,77 @@ void DwarfDebug::finishSubprogramDefinitions() {
}
}
+void DwarfDebug::addChangedSubprograms() {
+ // Generate additional dwarf for functions with signature changed.
+ DICompileUnit *ExtraCU = nullptr;
+ for (DICompileUnit *CUNode : MMI->getModule()->debug_compile_units()) {
+ if (CUNode->getFile()->getFilename() == "<changed_signatures>") {
+ ExtraCU = CUNode;
+ break;
+ }
+ }
+ if (!ExtraCU)
+ return;
+
+ llvm::DebugInfoFinder DIF;
+ DIF.processModule(*MMI->getModule());
+ for (auto *ExtraSP : DIF.subprograms()) {
+ if (ExtraSP->getUnit() != ExtraCU)
+ continue;
+
+ DISubprogram *SP = cast<DISubprogram>(ExtraSP->getScope());
+ DwarfCompileUnit &Cu = getOrCreateDwarfCompileUnit(SP->getUnit());
+ DIE *ScopeDIE =
+ DIE::get(DIEValueAllocator, dwarf::DW_TAG_inlined_subroutine);
+ Cu.getUnitDie().addChild(ScopeDIE);
+
+ Cu.addString(*ScopeDIE, dwarf::DW_AT_name, ExtraSP->getName());
+ if (!ExtraSP->getLinkageName().empty())
+ Cu.addString(*ScopeDIE, dwarf::DW_AT_linkage_name,
+ ExtraSP->getLinkageName());
+
+ DITypeRefArray Args = ExtraSP->getType()->getTypeArray();
+
+ if (Args[0])
+ Cu.addType(*ScopeDIE, Args[0]);
+
+ if (ExtraSP->getType()->getCC() == llvm::dwarf::DW_CC_nocall) {
+ Cu.addUInt(*ScopeDIE, dwarf::DW_AT_calling_convention,
+ dwarf::DW_FORM_data1, llvm::dwarf::DW_CC_nocall);
+ }
+
+ Cu.addFlag(*ScopeDIE, dwarf::DW_AT_artificial);
+
+ // dereference the DIE* for DIEEntry
+ DIE *OriginDIE = Cu.getOrCreateSubprogramDIE(SP, nullptr);
+ Cu.addDIEEntry(*ScopeDIE, dwarf::DW_AT_specification, DIEEntry(*OriginDIE));
+
+ SmallVector<const DILocalVariable *> ArgVars(Args.size());
+ for (const DINode *DN : ExtraSP->getRetainedNodes()) {
+ if (const auto *DV = dyn_cast<DILocalVariable>(DN)) {
+ uint32_t Arg = DV->getArg();
+ if (Arg)
+ ArgVars[Arg - 1] = DV;
+ }
+ }
+
+ for (unsigned i = 1, N = Args.size(); i < N; ++i) {
+ const DIType *Ty = Args[i];
+ DIE &Arg = Cu.createAndAddDIE(dwarf::DW_TAG_formal_parameter, *ScopeDIE);
+ const DILocalVariable *DV = ArgVars[i - 1];
+ Cu.addString(Arg, dwarf::DW_AT_name, DV->getName());
+ Cu.addType(Arg, Ty);
+ }
+ }
+}
+
void DwarfDebug::finalizeModuleInfo() {
const TargetLoweringObjectFile &TLOF = Asm->getObjFileLowering();
finishSubprogramDefinitions();
+ addChangedSubprograms();
+
finishEntityDefinitions();
bool HasEmittedSplitCU = false;
diff --git a/llvm/lib/CodeGen/AsmPrinter/DwarfDebug.h b/llvm/lib/CodeGen/AsmPrinter/DwarfDebug.h
index 1a1b28a6fc035..414abd4c7b8cf 100644
--- a/llvm/lib/CodeGen/AsmPrinter/DwarfDebug.h
+++ b/llvm/lib/CodeGen/AsmPrinter/DwarfDebug.h
@@ -565,6 +565,8 @@ class DwarfDebug : public DebugHandlerBase {
void finishSubprogramDefinitions();
+ void addChangedSubprograms();
+
/// Finish off debug information after all functions have been
/// processed.
void finalizeModuleInfo();
diff --git a/llvm/lib/Passes/PassBuilder.cpp b/llvm/lib/Passes/PassBuilder.cpp
index f5281ea69b512..28de1a2288120 100644
--- a/llvm/lib/Passes/PassBuilder.cpp
+++ b/llvm/lib/Passes/PassBuilder.cpp
@@ -351,6 +351,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 dd73c04959732..14ec065d8aa53 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"
@@ -1645,9 +1646,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 074c328ef0931..a81cb13ace357 100644
--- a/llvm/lib/Passes/PassRegistry.def
+++ b/llvm/lib/Passes/PassRegistry.def
@@ -76,6 +76,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..e5ed55c730edf
--- /dev/null
+++ b/llvm/lib/Transforms/Utils/EmitChangedFuncDebugInfo.cpp
@@ -0,0 +1,549 @@
+//==- 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 pass synthesizes a "shadow" DISubprogram carrying a changed signature
+// for certain optimized functions or a compiler generated function e.g.
+// foo.llvm.<hash>. The new subprogram lives in a dedicated DICompileUnit whose
+// file name is "<changed_signatures>", and is attached to a dummy
+// AvailableExternally function.
+//
+// The changed signatures rely on dbg records in the entry block. In certain
+// case, e.g., not able to get proper argument type, the function will be
+// skipped. Remarks provides additional information about what functions to be
+// skipped and useful information.
+//
+// Only C-family source language is supported. Functions with varargs are
+// skipped, and functions whose return type is a large by-value aggregate
+// is skipped, etc.
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Transforms/Utils/EmitChangedFuncDebugInfo.h"
+
+#include "llvm/Analysis/OptimizationRemarkEmitter.h"
+#include "llvm/IR/DIBuilder.h"
+#include "llvm/IR/IRBuilder.h"
+#include "llvm/IR/Module.h"
+#include "llvm/TargetParser/Triple.h"
+
+using namespace llvm;
+
+#define DEBUG_TYPE "emit-changed-func-debuginfo"
+
+// Disable/Enable switch.
+static cl::opt<bool> EnableChangedFuncDBInfo(
+ "enable-changed-func-dbinfo", cl::Hidden, cl::init(false),
+ cl::desc("Enable debuginfo emission for changed func signatures"));
+
+// Replace all '.' with "__" (stable with opaque-lifetime inputs).
+static std::string sanitizeDots(StringRef S) {
+ std::string Out = S.str();
+ for (size_t pos = 0; (pos = Out.find('.', pos)) != std::string::npos;
+ pos += 2)
+ Out.replace(pos, 1, "__");
+ return Out;
+}
+
+// Ensure a variable name is unique among previously recorded parameters.
+// If collision, append "__<Idx>".
+static std::string uniquifyParamName(StringRef Candidate,
+ ArrayRef<Metadata *> Existing,
+ unsigned Idx) {
+ for (unsigned i = 0; i < Existing.size(); ++i)
+ if (auto *LV = dyn_cast<DILocalVariable>(Existing[i]))
+ if (LV->getName() == Candidate)
+ return (Twine(Candidate) + "__" + Twine(Idx)).str();
+ return Candidate.str();
+}
+
+// Walk backward in the current block to see whether LocV matches one of
+// previous insn or some operand of those insns.
+static bool comesFromArgViaCast(Value *LocV, Argument *Arg, Instruction &At) {
+ if (!LocV)
+ return false;
+ for (Instruction *Prev = At.getPrevNode(); Prev; Prev = Prev->getPrevNode()) {
+ // FIXME: maybe some other insns need check as well.
+ if (auto *Z = dyn_cast<ZExtInst>(Prev))
+ if (Z->getOperand(0) == Arg && LocV == Prev)
+ return true;
+ if (auto *T = dyn_cast<TruncInst>(Prev))
+ if (T->getOperand(0) == Arg && LocV == Prev)
+ return true;
+ if (auto *T = dyn_cast<StoreInst>(Prev))
+ if (T->getOperand(0) == Arg && LocV == T->getOperand(1))
+ return true;
+ }
+ return false;
+}
+
+// Strip qualifiers/typedefs until the first pointer-type (which we keep), or
+// to the base non-derived type if no pointer is found.
+static DIType *stripToBaseOrFirstPointer(DIType *T) {
+ while (auto *DT = dyn_cast_or_null<DIDerivedType>(T)) {
+ if (DT->getTag() == dwarf::DW_TAG_pointer_type)
+ return DT;
+ T = DT->getBaseType();
+ }
+ return T;
+}
+
+static DIType *createBasicType(DIBuilder &DIB, uint64_t SizeInBits) {
+ switch (SizeInBits) {
+ case 8:
+ return DIB.createBasicType("signed char", 8, dwarf::DW_ATE_signed_char);
+ case 16:
+ return DIB.createBasicType("short", 16, dwarf::DW_ATE_signed);
+ case 32:
+ return DIB.createBasicType("int", 32, dwarf::DW_ATE_signed);
+ case 64:
+ return DIB.createBasicType("long", 64, dwarf::DW_ATE_signed);
+ default:
+ return DIB.createBasicType("__int128", SizeInBits, dwarf::DW_ATE_signed);
+ }
+}
+
+static DIType *getIntTypeFromExpr(DIBuilder &DIB, DIExpression *Expr,
+ DICompositeType *DTy, unsigned W,
+ unsigned PointerBitWidth, Function *F,
+ std::string &CoerceName, Argument *Arg,
+ unsigned Idx) {
+ for (auto Op : Expr->expr_ops()) {
+ if (Op.getOp() != dwarf::DW_OP_LLVM_fragment)
+ break;
+
+ uint64_t BitOffset = Op.getArg(0);
+ uint64_t BitSize = Op.getArg(1);
+ uint64_t BitUpLimit = BitOffset + BitSize;
+
+ DINodeArray Elems = DTy->getElements();
+ unsigned N = Elems.size();
+
+ for (unsigned i = 0; i < N; ++i) {
+ if (auto *Elem = dyn_cast<DIDerivedType>(Elems[i])) {
+ if (N >= 2 && i < N - 1) {
+ if (Elem->getOffsetInBits() <= BitOffset &&
+ BitUpLimit <= (Elem->getOffsetInBits() + Elem->getSizeInBits())) {
+ CoerceName = Elem->getName();
+ return Elem->getBaseType();
+ }
+ } else {
+ if (Elem->getOffsetInBits() <= BitOffset &&
+ BitUpLimit <= DTy->getSizeInBits()) {
+ CoerceName = Elem->getName();
+ return Elem->getBaseType();
+ }
+ }
+ }
+ }
+ }
+
+ OptimizationRemarkEmitter ORE(F);
+
+ ORE.emit([&]() {
+ return OptimizationRemark(DEBUG_TYPE, "ParamDITypeWithNocallCC", F)
+ << "create an int type " << ore::NV("ArgName", Arg->getName()) << "("
+ << ore::NV("ArgIndex", Idx) << ")";
+ });
+
+ return createBasicType(DIB, W);
+}
+
+static DIType *computeParamDIType(DIBuilder &DIB, Type *Ty, DIType *Orig,
+ unsigned PointerBitWidth, DIExpression *Expr,
+ bool ByVal, Function *F,
+ std::string &CoerceName, Argument *Arg,
+ unsigned Idx) {
+ DIType *Stripped = stripToBaseOrFirstPointer(Orig);
+ unsigned TyBitSize = Stripped->getSizeInBits();
+
+ if (TyBitSize <= PointerBitWidth || ByVal)
+ return Orig;
+
+ auto *Comp = cast<DICompositeType>(Stripped);
+ if (Comp->getTag() == dwarf::DW_TAG_union_type) {
+ OptimizationRemarkEmitter ORE(F);
+
+ ORE.emit([&]() {
+ return OptimizationRemark(DEBUG_TYPE, "ParamDITypeWithNocallCC", F)
+ << "cannot handle union type "
+ << ore::NV("ArgName", Arg->getName()) << "("
+ << ore::NV("ArgIndex", Idx) << ")";
+ });
+
+ return nullptr;
+ }
+
+ unsigned W = cast<IntegerType>(Ty)->getBitWidth();
+ return getIntTypeFromExpr(DIB, Expr, Comp, W, PointerBitWidth, F, CoerceName,
+ Arg, Idx);
+}
+
+static void pushParam(DIBuilder &DIB, DISubprogram *OldSP, DISubprogram *NewSP,
+ SmallVectorImpl<Metadata *> &TypeList,
+ SmallVectorImpl<Metadata *> &ArgList, DIType *Ty,
+ StringRef VarName, unsigned Idx) {
+ TypeList.push_back(Ty);
+ ArgList.push_back(DIB.createParameterVariable(
+ NewSP, VarName, Idx + 1, OldSP->getFile(), OldSP->getLine(), Ty));
+}
+
+// Argument collection.
+static bool getOneArgDI(unsigned Idx, BasicBlock &Entry, DIBuilder &DIB,
+ Function *F, DISubprogram *OldSP, DISubprogram *NewSP,
+ SmallVectorImpl<Metadata *> &TypeList,
+ SmallVectorImpl<Metadata *> &ArgList,
+ unsigned PointerBitWidth) {
+ Argument *Arg = F->getArg(Idx);
+ StringRef ArgName = Arg->getName();
+ Type *ArgTy = Arg->getType();
+
+ // If byval struct, remember its identified-name and kind to match via dbg.
+ StringRef ByValTypeName;
+ bool IsByValStruct = true;
+ if (ArgTy->isPointerTy() && Arg->hasByValAttr()) {
+ if (Type *ByValTy = F->getParamByValType(Idx))
+ if (auto *ST = dyn_cast<StructType>(ByValTy)) {
+ auto [Kind, Name] = ST->getName().split('.');
+ ByValTypeName = Name;
+ IsByValStruct = (Kind == "struct");
+ }
+ }
+
+ DILocalVariable *DIVar = nullptr;
+ DIExpression *DIExpr = nullptr;
+
+ // Scan the entry block for dbg records.
+ for (Instruction &I : Entry) {
+ bool Final = false;
+
+ for (DbgRecord &DR : I.getDbgRecordRange()) {
+ auto *DVR = dyn_cast<DbgVariableRecord>(&DR);
+ if (!DVR)
+ continue;
+
+ auto *VAM = dyn_cast_or_null<ValueAsMetadata>(DVR->getRawLocation());
+ if (!VAM)
+ continue;
+
+ Value *LocV = VAM->getValue();
+ auto *Var = DVR->getVariable();
+ if (!Var || !Var->getArg())
+ continue;
+
+ // Canonicalize through derived types stopping at first pointer.
+ 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 (LocV == Arg) {
+ DIVar = Var;
+ DIExpr = DVR->getExpression();
+ Final = true;
+ break;
+ }
+
+ if (!ByValTypeName.empty()) {
+ // Match by byval struct/union DI type’s name/kind.
+ DIType *Stripped = stripToBaseOrFirstPointer(Var->getType());
+ auto *Comp = dyn_cast<DICompositeType>(Stripped);
+ if (!Comp)
+ continue;
+ bool IsStruct = Comp->getTag() == dwarf::DW_TAG_structure_type;
+ if (Comp->getName() != ByValTypeName || IsStruct != IsByValStruct)
+ continue;
+ DIVar = Var;
+ DIExpr = DVR->getExpression();
+ // Possibly we may find a true match later.
+ }
+
+ if (ArgName.empty()) {
+ if (isa<PoisonValue>(LocV))
+ continue;
+
+ if (comesFromArgViaCast(LocV, Arg, I)) {
+ DIVar = Var;
+ DIExpr = DVR->getExpression();
+ Final = true;
+ break;
+ }
+
+ if (isa<AllocaInst>(LocV))
+ continue;
+ } else {
+ // We do have an IR arg name.
+ // Compare base names (before dot).
+ StringRef ArgBase = ArgName.take_front(ArgName.find('.'));
+ StringRef VarBase = Var->getName();
+
+ if (isa<PoisonValue>(LocV)) {
+ if (Var->getName() != ArgBase)
+ continue;
+ DIVar = Var;
+ DIExpr = DVR->getExpression();
+ // Possibly we may find a non poison value later.
+ } else if (isa<AllocaInst>(LocV)) {
+ if (Var->getName() != ArgName)
+ continue;
+ DIVar = Var;
+ DIExpr = DVR->getExpression();
+ Final = true;
+ break;
+ } else if (ArgBase == VarBase) {
+ DIVar = Var;
+ DIExpr = DVR->getExpression();
+ Final = true;
+ break;
+ } else if (comesFromArgViaCast(LocV, Arg, I)) {
+ DIVar = Var;
+ DIExpr = DVR->getExpression();
+ Final = true;
+ break;
+ }
+ }
+ }
+
+ if (Final)
+ break;
+ }
+
+ if (!DIVar) {
+ OptimizationRemarkEmitter ORE(F);
+
+ if (ArgTy->isIntegerTy()) {
+ // Probably due to argument promotion, e.g., a pointer argument becomes
+ // an integer. The dbg does not give a clear mapping to the argument.
+ // So create an int type.
+ auto *Ty = createBasicType(DIB, cast<IntegerType>(ArgTy)->getBitWidth());
+ pushParam(DIB, OldSP, NewSP, TypeList, ArgList, Ty,
+ (Twine("__") + Twine(Idx)).str(), Idx);
+
+ ORE.emit([&]() {
+ return OptimizationRemark(DEBUG_TYPE, "FindNoDIVariable", F)
+ << "create a new int type " << ore::NV("ArgName", Arg->getName())
+ << "(" << ore::NV("ArgIndex", Idx) << ")";
+ });
+
+ return true;
+ }
+
+ ORE.emit([&]() {
+ return OptimizationRemark(DEBUG_TYPE, "FindNoDIVariable", F)
+ << "cannot find pointee type "
+ << ore::NV("ArgName", Arg->getName()) << "("
+ << ore::NV("ArgIndex", Idx) << ")";
+ });
+
+ return false;
+ }
+
+ // Compute parameter DI type from IR type + original debug type.
+ std::string CoerceName;
+ DIType *ParamType =
+ computeParamDIType(DIB, ArgTy, DIVar->getType(), PointerBitWidth, DIExpr,
+ !ByValTypeName.empty(), F, CoerceName, Arg, Idx);
+ if (!ParamType)
+ return false;
+
+ // Decide the parameter name (sanitize + uniquify).
+ std::string VarName;
+ if (ArgName.empty()) {
+ VarName = CoerceName.empty() ? DIVar->getName() : CoerceName;
+ VarName = uniquifyParamName(VarName, ArgList, Idx);
+ } else {
+ VarName = sanitizeDots(ArgName);
+ }
+
+ pushParam(DIB, OldSP, NewSP, TypeList, ArgList, ParamType, VarName, Idx);
+ return true;
+}
+
+// Collect return and parameter DI information.
+static bool collectReturnAndArgs(DIBuilder &DIB, Function *F,
+ DISubprogram *OldSP, DISubprogram *NewSP,
+ SmallVectorImpl<Metadata *> &TypeList,
+ SmallVectorImpl<Metadata *> &ArgList,
+ unsigned PointerBitWidth) {
+ FunctionType *FTy = F->getFunctionType();
+ Type *RetTy = FTy->getReturnType();
+
+ if (RetTy->isVoidTy())
+ TypeList.push_back(nullptr);
+ else
+ TypeList.push_back(OldSP->getType()->getTypeArray()[0]);
+
+ BasicBlock &Entry = F->getEntryBlock();
+ for (unsigned i = 0, n = FTy->getNumParams(); i < n; ++i)
+ if (!getOneArgDI(i, Entry, DIB, F, OldSP, NewSP, TypeList, ArgList,
+ PointerBitWidth))
+ return false;
+ return true;
+}
+
+static DICompileUnit *findChangedSigCU(Module &M) {
+ if (NamedMDNode *CUs = M.getNamedMetadata("llvm.dbg.cu"))
+ for (MDNode *Node : CUs->operands()) {
+ auto *CU = cast<DICompileUnit>(Node);
+ if (CU->getFile()->getFilename() == "<changed_signatures>")
+ return CU;
+ }
+ return nullptr;
+}
+
+static void generateDebugInfo(Module &M, Function *F,
+ unsigned PointerBitWidth) {
+ DICompileUnit *NewCU = findChangedSigCU(M);
+ DIBuilder DIB(M, /*AllowUnresolved=*/false, NewCU);
+
+ DISubprogram *OldSP = F->getSubprogram();
+ DIFile *NewFile;
+
+ if (NewCU) {
+ NewFile = NewCU->getFile();
+ } else {
+ DICompileUnit *OldCU = OldSP->getUnit();
+ DIFile *OldFile = OldCU->getFile();
+ NewFile = DIB.createFile("<changed_signatures>", OldFile->getDirectory());
+ NewCU = DIB.createCompileUnit(
+ OldCU->getSourceLanguage(), NewFile, OldCU->getProducer(),
+ OldCU->isOptimized(), OldCU->getFlags(), OldCU->getRuntimeVersion());
+ }
+
+ SmallVector<Metadata *, 5> TypeList, ArgList;
+ DISubroutineType *SubTy = nullptr;
+ StringRef FuncName, LinkageName;
+ DINodeArray ArgArray;
+ DISubprogram *NewSP;
+
+ uint8_t cc = OldSP->getType()->getCC();
+ FuncName = F->getName();
+ if (cc != dwarf::DW_CC_nocall) {
+ SubTy = OldSP->getType();
+ NewSP =
+ DIB.createFunction(OldSP, FuncName, LinkageName, NewFile,
+ OldSP->getLine(), SubTy, OldSP->getScopeLine(),
+ DINode::FlagZero, DISubprogram::SPFlagDefinition);
+ for (DINode *DN : OldSP->getRetainedNodes()) {
+ if (auto *DV = dyn_cast<DILocalVariable>(DN)) {
+ ArgList.push_back(DIB.createParameterVariable(
+ NewSP, DV->getName(), DV->getArg(), OldSP->getFile(),
+ OldSP->getLine(), DV->getType()));
+ }
+ }
+ } else {
+ NewSP =
+ DIB.createFunction(OldSP, FuncName, LinkageName, NewFile,
+ OldSP->getLine(), SubTy, OldSP->getScopeLine(),
+ DINode::FlagZero, DISubprogram::SPFlagDefinition);
+
+ bool Success = collectReturnAndArgs(DIB, F, OldSP, NewSP, TypeList, ArgList,
+ PointerBitWidth);
+ if (!Success) {
+ DIB.finalize();
+ return;
+ }
+
+ DITypeRefArray DITypeArray =
+ DIB.getOrCreateTypeArray(ArrayRef<Metadata *>{TypeList});
+ SubTy = DIB.createSubroutineType(DITypeArray);
+ NewSP->replaceType(SubTy);
+ }
+ ArgArray = DIB.getOrCreateArray(ArgList);
+
+ // Dummy anchor function
+ Function *DummyF = Function::Create(F->getFunctionType(),
+ GlobalValue::AvailableExternallyLinkage,
+ F->getName() + ".newsig", &M);
+
+ // Provide a trivial body so the SP is marked as "defined".
+ BasicBlock *BB = BasicBlock::Create(M.getContext(), "entry", DummyF);
+ IRBuilder<> IRB(BB);
+ IRB.CreateUnreachable();
+ DummyF->setSubprogram(NewSP);
+
+ DIB.finalize();
+
+ // Add retained nodes after DIB.finalize(). Otherwise, DIB.finalize()
+ // might make un-intented change.
+ NewSP->replaceRetainedNodes(ArgArray);
+}
+
+PreservedAnalyses EmitChangedFuncDebugInfoPass::run(Module &M,
+ ModuleAnalysisManager &AM) {
+ if (!EnableChangedFuncDBInfo)
+ return PreservedAnalyses::all();
+
+ // Only C-family
+ for (DICompileUnit *CU : M.debug_compile_units()) {
+ auto L = CU->getSourceLanguage().getUnversionedName();
+ 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();
+ }
+
+ Triple T(M.getTargetTriple());
+
+ // Skip BPF for now.
+ if (T.isBPF())
+ return PreservedAnalyses::all();
+
+ unsigned PointerBitWidth = T.getArchPointerBitWidth();
+
+ SmallVector<Function *> ChangedFuncs;
+ for (Function &F : M) {
+ if (F.isIntrinsic() || F.isDeclaration())
+ continue;
+
+ DISubprogram *SP = F.getSubprogram();
+ if (!SP)
+ continue;
+
+ DISubroutineType *SPType = SP->getType();
+ DITypeRefArray TyArray = SPType->getTypeArray();
+ if (TyArray.size() == 0)
+ continue;
+
+ // Skip varargs
+ if (TyArray.size() > 1 && TyArray[TyArray.size() - 1] == nullptr)
+ continue;
+
+ // For C language, only supports int/ptr types, no support for
+ // floating_point/vector.
+ unsigned i = 0;
+ unsigned n = F.getFunctionType()->getNumParams();
+ for (i = 0; i < n; ++i) {
+ Type *ArgTy = F.getArg(i)->getType();
+ if (ArgTy->isVectorTy() || ArgTy->isFloatingPointTy())
+ break;
+ }
+ if (i != n)
+ continue;
+
+ // Skip if large by-value return
+ DIType *RetDI = stripToBaseOrFirstPointer(TyArray[0]);
+ if (auto *Comp = dyn_cast_or_null<DICompositeType>(RetDI))
+ if (Comp->getSizeInBits() > PointerBitWidth)
+ continue;
+
+ if (!F.getName().contains('.') && SPType->getCC() != dwarf::DW_CC_nocall)
+ continue;
+
+ ChangedFuncs.push_back(&F);
+ }
+
+ for (Function *F : ChangedFuncs)
+ generateDebugInfo(M, F, PointerBitWidth);
+
+ return ChangedFuncs.empty() ? PreservedAnalyses::all()
+ : PreservedAnalyses::none();
+}
diff --git a/llvm/test/Other/new-pm-defaults.ll b/llvm/test/Other/new-pm-defaults.ll
index 1f437a662cc96..747bb7126fd5b 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 2d8b8f1b22091..84274926ded69 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 7cacc17c7ab9a..9bdaf09deda72 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 ef6cd8354ae3d..9ae9b7558dc48 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/changed-func-dbg-argpromotion-dwarf.ll b/llvm/test/Transforms/Util/changed-func-dbg-argpromotion-dwarf.ll
new file mode 100644
index 0000000000000..7c8adcf3a4fd6
--- /dev/null
+++ b/llvm/test/Transforms/Util/changed-func-dbg-argpromotion-dwarf.ll
@@ -0,0 +1,81 @@
+; Check the generated DWARF debug info:
+; RUN: opt -S -mtriple=x86_64-unknown-unknown -passes=emit-changed-func-debuginfo -enable-changed-func-dbinfo < %s \
+; RUN: | %llc_dwarf -filetype=obj -o - \
+; RUN: | llvm-dwarfdump - | FileCheck %s --check-prefix=DWARF
+;
+; REQUIRES: debug_frame
+; REQUIRES: object-emission
+
+; Source code:
+; // clang -S -emit-llvm -O3 -g test.c
+; __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 {
+ #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 {
+ #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)
+
+; DWARF: DW_TAG_inlined_subroutine
+; DWARF-NEXT: DW_AT_name ("callee")
+; DWARF-NEXT: DW_AT_type
+; DWARF-SAME: "int"
+; DWARF-NEXT: DW_AT_artificial (true)
+; DWARF-NEXT: DW_AT_specification
+; DWARF-SAME: "callee"
+; DWARF-NEXT: {{^$}}
+; DWARF-NEXT: DW_TAG_formal_parameter
+; DWARF-NEXT: DW_AT_name ("__0")
+; DWARF-NEXT: DW_AT_type
+; DWARF-SAME: "int"
+; DWARF-NEXT: {{^$}}
+; DWARF-NEXT: NULL
diff --git a/llvm/test/Transforms/Util/changed-func-dbg-argpromotion.ll b/llvm/test/Transforms/Util/changed-func-dbg-argpromotion.ll
new file mode 100644
index 0000000000000..9d68a3103b056
--- /dev/null
+++ b/llvm/test/Transforms/Util/changed-func-dbg-argpromotion.ll
@@ -0,0 +1,96 @@
+; 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 -enable-changed-func-dbinfo < %s | FileCheck %s
+
+; Source code:
+; // clang -S -emit-llvm -O3 -g test.c
+; __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 [[DBG12:![0-9]+]] {
+; CHECK-NEXT: #dbg_value(i32 100, [[META17:![0-9]+]], !DIExpression(), [[META18:![0-9]+]])
+; CHECK-NEXT: [[TMP1:%.*]] = tail call fastcc i32 @callee(i32 100), !dbg [[DBG19:![0-9]+]]
+; CHECK-NEXT: ret i32 [[TMP1]], !dbg [[DBG20:![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 [[DBG21:![0-9]+]] {
+; CHECK-NEXT: #dbg_value(ptr poison, [[META27:![0-9]+]], !DIExpression(), [[META28:![0-9]+]])
+; CHECK-NEXT: [[TMP2:%.*]] = add nsw i32 [[TMP0]], 42, !dbg [[DBG29:![0-9]+]]
+; CHECK-NEXT: ret i32 [[TMP2]], !dbg [[DBG30:![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: [[DBG12]] = distinct !DISubprogram(name: "caller", scope: [[META1]], file: [[META1]], line: 2, type: [[META13:![0-9]+]], scopeLine: 2, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: [[META0]], retainedNodes: [[META16:![0-9]+]], keyInstructions: true)
+; CHECK: [[META13]] = !DISubroutineType(types: [[META14:![0-9]+]])
+; CHECK: [[META14]] = !{[[META15:![0-9]+]]}
+; CHECK: [[META15]] = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+; CHECK: [[META16]] = !{[[META17]]}
+; CHECK: [[META17]] = !DILocalVariable(name: "x", scope: [[DBG12]], file: [[META1]], line: 3, type: [[META15]])
+; CHECK: [[META18]] = !DILocation(line: 0, scope: [[DBG12]])
+; CHECK: [[DBG19]] = !DILocation(line: 4, column: 10, scope: [[DBG12]], atomGroup: 3, atomRank: 2)
+; CHECK: [[DBG20]] = !DILocation(line: 4, column: 3, scope: [[DBG12]], atomGroup: 3, atomRank: 1)
+; CHECK: [[DBG21]] = distinct !DISubprogram(name: "callee", scope: [[META1]], file: [[META1]], line: 1, type: [[META22:![0-9]+]], scopeLine: 1, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagLocalToUnit | DISPFlagDefinition | DISPFlagOptimized, unit: [[META0]], retainedNodes: [[META26:![0-9]+]], keyInstructions: true)
+; CHECK: [[META22]] = !DISubroutineType(cc: DW_CC_nocall, types: [[META23:![0-9]+]])
+; CHECK: [[META23]] = !{[[META15]], [[META24:![0-9]+]]}
+; CHECK: [[META24]] = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: [[META25:![0-9]+]], size: 64)
+; CHECK: [[META25]] = !DIDerivedType(tag: DW_TAG_const_type, baseType: [[META15]])
+; CHECK: [[META26]] = !{[[META27]]}
+; CHECK: [[META27]] = !DILocalVariable(name: "p", arg: 1, scope: [[DBG21]], file: [[META1]], line: 1, type: [[META24]])
+; CHECK: [[META28]] = !DILocation(line: 0, scope: [[DBG21]])
+; CHECK: [[DBG29]] = !DILocation(line: 1, column: 71, scope: [[DBG21]], atomGroup: 1, atomRank: 2)
+; CHECK: [[DBG30]] = !DILocation(line: 1, column: 61, scope: [[DBG21]], atomGroup: 1, atomRank: 1)
+;.
diff --git a/llvm/test/Transforms/Util/changed-func-dbg-deadarg-dwarf.ll b/llvm/test/Transforms/Util/changed-func-dbg-deadarg-dwarf.ll
new file mode 100644
index 0000000000000..1fab37e36d0c7
--- /dev/null
+++ b/llvm/test/Transforms/Util/changed-func-dbg-deadarg-dwarf.ll
@@ -0,0 +1,106 @@
+; Check the generated DWARF debug info:
+; RUN: opt -S -mtriple=x86_64-unknown-unknown -passes=emit-changed-func-debuginfo -enable-changed-func-dbinfo < %s \
+; RUN: | %llc_dwarf -filetype=obj -o - \
+; RUN: | llvm-dwarfdump - | FileCheck %s --check-prefix=DWARF
+;
+; REQUIRES: debug_frame
+; REQUIRES: object-emission
+
+; Source code:
+; // clang -O2 -S -emit-llvm -g 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);
+; }
+
+; Function Attrs: nounwind uwtable
+define dso_local ptr @bar(ptr noundef %0, ptr noundef %1) local_unnamed_addr #0 !dbg !10 {
+ #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 {
+ #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)
+
+; DWARF: DW_TAG_inlined_subroutine
+; DWARF-NEXT: DW_AT_name ("foo")
+; DWARF-NEXT: DW_AT_type
+; DWARF-SAME: "char *"
+; DWARF-NEXT: DW_AT_artificial (true)
+; DWARF-NEXT: DW_AT_specification
+; DWARF-SAME: "foo"
+; DWARF-NEXT: {{^$}}
+; DWARF-NEXT: DW_TAG_formal_parameter
+; DWARF-NEXT: DW_AT_name ("a")
+; DWARF-NEXT: DW_AT_type
+; DWARF-SAME: "t *"
+; DWARF-NEXT: {{^$}}
+; DWARF-NEXT: DW_TAG_formal_parameter
+; DWARF-NEXT: DW_AT_name ("d")
+; DWARF-NEXT: DW_AT_type
+; DWARF-SAME: "t *"
+; DWARF-NEXT: {{^$}}
+; DWARF-NEXT: NULL
diff --git a/llvm/test/Transforms/Util/changed-func-dbg-deadarg.ll b/llvm/test/Transforms/Util/changed-func-dbg-deadarg.ll
new file mode 100644
index 0000000000000..f92b0f128a1db
--- /dev/null
+++ b/llvm/test/Transforms/Util/changed-func-dbg-deadarg.ll
@@ -0,0 +1,126 @@
+; 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 -enable-changed-func-dbinfo < %s | FileCheck %s
+
+; Source code:
+; // clang -O2 -S -emit-llvm -g 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);
+; }
+
+; 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 [[DBG12:![0-9]+]] {
+; CHECK-NEXT: #dbg_value(ptr [[TMP0]], [[META23:![0-9]+]], !DIExpression(), [[META25:![0-9]+]])
+; CHECK-NEXT: #dbg_value(ptr [[TMP1]], [[META24:![0-9]+]], !DIExpression(), [[META25]])
+; CHECK-NEXT: [[TMP3:%.*]] = tail call fastcc ptr @foo(ptr noundef [[TMP0]], ptr noundef [[TMP1]]), !dbg [[DBG26:![0-9]+]]
+; CHECK-NEXT: ret ptr [[TMP3]], !dbg [[DBG27:![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 [[DBG28:![0-9]+]] {
+; CHECK-NEXT: #dbg_value(ptr [[TMP0]], [[META32:![0-9]+]], !DIExpression(), [[META35:![0-9]+]])
+; CHECK-NEXT: #dbg_value(ptr [[TMP1]], [[META33:![0-9]+]], !DIExpression(), [[META35]])
+; CHECK-NEXT: #dbg_value(i32 poison, [[META34:![0-9]+]], !DIExpression(), [[META35]])
+; CHECK-NEXT: [[TMP3:%.*]] = tail call ptr @tar(ptr noundef [[TMP0]], ptr noundef [[TMP1]]) #[[ATTR3:[0-9]+]], !dbg [[DBG36:![0-9]+]]
+; CHECK-NEXT: ret ptr [[TMP3]], !dbg [[DBG37:![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: [[DBG12]] = distinct !DISubprogram(name: "bar", scope: [[META1]], file: [[META1]], line: 7, type: [[META13:![0-9]+]], scopeLine: 8, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: [[META0]], retainedNodes: [[META22:![0-9]+]], keyInstructions: true)
+; CHECK: [[META13]] = !DISubroutineType(types: [[META14:![0-9]+]])
+; CHECK: [[META14]] = !{[[META15:![0-9]+]], [[META17:![0-9]+]], [[META17]]}
+; CHECK: [[META15]] = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: [[META16:![0-9]+]], size: 64)
+; CHECK: [[META16]] = !DIBasicType(name: "char", size: 8, encoding: DW_ATE_signed_char)
+; CHECK: [[META17]] = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: [[META18:![0-9]+]], size: 64)
+; CHECK: [[META18]] = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "t", file: [[META1]], line: 1, size: 32, elements: [[META19:![0-9]+]])
+; CHECK: [[META19]] = !{[[META20:![0-9]+]]}
+; CHECK: [[META20]] = !DIDerivedType(tag: DW_TAG_member, name: "a", scope: [[META18]], file: [[META1]], line: 1, baseType: [[META21:![0-9]+]], size: 32)
+; CHECK: [[META21]] = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+; CHECK: [[META22]] = !{[[META23]], [[META24]]}
+; CHECK: [[META23]] = !DILocalVariable(name: "a", arg: 1, scope: [[DBG12]], file: [[META1]], line: 7, type: [[META17]])
+; CHECK: [[META24]] = !DILocalVariable(name: "d", arg: 2, scope: [[DBG12]], file: [[META1]], line: 7, type: [[META17]])
+; CHECK: [[META25]] = !DILocation(line: 0, scope: [[DBG12]])
+; CHECK: [[DBG26]] = !DILocation(line: 9, column: 10, scope: [[DBG12]], atomGroup: 1, atomRank: 2)
+; CHECK: [[DBG27]] = !DILocation(line: 9, column: 3, scope: [[DBG12]], atomGroup: 1, atomRank: 1)
+; CHECK: [[DBG28]] = distinct !DISubprogram(name: "foo", scope: [[META1]], file: [[META1]], line: 3, type: [[META29:![0-9]+]], scopeLine: 4, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagLocalToUnit | DISPFlagDefinition | DISPFlagOptimized, unit: [[META0]], retainedNodes: [[META31:![0-9]+]], keyInstructions: true)
+; CHECK: [[META29]] = !DISubroutineType(cc: DW_CC_nocall, types: [[META30:![0-9]+]])
+; CHECK: [[META30]] = !{[[META15]], [[META17]], [[META17]], [[META21]]}
+; CHECK: [[META31]] = !{[[META32]], [[META33]], [[META34]]}
+; CHECK: [[META32]] = !DILocalVariable(name: "a", arg: 1, scope: [[DBG28]], file: [[META1]], line: 3, type: [[META17]])
+; CHECK: [[META33]] = !DILocalVariable(name: "d", arg: 2, scope: [[DBG28]], file: [[META1]], line: 3, type: [[META17]])
+; CHECK: [[META34]] = !DILocalVariable(name: "b", arg: 3, scope: [[DBG28]], file: [[META1]], line: 3, type: [[META21]])
+; CHECK: [[META35]] = !DILocation(line: 0, scope: [[DBG28]])
+; CHECK: [[DBG36]] = !DILocation(line: 5, column: 10, scope: [[DBG28]], atomGroup: 1, atomRank: 2)
+; CHECK: [[DBG37]] = !DILocation(line: 5, column: 3, scope: [[DBG28]], atomGroup: 1, atomRank: 1)
+;.
diff --git a/llvm/test/Transforms/Util/changed-func-dbg-struct-16B-dwarf.ll b/llvm/test/Transforms/Util/changed-func-dbg-struct-16B-dwarf.ll
new file mode 100644
index 0000000000000..b89ca17000045
--- /dev/null
+++ b/llvm/test/Transforms/Util/changed-func-dbg-struct-16B-dwarf.ll
@@ -0,0 +1,100 @@
+; Check the generated DWARF debug info:
+; RUN: opt -S -mtriple=x86_64-unknown-unknown -passes=emit-changed-func-debuginfo -enable-changed-func-dbinfo < %s \
+; RUN: | %llc_dwarf -filetype=obj -o - \
+; RUN: | llvm-dwarfdump - | FileCheck %s --check-prefix=DWARF
+;
+; REQUIRES: debug_frame
+; REQUIRES: object-emission
+
+; Source code:
+; // clang -O2 -S -emit-llvm -g test1.c
+; struct t { long a; long b; };
+; __attribute__((noinline)) static int foo(struct t arg, int a) { return arg.a * arg.b; }
+; int bar(struct t arg) {
+; return foo(arg, 1);
+; }
+
+; Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(none) uwtable
+define dso_local i32 @bar(i64 %0, i64 %1) local_unnamed_addr #0 !dbg !14 {
+ #dbg_value(i64 %0, !24, !DIExpression(DW_OP_LLVM_fragment, 0, 64), !25)
+ #dbg_value(i64 %1, !24, !DIExpression(DW_OP_LLVM_fragment, 64, 64), !25)
+ %3 = tail call fastcc i32 @foo(i64 %0, i64 %1), !dbg !26
+ ret i32 %3, !dbg !27
+}
+
+; Function Attrs: mustprogress nofree noinline norecurse nosync nounwind willreturn memory(none) uwtable
+define internal fastcc i32 @foo(i64 %0, i64 %1) unnamed_addr #1 !dbg !28 {
+ #dbg_value(i64 %0, !32, !DIExpression(DW_OP_LLVM_fragment, 0, 64), !34)
+ #dbg_value(i64 %1, !32, !DIExpression(DW_OP_LLVM_fragment, 64, 64), !34)
+ #dbg_value(i32 poison, !33, !DIExpression(), !34)
+ %3 = mul nsw i64 %1, %0, !dbg !35
+ %4 = trunc i64 %3 to i32, !dbg !36
+ ret i32 %4, !dbg !37
+}
+
+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}
+!llvm.errno.tbaa = !{!10}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C11, file: !1, producer: "clang version 22.0.0git (git at github.com:yonghong-song/llvm-project.git 2bb68bb783927bdc2b54e64aea1b78ba598a3349)", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None)
+!1 = !DIFile(filename: "test1.c", directory: "/home/yhs/tests/sig-change/struct16B", checksumkind: CSK_MD5, checksum: "c01b6b6ca539b790114bf7472cfb761a")
+!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 2bb68bb783927bdc2b54e64aea1b78ba598a3349)"}
+!10 = !{!11, !11, i64 0}
+!11 = !{!"int", !12, i64 0}
+!12 = !{!"omnipotent char", !13, i64 0}
+!13 = !{!"Simple C/C++ TBAA"}
+!14 = distinct !DISubprogram(name: "bar", scope: !1, file: !1, line: 3, type: !15, scopeLine: 3, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !23, keyInstructions: true)
+!15 = !DISubroutineType(types: !16)
+!16 = !{!17, !18}
+!17 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+!18 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "t", file: !1, line: 1, size: 128, elements: !19)
+!19 = !{!20, !22}
+!20 = !DIDerivedType(tag: DW_TAG_member, name: "a", scope: !18, file: !1, line: 1, baseType: !21, size: 64)
+!21 = !DIBasicType(name: "long", size: 64, encoding: DW_ATE_signed)
+!22 = !DIDerivedType(tag: DW_TAG_member, name: "b", scope: !18, file: !1, line: 1, baseType: !21, size: 64, offset: 64)
+!23 = !{!24}
+!24 = !DILocalVariable(name: "arg", arg: 1, scope: !14, file: !1, line: 3, type: !18)
+!25 = !DILocation(line: 0, scope: !14)
+!26 = !DILocation(line: 4, column: 10, scope: !14, atomGroup: 1, atomRank: 2)
+!27 = !DILocation(line: 4, column: 3, scope: !14, atomGroup: 1, atomRank: 1)
+!28 = distinct !DISubprogram(name: "foo", scope: !1, file: !1, line: 2, type: !29, scopeLine: 2, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagLocalToUnit | DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !31, keyInstructions: true)
+!29 = !DISubroutineType(cc: DW_CC_nocall, types: !30)
+!30 = !{!17, !18, !17}
+!31 = !{!32, !33}
+!32 = !DILocalVariable(name: "arg", arg: 1, scope: !28, file: !1, line: 2, type: !18)
+!33 = !DILocalVariable(name: "a", arg: 2, scope: !28, file: !1, line: 2, type: !17)
+!34 = !DILocation(line: 0, scope: !28)
+!35 = !DILocation(line: 2, column: 78, scope: !28, atomGroup: 1, atomRank: 3)
+!36 = !DILocation(line: 2, column: 72, scope: !28, atomGroup: 1, atomRank: 2)
+!37 = !DILocation(line: 2, column: 65, scope: !28, atomGroup: 1, atomRank: 1)
+
+; DWARF: DW_TAG_inlined_subroutine
+; DWARF-NEXT: DW_AT_name ("foo")
+; DWARF-NEXT: DW_AT_type
+; DWARF-SAME: "int"
+; DWARF-NEXT: DW_AT_artificial (true)
+; DWARF-NEXT: DW_AT_specification
+; DWARF-SAME: "foo"
+; DWARF-NEXT: {{^$}}
+; DWARF-NEXT: DW_TAG_formal_parameter
+; DWARF-NEXT: DW_AT_name ("a")
+; DWARF-NEXT: DW_AT_type
+; DWARF-SAME: "long"
+; DWARF-NEXT: {{^$}}
+; DWARF-NEXT: DW_TAG_formal_parameter
+; DWARF-NEXT: DW_AT_name ("b")
+; DWARF-NEXT: DW_AT_type
+; DWARF-SAME: "long"
+; DWARF-NEXT: {{^$}}
+; DWARF-NEXT: NULL
diff --git a/llvm/test/Transforms/Util/changed-func-dbg-struct-16B.ll b/llvm/test/Transforms/Util/changed-func-dbg-struct-16B.ll
new file mode 100644
index 0000000000000..5db1a1bbbd5b7
--- /dev/null
+++ b/llvm/test/Transforms/Util/changed-func-dbg-struct-16B.ll
@@ -0,0 +1,71 @@
+; 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 -enable-changed-func-dbinfo < %s | FileCheck %s
+
+; Source code:
+; // clang -O2 -S -emit-llvm -g test1.c
+; 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 [[DBG12:![0-9]+]] {
+; CHECK-NEXT: #dbg_value(i64 [[TMP0]], [[META21:![0-9]+]], !DIExpression(DW_OP_LLVM_fragment, 0, 64), [[META22:![0-9]+]])
+; CHECK-NEXT: #dbg_value(i64 [[TMP1]], [[META21]], !DIExpression(DW_OP_LLVM_fragment, 64, 64), [[META22]])
+; CHECK-NEXT: [[TMP3:%.*]] = mul nsw i64 [[TMP1]], [[TMP0]], !dbg [[DBG23:![0-9]+]]
+; CHECK-NEXT: ret i64 [[TMP3]], !dbg [[DBG24:![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: [[DBG12]] = distinct !DISubprogram(name: "foo", scope: [[META1]], file: [[META1]], line: 2, type: [[META13:![0-9]+]], scopeLine: 2, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: [[META0]], retainedNodes: [[META20:![0-9]+]], keyInstructions: true)
+; CHECK: [[META13]] = !DISubroutineType(types: [[META14:![0-9]+]])
+; CHECK: [[META14]] = !{[[META15:![0-9]+]], [[META16:![0-9]+]]}
+; CHECK: [[META15]] = !DIBasicType(name: "long", size: 64, encoding: DW_ATE_signed)
+; CHECK: [[META16]] = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "t", file: [[META1]], line: 1, size: 128, elements: [[META17:![0-9]+]])
+; CHECK: [[META17]] = !{[[META18:![0-9]+]], [[META19:![0-9]+]]}
+; CHECK: [[META18]] = !DIDerivedType(tag: DW_TAG_member, name: "a", scope: [[META16]], file: [[META1]], line: 1, baseType: [[META15]], size: 64)
+; CHECK: [[META19]] = !DIDerivedType(tag: DW_TAG_member, name: "b", scope: [[META16]], file: [[META1]], line: 1, baseType: [[META15]], size: 64, offset: 64)
+; CHECK: [[META20]] = !{[[META21]]}
+; CHECK: [[META21]] = !DILocalVariable(name: "arg", arg: 1, scope: [[DBG12]], file: [[META1]], line: 2, type: [[META16]])
+; CHECK: [[META22]] = !DILocation(line: 0, scope: [[DBG12]])
+; CHECK: [[DBG23]] = !DILocation(line: 3, column: 16, scope: [[DBG12]], atomGroup: 1, atomRank: 2)
+; CHECK: [[DBG24]] = !DILocation(line: 3, column: 3, scope: [[DBG12]], atomGroup: 1, atomRank: 1)
+;.
diff --git a/llvm/test/Transforms/Util/changed-func-dbg-struct-large-dwarf.ll b/llvm/test/Transforms/Util/changed-func-dbg-struct-large-dwarf.ll
new file mode 100644
index 0000000000000..19b356ebbcf11
--- /dev/null
+++ b/llvm/test/Transforms/Util/changed-func-dbg-struct-large-dwarf.ll
@@ -0,0 +1,112 @@
+; Check the generated DWARF debug info:
+; RUN: opt -S -mtriple=x86_64-unknown-unknown -passes=emit-changed-func-debuginfo -enable-changed-func-dbinfo < %s \
+; RUN: | %llc_dwarf -filetype=obj -o - \
+; RUN: | llvm-dwarfdump - | FileCheck %s --check-prefix=DWARF
+;
+; REQUIRES: debug_frame
+; REQUIRES: object-emission
+
+; Source code:
+; // clang -O2 -S -emit-llvm -g test2.c
+; struct t { long a; long b; long c; };
+; __attribute__((noinline)) static int foo(struct t arg, int a) { return arg.a * arg.b * arg.c; }
+; int bar(struct t arg) {
+; return foo(arg, 1);
+; }
+
+%struct.t = type { i64, i64, i64 }
+
+; Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(argmem: read) uwtable
+define dso_local i32 @bar(ptr noundef readonly byval(%struct.t) align 8 captures(none) %0) local_unnamed_addr #0 !dbg !14 {
+ #dbg_declare(ptr %0, !25, !DIExpression(), !26)
+ %2 = tail call fastcc i32 @foo(ptr noundef nonnull byval(%struct.t) align 8 %0), !dbg !27
+ ret i32 %2, !dbg !28
+}
+
+; Function Attrs: mustprogress nofree noinline norecurse nosync nounwind willreturn memory(argmem: read) uwtable
+define internal fastcc i32 @foo(ptr noundef readonly byval(%struct.t) align 8 captures(none) %0) unnamed_addr #1 !dbg !29 {
+ #dbg_declare(ptr %0, !33, !DIExpression(), !35)
+ #dbg_value(i32 poison, !34, !DIExpression(), !36)
+ %2 = load i64, ptr %0, align 8, !dbg !37, !tbaa !38
+ %3 = getelementptr inbounds nuw i8, ptr %0, i64 8, !dbg !41
+ %4 = load i64, ptr %3, align 8, !dbg !41, !tbaa !42
+ %5 = mul nsw i64 %4, %2, !dbg !43
+ %6 = getelementptr inbounds nuw i8, ptr %0, i64 16, !dbg !44
+ %7 = load i64, ptr %6, align 8, !dbg !44, !tbaa !45
+ %8 = mul nsw i64 %5, %7, !dbg !46
+ %9 = trunc i64 %8 to i32, !dbg !47
+ ret i32 %9, !dbg !48
+}
+
+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" }
+attributes #1 = { mustprogress nofree noinline 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, !8}
+!llvm.ident = !{!9}
+!llvm.errno.tbaa = !{!10}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C11, file: !1, producer: "clang version 22.0.0git (git at github.com:yonghong-song/llvm-project.git 2bb68bb783927bdc2b54e64aea1b78ba598a3349)", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None)
+!1 = !DIFile(filename: "test2.c", directory: "/home/yhs/tests/sig-change/struct16B", checksumkind: CSK_MD5, checksum: "70265912cbcea4a3b09ee4c21d26b33e")
+!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 2bb68bb783927bdc2b54e64aea1b78ba598a3349)"}
+!10 = !{!11, !11, i64 0}
+!11 = !{!"int", !12, i64 0}
+!12 = !{!"omnipotent char", !13, i64 0}
+!13 = !{!"Simple C/C++ TBAA"}
+!14 = distinct !DISubprogram(name: "bar", scope: !1, file: !1, line: 3, type: !15, scopeLine: 3, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !24, keyInstructions: true)
+!15 = !DISubroutineType(types: !16)
+!16 = !{!17, !18}
+!17 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+!18 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "t", file: !1, line: 1, size: 192, elements: !19)
+!19 = !{!20, !22, !23}
+!20 = !DIDerivedType(tag: DW_TAG_member, name: "a", scope: !18, file: !1, line: 1, baseType: !21, size: 64)
+!21 = !DIBasicType(name: "long", size: 64, encoding: DW_ATE_signed)
+!22 = !DIDerivedType(tag: DW_TAG_member, name: "b", scope: !18, file: !1, line: 1, baseType: !21, size: 64, offset: 64)
+!23 = !DIDerivedType(tag: DW_TAG_member, name: "c", scope: !18, file: !1, line: 1, baseType: !21, size: 64, offset: 128)
+!24 = !{!25}
+!25 = !DILocalVariable(name: "arg", arg: 1, scope: !14, file: !1, line: 3, type: !18)
+!26 = !DILocation(line: 3, column: 18, scope: !14)
+!27 = !DILocation(line: 4, column: 10, scope: !14, atomGroup: 1, atomRank: 2)
+!28 = !DILocation(line: 4, column: 3, scope: !14, atomGroup: 1, atomRank: 1)
+!29 = distinct !DISubprogram(name: "foo", scope: !1, file: !1, line: 2, type: !30, scopeLine: 2, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagLocalToUnit | DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !32, keyInstructions: true)
+!30 = !DISubroutineType(cc: DW_CC_nocall, types: !31)
+!31 = !{!17, !18, !17}
+!32 = !{!33, !34}
+!33 = !DILocalVariable(name: "arg", arg: 1, scope: !29, file: !1, line: 2, type: !18)
+!34 = !DILocalVariable(name: "a", arg: 2, scope: !29, file: !1, line: 2, type: !17)
+!35 = !DILocation(line: 2, column: 51, scope: !29)
+!36 = !DILocation(line: 0, scope: !29)
+!37 = !DILocation(line: 2, column: 76, scope: !29)
+!38 = !{!39, !40, i64 0}
+!39 = !{!"t", !40, i64 0, !40, i64 8, !40, i64 16}
+!40 = !{!"long", !12, i64 0}
+!41 = !DILocation(line: 2, column: 84, scope: !29)
+!42 = !{!39, !40, i64 8}
+!43 = !DILocation(line: 2, column: 78, scope: !29)
+!44 = !DILocation(line: 2, column: 92, scope: !29)
+!45 = !{!39, !40, i64 16}
+!46 = !DILocation(line: 2, column: 86, scope: !29, atomGroup: 1, atomRank: 3)
+!47 = !DILocation(line: 2, column: 72, scope: !29, atomGroup: 1, atomRank: 2)
+!48 = !DILocation(line: 2, column: 65, scope: !29, atomGroup: 1, atomRank: 1)
+
+; DWARF: DW_TAG_inlined_subroutine
+; DWARF-NEXT: DW_AT_name ("foo")
+; DWARF-NEXT: DW_AT_type
+; DWARF-SAME: "int"
+; DWARF-NEXT: DW_AT_artificial (true)
+; DWARF-NEXT: DW_AT_specification
+; DWARF-SAME: "foo"
+; DWARF-NEXT: {{^$}}
+; DWARF-NEXT: DW_TAG_formal_parameter
+; DWARF-NEXT: DW_AT_name ("arg")
+; DWARF-NEXT: DW_AT_type
+; DWARF-SAME: "t"
+; DWARF-NEXT: {{^$}}
+; DWARF-NEXT: NULL
diff --git a/llvm/test/Transforms/Util/changed-func-dbg-struct-large.ll b/llvm/test/Transforms/Util/changed-func-dbg-struct-large.ll
new file mode 100644
index 0000000000000..9a1f16db7a907
--- /dev/null
+++ b/llvm/test/Transforms/Util/changed-func-dbg-struct-large.ll
@@ -0,0 +1,94 @@
+; 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 -enable-changed-func-dbinfo < %s | FileCheck %s
+
+; Source code:
+; // clang -O2 -S -emit-llvm -g test2.c
+; 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 [[DBG11:![0-9]+]] {
+; CHECK-NEXT: #dbg_declare(ptr [[TMP0]], [[META21:![0-9]+]], !DIExpression(), [[META22:![0-9]+]])
+; CHECK-NEXT: [[TMP2:%.*]] = load i64, ptr [[TMP0]], align 8, !dbg [[DBG23:![0-9]+]], !tbaa [[LONG_TBAA24:![0-9]+]]
+; CHECK-NEXT: [[TMP3:%.*]] = getelementptr inbounds nuw i8, ptr [[TMP0]], i64 16, !dbg [[DBG29:![0-9]+]]
+; CHECK-NEXT: [[TMP4:%.*]] = load i64, ptr [[TMP3]], align 8, !dbg [[DBG29]], !tbaa [[LONG_TBAA30:![0-9]+]]
+; CHECK-NEXT: [[TMP5:%.*]] = mul nsw i64 [[TMP4]], [[TMP2]], !dbg [[DBG31:![0-9]+]]
+; CHECK-NEXT: ret i64 [[TMP5]], !dbg [[DBG32:![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: [[DBG11]] = distinct !DISubprogram(name: "foo", scope: [[META1]], file: [[META1]], line: 2, type: [[META12:![0-9]+]], scopeLine: 2, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: [[META0]], retainedNodes: [[META20:![0-9]+]], keyInstructions: true)
+; CHECK: [[META12]] = !DISubroutineType(types: [[META13:![0-9]+]])
+; CHECK: [[META13]] = !{[[META14:![0-9]+]], [[META15:![0-9]+]]}
+; CHECK: [[META14]] = !DIBasicType(name: "long", size: 64, encoding: DW_ATE_signed)
+; CHECK: [[META15]] = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "t", file: [[META1]], line: 1, size: 192, elements: [[META16:![0-9]+]])
+; CHECK: [[META16]] = !{[[META17:![0-9]+]], [[META18:![0-9]+]], [[META19:![0-9]+]]}
+; CHECK: [[META17]] = !DIDerivedType(tag: DW_TAG_member, name: "a", scope: [[META15]], file: [[META1]], line: 1, baseType: [[META14]], size: 64)
+; CHECK: [[META18]] = !DIDerivedType(tag: DW_TAG_member, name: "b", scope: [[META15]], file: [[META1]], line: 1, baseType: [[META14]], size: 64, offset: 64)
+; CHECK: [[META19]] = !DIDerivedType(tag: DW_TAG_member, name: "c", scope: [[META15]], file: [[META1]], line: 1, baseType: [[META14]], size: 64, offset: 128)
+; CHECK: [[META20]] = !{[[META21]]}
+; CHECK: [[META21]] = !DILocalVariable(name: "arg", arg: 1, scope: [[DBG11]], file: [[META1]], line: 2, type: [[META15]])
+; CHECK: [[META22]] = !DILocation(line: 2, column: 19, scope: [[DBG11]])
+; CHECK: [[DBG23]] = !DILocation(line: 3, column: 14, scope: [[DBG11]])
+; CHECK: [[LONG_TBAA24]] = !{[[META25:![0-9]+]], [[META26:![0-9]+]], i64 0}
+; CHECK: [[META25]] = !{!"t", [[META26]], i64 0, [[META26]], i64 8, [[META26]], i64 16}
+; CHECK: [[META26]] = !{!"long", [[META27:![0-9]+]], i64 0}
+; CHECK: [[META27]] = !{!"omnipotent char", [[META28:![0-9]+]], i64 0}
+; CHECK: [[META28]] = !{!"Simple C/C++ TBAA"}
+; CHECK: [[DBG29]] = !DILocation(line: 3, column: 22, scope: [[DBG11]])
+; CHECK: [[LONG_TBAA30]] = !{[[META25]], [[META26]], i64 16}
+; CHECK: [[DBG31]] = !DILocation(line: 3, column: 16, scope: [[DBG11]], atomGroup: 1, atomRank: 2)
+; CHECK: [[DBG32]] = !DILocation(line: 3, column: 3, scope: [[DBG11]], atomGroup: 1, atomRank: 1)
+;.
More information about the llvm-commits
mailing list