[llvm] [RFC] Emit dwarf data for signature-changed or new functions (PR #157349)
via llvm-commits
llvm-commits at lists.llvm.org
Thu Oct 2 22:57:01 PDT 2025
https://github.com/yonghong-song updated https://github.com/llvm/llvm-project/pull/157349
>From 6b72b03ea2c6fca18a536c49be875a16b9abcf4d Mon Sep 17 00:00:00 2001
From: Yonghong Song <yonghong.song at linux.dev>
Date: Sat, 6 Sep 2025 23:09:01 -0700
Subject: [PATCH 1/3] [ArgPromotion] Add DW_CC_nocall to DISubprogram
ArgumentPromotion pass may change function signatures. If this happens
and debuginfo is enabled, let us add DW_CC_nocall to debuginfo so it is
clear that the function signature has changed.
DeadArgumentElimination ([1]) has similar implementation.
Also fix an ArgumentPromotion test due to adding DW_CC_nocall to
debuginfo.
[1] https://github.com/llvm/llvm-project/commit/340b0ca90095d838f095271aaa1098fa1bd5ecbe
---
llvm/lib/Transforms/IPO/ArgumentPromotion.cpp | 11 +++++++++++
llvm/test/Transforms/ArgumentPromotion/dbg.ll | 6 +++++-
2 files changed, 16 insertions(+), 1 deletion(-)
diff --git a/llvm/lib/Transforms/IPO/ArgumentPromotion.cpp b/llvm/lib/Transforms/IPO/ArgumentPromotion.cpp
index 262c902d40d2d..87b0d069ec04e 100644
--- a/llvm/lib/Transforms/IPO/ArgumentPromotion.cpp
+++ b/llvm/lib/Transforms/IPO/ArgumentPromotion.cpp
@@ -50,6 +50,7 @@
#include "llvm/IR/BasicBlock.h"
#include "llvm/IR/CFG.h"
#include "llvm/IR/Constants.h"
+#include "llvm/IR/DIBuilder.h"
#include "llvm/IR/DataLayout.h"
#include "llvm/IR/DerivedTypes.h"
#include "llvm/IR/Dominators.h"
@@ -432,6 +433,16 @@ doPromotion(Function *F, FunctionAnalysisManager &FAM,
PromoteMemToReg(Allocas, DT, &AC);
}
+ // If argument(s) are dead (hence removed) or promoted, probably the function
+ // does not follow standard calling convention anymore. Add DW_CC_nocall to
+ // DISubroutineType to inform debugger that it may not be safe to call this
+ // function.
+ DISubprogram *SP = NF->getSubprogram();
+ if (SP) {
+ auto Temp = SP->getType()->cloneWithCC(llvm::dwarf::DW_CC_nocall);
+ SP->replaceType(MDNode::replaceWithPermanent(std::move(Temp)));
+ }
+
return NF;
}
diff --git a/llvm/test/Transforms/ArgumentPromotion/dbg.ll b/llvm/test/Transforms/ArgumentPromotion/dbg.ll
index 6a14facfb36a2..ce86aaa3884de 100644
--- a/llvm/test/Transforms/ArgumentPromotion/dbg.ll
+++ b/llvm/test/Transforms/ArgumentPromotion/dbg.ll
@@ -53,7 +53,11 @@ define void @caller(ptr %Y, ptr %P) {
!0 = !{i32 2, !"Debug Info Version", i32 3}
!1 = !DILocation(line: 8, scope: !2)
-!2 = distinct !DISubprogram(name: "test", file: !5, line: 3, isLocal: true, isDefinition: true, virtualIndex: 6, flags: DIFlagPrototyped, isOptimized: false, unit: !3, scopeLine: 3, scope: null)
+!2 = distinct !DISubprogram(name: "test", file: !5, line: 3, type: !7, isLocal: true, isDefinition: true, flags: DIFlagPrototyped, isOptimized: false, unit: !3, scopeLine: 3, scope: null)
!3 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus, producer: "clang version 3.5.0 ", isOptimized: false, emissionKind: LineTablesOnly, file: !5)
!5 = !DIFile(filename: "test.c", directory: "")
!6 = !DILocation(line: 9, scope: !2)
+!7 = !DISubroutineType(types: !8)
+!8 = !{null, !9}
+!9 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !10)
+!10 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
>From 0a87115173804d3f2eed94986b4fa0010ac68d8a Mon Sep 17 00:00:00 2001
From: Yonghong Song <yonghong.song at linux.dev>
Date: Thu, 18 Sep 2025 12:37:15 -0700
Subject: [PATCH 2/3] [SampleProfile][Test] Fix DISubroutineType debuginfo for
two tests
During development of emitting dwarf data for signature-changed or new
functions, I found two test failures
llvm/test/Transforms/SampleProfile/ctxsplit.ll
llvm/test/Transforms/SampleProfile/flattened.ll
due to incorrect DISubroutineType(s). This patch fixed the issue with
proper types.
---
llvm/test/Transforms/SampleProfile/ctxsplit.ll | 8 +++++---
llvm/test/Transforms/SampleProfile/flattened.ll | 5 ++++-
2 files changed, 9 insertions(+), 4 deletions(-)
diff --git a/llvm/test/Transforms/SampleProfile/ctxsplit.ll b/llvm/test/Transforms/SampleProfile/ctxsplit.ll
index 46e088a63e941..4acd8b5861c14 100644
--- a/llvm/test/Transforms/SampleProfile/ctxsplit.ll
+++ b/llvm/test/Transforms/SampleProfile/ctxsplit.ll
@@ -51,9 +51,11 @@ attributes #0 = { "use-sample-profile" }
!4 = !{i32 2, !"Debug Info Version", i32 3}
!5 = !{i32 1, !"wchar_size", i32 4}
!6 = !{!"clang version 8.0.0 (trunk 345241)"}
-!7 = distinct !DISubprogram(name: "foo", scope: !1, file: !1, line: 1, type: !8, isLocal: false, isDefinition: true, scopeLine: 1, isOptimized: true, unit: !0, retainedNodes: !2)
+!7 = distinct !DISubprogram(name: "foo", scope: !1, file: !1, line: 1, type: !12, isLocal: false, isDefinition: true, scopeLine: 1, isOptimized: true, unit: !0, retainedNodes: !2)
!8 = !DISubroutineType(types: !2)
!9 = !DILocation(line: 2, column: 3, scope: !7)
-!10 = distinct !DISubprogram(name: "goo", scope: !1, file: !1, line: 8, type: !8, isLocal: false, isDefinition: true, scopeLine: 8, isOptimized: true, unit: !0, retainedNodes: !2)
+!10 = distinct !DISubprogram(name: "goo", scope: !1, file: !1, line: 8, type: !12, isLocal: false, isDefinition: true, scopeLine: 8, isOptimized: true, unit: !0, retainedNodes: !2)
!11 = !DILocation(line: 10, column: 3, scope: !10)
-
+!12 = !DISubroutineType(types: !13)
+!13 = !{!14}
+!14 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
diff --git a/llvm/test/Transforms/SampleProfile/flattened.ll b/llvm/test/Transforms/SampleProfile/flattened.ll
index acc6459c99a33..e92264f693bb8 100644
--- a/llvm/test/Transforms/SampleProfile/flattened.ll
+++ b/llvm/test/Transforms/SampleProfile/flattened.ll
@@ -31,6 +31,9 @@ entry:
!4 = !{i32 2, !"Debug Info Version", i32 3}
!5 = !{i32 1, !"wchar_size", i32 4}
!6 = !{!"clang version 8.0.0 (trunk 345241)"}
-!7 = distinct !DISubprogram(name: "foo", scope: !1, file: !1, line: 1, type: !8, isLocal: false, isDefinition: true, scopeLine: 1, isOptimized: true, unit: !0, retainedNodes: !2)
+!7 = distinct !DISubprogram(name: "foo", scope: !1, file: !1, line: 1, type: !10, isLocal: false, isDefinition: true, scopeLine: 1, isOptimized: true, unit: !0, retainedNodes: !2)
!8 = !DISubroutineType(types: !2)
!9 = !DILocation(line: 2, column: 3, scope: !7)
+!10 = !DISubroutineType(types: !11)
+!11 = !{!12}
+!12 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
>From d33661d1f78531bab47c3fffd93a1be16b656ad1 Mon Sep 17 00:00:00 2001
From: Yonghong Song <yonghong.song at linux.dev>
Date: Tue, 29 Jul 2025 16:29:44 -0700
Subject: [PATCH 3/3] [LLVM] Emit dwarf data for changed-signature and new
functions
Add a new pass EmitChangedFuncDebugInfo which will add dwarf for
additional functions including functions with signature change
and new functions.
The previous approach in [1] tries to add debuginfo for those
optimization passes which cause signature changes. Based on
discussion in [1], it is preferred to have a specific pass to
add debuginfo and later on dwarf generation can include those
new debuginfo.
The following is an example:
Source:
$ cat test.c
struct t { int a; };
char *tar(struct t *a, struct t *d);
__attribute__((noinline)) static char * foo(struct t *a, struct t *d, int b)
{
return tar(a, d);
}
char *bar(struct t *a, struct t *d)
{
return foo(a, d, 1);
}
Compiled and dump dwarf with:
clang -O2 -c -g test.c
llvm-dwarfdump test.o
0x0000005c: DW_TAG_subprogram
DW_AT_low_pc (0x0000000000000010)
DW_AT_high_pc (0x0000000000000015)
DW_AT_frame_base (DW_OP_reg7 RSP)
DW_AT_linkage_name ("foo")
DW_AT_name ("foo")
DW_AT_decl_file ("/home/yhs/tests/sig-change/deadarg/test.c")
DW_AT_decl_line (3)
DW_AT_type (0x000000bb "char *")
DW_AT_artificial (true)
DW_AT_external (true)
0x0000006c: DW_TAG_formal_parameter
DW_AT_location (DW_OP_reg5 RDI)
DW_AT_decl_file ("/home/yhs/tests/sig-change/deadarg/test.c")
DW_AT_decl_line (3)
DW_AT_type (0x000000c4 "t *")
0x00000075: DW_TAG_formal_parameter
DW_AT_location (DW_OP_reg4 RSI)
DW_AT_decl_file ("/home/yhs/tests/sig-change/deadarg/test.c")
DW_AT_decl_line (3)
DW_AT_type (0x000000c4 "t *")
0x0000007e: DW_TAG_inlined_subroutine
DW_AT_abstract_origin (0x0000009a "foo")
DW_AT_low_pc (0x0000000000000010)
DW_AT_high_pc (0x0000000000000015)
DW_AT_call_file ("/home/yhs/tests/sig-change/deadarg/test.c")
DW_AT_call_line (0)
0x0000008a: DW_TAG_formal_parameter
DW_AT_location (DW_OP_reg5 RDI)
DW_AT_abstract_origin (0x000000a2 "a")
0x00000091: DW_TAG_formal_parameter
DW_AT_location (DW_OP_reg4 RSI)
DW_AT_abstract_origin (0x000000aa "d")
0x00000098: NULL
0x00000099: NULL
0x0000009a: DW_TAG_subprogram
DW_AT_name ("foo")
DW_AT_decl_file ("/home/yhs/tests/sig-change/deadarg/test.c")
DW_AT_decl_line (3)
DW_AT_prototyped (true)
DW_AT_type (0x000000bb "char *")
DW_AT_inline (DW_INL_inlined)
0x000000a2: DW_TAG_formal_parameter
DW_AT_name ("a")
DW_AT_decl_file ("/home/yhs/tests/sig-change/deadarg/test.c")
DW_AT_decl_line (3)
DW_AT_type (0x000000c4 "t *")
0x000000aa: DW_TAG_formal_parameter
DW_AT_name ("d")
DW_AT_decl_file ("/home/yhs/tests/sig-change/deadarg/test.c")
DW_AT_decl_line (3)
DW_AT_type (0x000000c4 "t *")
0x000000b2: DW_TAG_formal_parameter
DW_AT_name ("b")
DW_AT_decl_file ("/home/yhs/tests/sig-change/deadarg/test.c")
DW_AT_decl_line (3)
DW_AT_type (0x000000d8 "int")
0x000000ba: NULL
There are some restrictions in the current implementation:
- Only C language is supported
- BPF target is excluded as one of main goals for this pull request
is to generate proper vmlinux BTF for arch's like x86_64/arm64 etc.
- Function must not be a intrinsic, decl only, return value size more
than arch register size and func with variable arguments.
- Missed flag to turn off this feature and missed some dbg info (e.g.
argument cannot be easily retrieved from dbg_value etc.).
- Currently, some functions (e.g. foo.llvm.<hash>) do not change
signatures but the current implementation still marks them
as artificial. We might be able to avoid DW_TAG_inlined_subroutine
for these routines.
I have tested this patch set by building latest bpf-next linux kernel.
For no-lto case:
65341 original number of functions
1085 new functions with this patch
For thin-lto case:
65595 original number of functions
2492 new functions with this patch
[1] https://github.com/llvm/llvm-project/pull/127855
---
.../Utils/EmitChangedFuncDebugInfo.h | 33 ++
llvm/lib/Passes/PassBuilder.cpp | 1 +
llvm/lib/Passes/PassBuilderPipelines.cpp | 8 +-
llvm/lib/Passes/PassRegistry.def | 1 +
llvm/lib/Transforms/Utils/CMakeLists.txt | 1 +
.../Utils/EmitChangedFuncDebugInfo.cpp | 467 ++++++++++++++++++
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 +
10 files changed, 514 insertions(+), 2 deletions(-)
create mode 100644 llvm/include/llvm/Transforms/Utils/EmitChangedFuncDebugInfo.h
create mode 100644 llvm/lib/Transforms/Utils/EmitChangedFuncDebugInfo.cpp
diff --git a/llvm/include/llvm/Transforms/Utils/EmitChangedFuncDebugInfo.h b/llvm/include/llvm/Transforms/Utils/EmitChangedFuncDebugInfo.h
new file mode 100644
index 0000000000000..8d569cd95d7f7
--- /dev/null
+++ b/llvm/include/llvm/Transforms/Utils/EmitChangedFuncDebugInfo.h
@@ -0,0 +1,33 @@
+//===- EmitChangedFuncDebugInfo.h - Emit Additional Debug Info -*- C++ --*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+/// \file
+/// Emit debug info for changed or new funcs.
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_TRANSFORMS_UTILS_EMITCHANGEDFUNCDEBUGINFO_H
+#define LLVM_TRANSFORMS_UTILS_EMITCHANGEDFUNCDEBUGINFO_H
+
+#include "llvm/IR/PassManager.h"
+
+namespace llvm {
+
+class Module;
+
+// Pass that emits late dwarf.
+class EmitChangedFuncDebugInfoPass
+ : public PassInfoMixin<EmitChangedFuncDebugInfoPass> {
+public:
+ EmitChangedFuncDebugInfoPass() = default;
+
+ PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM);
+};
+
+} // end namespace llvm
+
+#endif // LLVM_TRANSFORMS_UTILS_EMITCHANGEDFUNCDEBUGINFO_H
diff --git a/llvm/lib/Passes/PassBuilder.cpp b/llvm/lib/Passes/PassBuilder.cpp
index c234623caecf9..a4013d88c7ad1 100644
--- a/llvm/lib/Passes/PassBuilder.cpp
+++ b/llvm/lib/Passes/PassBuilder.cpp
@@ -350,6 +350,7 @@
#include "llvm/Transforms/Utils/DXILUpgrade.h"
#include "llvm/Transforms/Utils/Debugify.h"
#include "llvm/Transforms/Utils/DeclareRuntimeLibcalls.h"
+#include "llvm/Transforms/Utils/EmitChangedFuncDebugInfo.h"
#include "llvm/Transforms/Utils/EntryExitInstrumenter.h"
#include "llvm/Transforms/Utils/FixIrreducible.h"
#include "llvm/Transforms/Utils/HelloWorld.h"
diff --git a/llvm/lib/Passes/PassBuilderPipelines.cpp b/llvm/lib/Passes/PassBuilderPipelines.cpp
index 7069e8d67c2f1..1c15e497001dc 100644
--- a/llvm/lib/Passes/PassBuilderPipelines.cpp
+++ b/llvm/lib/Passes/PassBuilderPipelines.cpp
@@ -135,6 +135,7 @@
#include "llvm/Transforms/Utils/AssumeBundleBuilder.h"
#include "llvm/Transforms/Utils/CanonicalizeAliases.h"
#include "llvm/Transforms/Utils/CountVisits.h"
+#include "llvm/Transforms/Utils/EmitChangedFuncDebugInfo.h"
#include "llvm/Transforms/Utils/EntryExitInstrumenter.h"
#include "llvm/Transforms/Utils/ExtraPassManager.h"
#include "llvm/Transforms/Utils/InjectTLIMappings.h"
@@ -1640,9 +1641,12 @@ PassBuilder::buildModuleOptimizationPipeline(OptimizationLevel Level,
if (PTO.CallGraphProfile && !LTOPreLink)
MPM.addPass(CGProfilePass(isLTOPostLink(LTOPhase)));
- // RelLookupTableConverterPass runs later in LTO post-link pipeline.
- if (!LTOPreLink)
+ // RelLookupTableConverterPass and EmitChangedFuncDebugInfoPass run later in
+ // LTO post-link pipeline.
+ if (!LTOPreLink) {
MPM.addPass(RelLookupTableConverterPass());
+ MPM.addPass(EmitChangedFuncDebugInfoPass());
+ }
return MPM;
}
diff --git a/llvm/lib/Passes/PassRegistry.def b/llvm/lib/Passes/PassRegistry.def
index f0e7d36f78aab..a74513e8e9efd 100644
--- a/llvm/lib/Passes/PassRegistry.def
+++ b/llvm/lib/Passes/PassRegistry.def
@@ -73,6 +73,7 @@ MODULE_PASS("debugify", NewPMDebugifyPass())
MODULE_PASS("declare-runtime-libcalls", DeclareRuntimeLibcallsPass())
MODULE_PASS("dfsan", DataFlowSanitizerPass())
MODULE_PASS("dot-callgraph", CallGraphDOTPrinterPass())
+MODULE_PASS("dwarf-emit-late", EmitChangedFuncDebugInfoPass())
MODULE_PASS("dxil-upgrade", DXILUpgradePass())
MODULE_PASS("elim-avail-extern", EliminateAvailableExternallyPass())
MODULE_PASS("extract-blocks", BlockExtractorPass({}, false))
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..5fae8a65a5446
--- /dev/null
+++ b/llvm/lib/Transforms/Utils/EmitChangedFuncDebugInfo.cpp
@@ -0,0 +1,467 @@
+//==- EmitChangedFuncDebugInfoPass - Emit Additional Debug Info -*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file implements emitting debug info for functions with changed
+// signatures or new functions.
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Transforms/Utils/EmitChangedFuncDebugInfo.h"
+#include "llvm/IR/DIBuilder.h"
+#include "llvm/IR/IRBuilder.h"
+#include "llvm/IR/IntrinsicInst.h"
+#include "llvm/IR/Module.h"
+#include "llvm/TargetParser/Triple.h"
+
+using namespace llvm;
+
+// A struct param breaks into two actual arguments like
+// static int count(struct user_arg_ptr argv, int max)
+// and the actual func signature:
+// i32 @count(i8 range(i8 0, 2) %argv.coerce0, ptr %argv.coerce1)
+// {
+// #dbg_value(i8 %argv.coerce0, !14759,
+// !DIExpression(DW_OP_LLVM_fragment, 0, 8), !14768)
+// #dbg_value(ptr %argv.coerce1, !14759,
+// !DIExpression(DW_OP_LLVM_fragment, 64, 64), !14768)
+// ...
+// }
+static DIType *getTypeFromExpr(DIBuilder &DIB, DIExpression *Expr,
+ DICompositeType *DTy) {
+ for (auto Op : Expr->expr_ops()) {
+ if (Op.getOp() != dwarf::DW_OP_LLVM_fragment)
+ continue;
+
+ uint64_t BitOffset = Op.getArg(0);
+ uint64_t BitSize = Op.getArg(1);
+
+ for (auto *Element : DTy->getElements()) {
+ auto Elem = cast<DIDerivedType>(Element);
+ if (Elem->getSizeInBits() == BitSize &&
+ Elem->getOffsetInBits() == BitOffset)
+ return Elem->getBaseType();
+ else
+ // Create a new int type. For example, original debuginfo is an array.
+ return DIB.createBasicType("int" + std::to_string(BitSize), BitSize,
+ dwarf::DW_ATE_signed);
+ }
+ }
+ return nullptr;
+}
+
+static bool getArg(Module &M, unsigned Idx, BasicBlock &FirstBB, DIBuilder &DIB,
+ Function *F, DISubprogram *OldSP, DISubprogram *NewSP,
+ SmallVector<Metadata *, 5> &TypeList,
+ SmallVector<Metadata *, 5> &ArgList,
+ unsigned PointerBitWidth) {
+ // LLVMContext &Ctx = M.getContext();
+ for (Instruction &I : FirstBB) {
+ for (DbgRecord &DR : I.getDbgRecordRange()) {
+ auto *DVR = dyn_cast<DbgVariableRecord>(&DR);
+ if (!DVR)
+ continue;
+ // All of DbgVariableRecord::LocationType::{Value,Assign,Declare}
+ // are covered.
+ Metadata *Loc = DVR->getRawLocation();
+ auto *ValueMDN = dyn_cast<ValueAsMetadata>(Loc);
+ if (!ValueMDN)
+ continue;
+
+ Value *MDNValue = ValueMDN->getValue();
+ if (!MDNValue)
+ continue;
+
+ Type *Ty = ValueMDN->getType();
+ auto *Var = DVR->getVariable();
+ if (!Var->getArg())
+ continue;
+
+ if (dyn_cast<AllocaInst>(MDNValue)) {
+ // A struct turned into a pointer to struct.
+ // @rhashtable_lookup_fast(ptr noundef %key,
+ // ptr noundef readonly byval(%struct.rhashtable_params)
+ // align 8 captures(none) %params) {
+ // ...
+ // %MyAlloca = alloca [160 x i8], align 32
+ // %0 = ptrtoint ptr %MyAlloca to i64
+ // %1 = add i64 %0, 32
+ // %2 = inttoptr i64 %1 to ptr
+ // ...
+ // call void @llvm.memcpy.p0.p0.i64(ptr align 8 %2, ptr align 8
+ // %params, i64 40, i1 false)
+ // #dbg_value(ptr @offdevs, !15308, !DIExpression(), !15312)
+ // #dbg_value(ptr %key, !15309, !DIExpression(), !15312)
+ // #dbg_declare(ptr %MyAlloca, !15310,
+ // !DIExpression(DW_OP_plus_uconst, 32), !15313)
+ // tail call void @__rcu_read_lock() #14, !dbg !15314
+ // }
+ if (Var->getName() != F->getArg(Idx)->getName())
+ continue;
+ } else if (MDNValue != F->getArg(Idx)) {
+ // Handle the following pattern:
+ // ... @vgacon_do_font_op(..., i32 noundef, i1 noundef zeroext %ch512)
+ // ... {
+ // ...
+ // #dbg_value(i32 %set, !8568, !DIExpression(), !8589)
+ // %storedv = zext i1 %ch512 to i8
+ // #dbg_value(i8 %storedv, !8569, !DIExpression(), !8589)
+ // ...
+ // }
+ Instruction *PrevI = I.getPrevNode();
+ if (!PrevI)
+ continue;
+ if (MDNValue != PrevI)
+ continue;
+ auto *ZExt = dyn_cast<ZExtInst>(PrevI);
+ if (!ZExt)
+ continue;
+ if (ZExt->getOperand(0) != F->getArg(Idx))
+ continue;
+ }
+
+ auto *Expr = DVR->getExpression();
+
+ // Strip modifiers (const, volatile, etc.)
+ DIType *DITy = Var->getType();
+ while (auto *DTy = dyn_cast<DIDerivedType>(DITy)) {
+ if (DTy->getTag() == dwarf::DW_TAG_pointer_type) {
+ DITy = DTy;
+ break;
+ }
+ DITy = DTy->getBaseType();
+ }
+
+ DIType *ParamType = Var->getType();
+ if (Ty->isIntegerTy()) {
+ if (auto *DTy = dyn_cast<DICompositeType>(DITy)) {
+ if (!Ty->isIntegerTy(DTy->getSizeInBits())) {
+ ParamType = getTypeFromExpr(DIB, Expr, DTy);
+ if (!ParamType)
+ return false;
+ }
+ }
+ } else if (Ty->isPointerTy()) {
+ if (dyn_cast<DICompositeType>(DITy)) {
+ ParamType = DIB.createPointerType(DITy, PointerBitWidth);
+ } else {
+ auto *DTy = dyn_cast<DIDerivedType>(DITy);
+ if (!DTy)
+ continue;
+ if (DTy->getTag() != dwarf::DW_TAG_pointer_type)
+ continue;
+ }
+ }
+
+ TypeList.push_back(ParamType);
+ Var = DIB.createParameterVariable(NewSP, F->getArg(Idx)->getName(),
+ Idx + 1, OldSP->getUnit()->getFile(),
+ OldSP->getLine(), ParamType);
+ ArgList.push_back(Var);
+ return true;
+ }
+ }
+
+ /* The parameter is not handled due to poison value, so just create a new type
+ */
+ Type *Ty = F->getArg(Idx)->getType();
+ unsigned IntBitWidth = 32;
+ if (Ty->isIntegerTy())
+ IntBitWidth = cast<IntegerType>(Ty)->getBitWidth();
+
+ DIType *ParamType = DIB.createBasicType("int" + std::to_string(IntBitWidth),
+ IntBitWidth, dwarf::DW_ATE_signed);
+ DILocalVariable *Var = DIB.createParameterVariable(
+ NewSP, F->getArg(Idx)->getName(), Idx + 1, OldSP->getUnit()->getFile(),
+ OldSP->getLine(), ParamType);
+ TypeList.push_back(ParamType);
+ ArgList.push_back(Var);
+ return true;
+}
+
+static bool getTypeArgList(Module &M, DIBuilder &DIB, Function *F,
+ DISubprogram *OldSP, DISubprogram *NewSP,
+ SmallVector<Metadata *, 5> &TypeList,
+ SmallVector<Metadata *, 5> &ArgList,
+ unsigned PointerBitWidth) {
+ FunctionType *FTy = F->getFunctionType();
+ Type *RetTy = FTy->getReturnType();
+ if (RetTy->isVoidTy()) {
+ // Void return type may be due to optimization.
+ TypeList.push_back(nullptr);
+ } else {
+ // Optimization does not change return type from one
+ // non-void type to another non-void type.
+ DITypeRefArray TyArray = OldSP->getType()->getTypeArray();
+ TypeList.push_back(TyArray[0]);
+ }
+
+ unsigned NumArgs = FTy->getNumParams();
+ if (!NumArgs)
+ return true;
+
+ BasicBlock &FirstBB = F->getEntryBlock();
+ for (unsigned i = 0; i < NumArgs; ++i) {
+ if (!getArg(M, i, FirstBB, DIB, F, OldSP, NewSP, TypeList, ArgList,
+ PointerBitWidth))
+ return false;
+ }
+
+ return true;
+}
+
+static Metadata *
+mapAllDILocs(Metadata *M,
+ std::function<const DILocation *(const DILocation *)> X,
+ LLVMContext &Ctx) {
+ if (!M)
+ return nullptr;
+
+ if (auto *DL = dyn_cast<DILocation>(M))
+ return const_cast<DILocation *>(X(DL));
+
+ if (auto *N = dyn_cast<MDNode>(M)) {
+ SmallVector<Metadata *, 8> NewOps;
+ NewOps.reserve(N->getNumOperands());
+ for (const MDOperand &Op : N->operands())
+ NewOps.push_back(mapAllDILocs(Op.get(), X, Ctx));
+ // Tag nodes need not be distinct.
+ return MDNode::get(Ctx, NewOps);
+ }
+
+ // MDString / ConstantAsMetadata / etc.
+ return M;
+}
+
+static MDNode *cloneLoopIDReplacingAllDILocs(
+ MDNode *OldLoopID, std::function<const DILocation *(const DILocation *)> X,
+ LLVMContext &Ctx) {
+ SmallVector<Metadata *, 8> Ops;
+ Ops.reserve(OldLoopID->getNumOperands());
+ Ops.push_back(nullptr); // placeholder for self
+
+ // Copy/transform operands 1..N (operand 0 is always the self reference)
+ for (unsigned i = 1, e = OldLoopID->getNumOperands(); i < e; ++i) {
+ Metadata *Old = OldLoopID->getOperand(i).get();
+ Ops.push_back(mapAllDILocs(Old, X, Ctx));
+ }
+
+ MDNode *NewLoopID = MDNode::getDistinct(Ctx, Ops);
+ NewLoopID->replaceOperandWith(0, NewLoopID); // self reference
+ return NewLoopID;
+}
+
+// For a particular function, we do the following three steps:
+// 1. Collect new signatures for the function.
+// 2. Go through all function body for all DILocations
+// add inlinedAt() for the new function.
+// 3. At the beginning of the function, add dbg_value
+// for all actual arguments.
+static void generateDebugInfo(Module &M, Function *F,
+ unsigned PointerBitWidth) {
+ DISubprogram *OldSP = F->getSubprogram();
+ DICompileUnit *CU = OldSP->getUnit();
+ DIBuilder DIB(M, /*AllowUnresolved=*/false, CU);
+
+ SmallVector<Metadata *, 5> TypeList;
+ SmallVector<Metadata *, 5> ArgList;
+
+ // Collect new signatures for the function.
+ DISubprogram *NewSP =
+ DIB.createFunction(OldSP->getScope(), // Scope
+ F->getName(), // Name
+ F->getName(), // Linkage name
+ CU->getFile(), // File
+ OldSP->getLine(), // Line
+ nullptr, // DISubroutineType
+ OldSP->getScopeLine(), // ScopeLine
+ DINode::FlagZero, DISubprogram::SPFlagDefinition);
+ NewSP = DIB.createArtificialSubprogram(NewSP);
+
+ bool Success = getTypeArgList(M, DIB, F, OldSP, NewSP, TypeList, ArgList,
+ PointerBitWidth);
+ if (!Success) {
+ DIB.finalize();
+ return;
+ }
+
+ DITypeRefArray DITypeArray = DIB.getOrCreateTypeArray(TypeList);
+ auto *SubroutineType = DIB.createSubroutineType(DITypeArray);
+ DINodeArray ArgArray = DIB.getOrCreateArray(ArgList);
+
+ NewSP->replaceType(SubroutineType);
+ NewSP->replaceRetainedNodes(ArgArray);
+
+ F->setSubprogram(NewSP);
+
+ // Go through the function itself to replace DILocations.
+ LLVMContext &Ctx = M.getContext();
+ DILocation *DL2 = DILocation::get(Ctx, 0, 0, NewSP, nullptr, 0, 0);
+ for (BasicBlock &BB : *F) {
+ for (Instruction &I : BB) {
+ for (DbgRecord &DR : I.getDbgRecordRange()) {
+ DebugLoc DL = DR.getDebugLoc();
+ auto *OldDL = DL.get();
+ SmallVector<DILocation *, 5> DLlist;
+
+ DLlist.push_back(OldDL);
+ while (OldDL->getInlinedAt()) {
+ OldDL = OldDL->getInlinedAt();
+ DLlist.push_back(OldDL);
+ }
+ DILocation *PrevLoc = DL2;
+ for (int i = DLlist.size() - 1; i >= 0; i--) {
+ OldDL = DLlist[i];
+ PrevLoc = DILocation::get(
+ Ctx, OldDL->getLine(), OldDL->getColumn(), OldDL->getScope(),
+ PrevLoc, OldDL->isImplicitCode(), OldDL->getAtomGroup(),
+ OldDL->getAtomRank());
+ }
+ DR.setDebugLoc(DebugLoc(const_cast<DILocation *>(PrevLoc)));
+ }
+ if (DebugLoc DL = I.getDebugLoc()) {
+ auto *OldDL = DL.get();
+ SmallVector<DILocation *, 5> DLlist;
+
+ DLlist.push_back(OldDL);
+ while (OldDL->getInlinedAt()) {
+ OldDL = OldDL->getInlinedAt();
+ DLlist.push_back(OldDL);
+ }
+ DILocation *PrevLoc = DL2;
+ for (int i = DLlist.size() - 1; i >= 0; i--) {
+ OldDL = DLlist[i];
+ PrevLoc = DILocation::get(
+ Ctx, OldDL->getLine(), OldDL->getColumn(), OldDL->getScope(),
+ PrevLoc, OldDL->isImplicitCode(), OldDL->getAtomGroup(),
+ OldDL->getAtomRank());
+ }
+ I.setDebugLoc(DebugLoc(PrevLoc));
+ }
+ if (MDNode *LoopID = I.getMetadata(LLVMContext::MD_loop)) {
+ auto X = [&](const DILocation *OldDL) -> const DILocation * {
+ return DILocation::get(Ctx, OldDL->getLine(), OldDL->getColumn(),
+ OldDL->getScope(), DL2,
+ OldDL->isImplicitCode(), OldDL->getAtomGroup(),
+ OldDL->getAtomRank());
+ };
+ MDNode *New = cloneLoopIDReplacingAllDILocs(LoopID, X, Ctx);
+ I.setMetadata(LLVMContext::MD_loop, New);
+ }
+ }
+ }
+
+ // At the beginning of the function, add dbg_values for true func signatures.
+ unsigned NumArgs = F->getFunctionType()->getNumParams();
+ if (NumArgs) {
+ BasicBlock::iterator InsertPt = F->getEntryBlock().getFirstInsertionPt();
+ for (int i = NumArgs - 1; i >= 0; --i) {
+ DILocalVariable *Var = cast<DILocalVariable>(ArgList[i]);
+ DIB.insertDbgValueIntrinsic(F->getArg(i), Var, DIB.createExpression(),
+ DL2, InsertPt);
+ }
+ }
+
+ DIB.finalize();
+}
+
+PreservedAnalyses EmitChangedFuncDebugInfoPass::run(Module &M,
+ ModuleAnalysisManager &AM) {
+ // For C only
+ for (DICompileUnit *CU : M.debug_compile_units()) {
+ auto L = static_cast<llvm::dwarf::SourceLanguage>(CU->getSourceLanguage());
+ if (L != dwarf::DW_LANG_C && L != dwarf::DW_LANG_C89 &&
+ L != dwarf::DW_LANG_C99 && L != dwarf::DW_LANG_C11 &&
+ L != dwarf::DW_LANG_C17)
+ return PreservedAnalyses::all();
+ }
+
+ llvm::Triple T(M.getTargetTriple());
+
+ // FIXME: Skip if BPF target. Unlike other architectures, BPF target will
+ // generate BTF in LLVM. We can tune BPF target later.
+ if (T.isBPF())
+ return PreservedAnalyses::all();
+
+ unsigned PointerBitWidth = T.getArchPointerBitWidth();
+
+ SmallVector<Function *> ChangedFuncs;
+ for (auto &F : M) {
+ // Function must already have DebugInfo.
+ DISubprogram *SP = F.getSubprogram();
+ if (!SP)
+ continue;
+
+ // Ignore all intrinsics/declare-only functions.
+ if (F.isIntrinsic() || F.isDeclaration())
+ continue;
+
+ // Skip if the return value is a DICompositeType and its size is greater
+ // than PointerBitWidth.
+ DITypeRefArray TyArray = SP->getType()->getTypeArray();
+ if (TyArray.size() == 0)
+ continue;
+ DIType *DITy = TyArray[0];
+ while (auto *DTy = dyn_cast_or_null<DIDerivedType>(DITy)) {
+ if (DTy->getTag() == dwarf::DW_TAG_pointer_type) {
+ DITy = DTy;
+ break;
+ }
+ DITy = DTy->getBaseType();
+ }
+ if (auto *DTy = dyn_cast_or_null<DICompositeType>(DITy)) {
+ if (DTy->getSizeInBits() > PointerBitWidth)
+ continue;
+ }
+
+ // Skip if the func has variable number of arguments
+ if (TyArray.size() > 1 && TyArray[TyArray.size() - 1] == nullptr)
+ continue;
+
+ // For original functions with struct/union as the argument and
+ // if the argument size is greater than 8 bytes, consider this
+ // function as signature changed.
+ StringRef FName = F.getName();
+ if (!FName.contains('.')) {
+ uint8_t cc = SP->getType()->getCC();
+ if (cc != llvm::dwarf::DW_CC_nocall) {
+ bool SigChanged = false;
+ for (unsigned i = 1; i < TyArray.size(); ++i) {
+ DITy = TyArray[i];
+ while (auto *DTy = dyn_cast<DIDerivedType>(DITy)) {
+ if (DTy->getTag() == dwarf::DW_TAG_pointer_type) {
+ DITy = DTy;
+ break;
+ }
+ DITy = DTy->getBaseType();
+ }
+ if (auto *DTy = dyn_cast<DICompositeType>(DITy)) {
+ if (DTy->getSizeInBits() <= PointerBitWidth)
+ continue;
+ SigChanged = true;
+ break;
+ }
+ }
+ if (!SigChanged)
+ continue;
+ }
+ }
+
+ // Reset calling convention to DW_CC_normal as later the function will
+ // be marked as Artificial.
+ auto Temp = SP->getType()->cloneWithCC(llvm::dwarf::DW_CC_normal);
+ SP->replaceType(MDNode::replaceWithPermanent(std::move(Temp)));
+
+ ChangedFuncs.push_back(&F);
+ }
+
+ bool Changed = ChangedFuncs.size() != 0;
+ for (auto *F : ChangedFuncs)
+ generateDebugInfo(M, F, PointerBitWidth);
+
+ return Changed ? PreservedAnalyses::none() : PreservedAnalyses::all();
+}
diff --git a/llvm/test/Other/new-pm-defaults.ll b/llvm/test/Other/new-pm-defaults.ll
index 94e860b8ce304..650bcd6f0f98d 100644
--- a/llvm/test/Other/new-pm-defaults.ll
+++ b/llvm/test/Other/new-pm-defaults.ll
@@ -295,6 +295,8 @@
; CHECK-DEFAULT-NEXT: Running pass: CGProfilePass
; CHECK-DEFAULT-NEXT: Running pass: RelLookupTableConverterPass
; CHECK-LTO-NOT: Running pass: RelLookupTableConverterPass
+; CHECK-DEFAULT-NEXT: Running pass: EmitChangedFuncDebugInfoPass
+; CHECK-LTO-NOT: Running pass: EmitChangedFuncDebugInfoPass
; CHECK-O-NEXT: Running pass: AnnotationRemarksPass on foo
; CHECK-LTO-NEXT: Running pass: CanonicalizeAliasesPass
; CHECK-LTO-NEXT: Running pass: NameAnonGlobalPass
diff --git a/llvm/test/Other/new-pm-thinlto-postlink-defaults.ll b/llvm/test/Other/new-pm-thinlto-postlink-defaults.ll
index a08a140a35166..b913cd3e2d90b 100644
--- a/llvm/test/Other/new-pm-thinlto-postlink-defaults.ll
+++ b/llvm/test/Other/new-pm-thinlto-postlink-defaults.ll
@@ -208,6 +208,7 @@
; CHECK-POSTLINK-O-NEXT: Running pass: ConstantMergePass
; CHECK-POSTLINK-O-NEXT: Running pass: CGProfilePass
; CHECK-POSTLINK-O-NEXT: Running pass: RelLookupTableConverterPass
+; CHECK-POSTLINK-O-NEXT: Running pass: EmitChangedFuncDebugInfoPass
; CHECK-EP-OPT-EARLY-NEXT: Running pass: NoOpModulePass
; CHECK-EP-OPT-LAST-NEXT: Running pass: NoOpModulePass
; CHECK-O-NEXT: Running pass: AnnotationRemarksPass on foo
diff --git a/llvm/test/Other/new-pm-thinlto-postlink-pgo-defaults.ll b/llvm/test/Other/new-pm-thinlto-postlink-pgo-defaults.ll
index d9e2dd37a7985..38e3238b3c170 100644
--- a/llvm/test/Other/new-pm-thinlto-postlink-pgo-defaults.ll
+++ b/llvm/test/Other/new-pm-thinlto-postlink-pgo-defaults.ll
@@ -192,6 +192,7 @@
; CHECK-O-NEXT: Running pass: ConstantMergePass
; CHECK-O-NEXT: Running pass: CGProfilePass
; CHECK-O-NEXT: Running pass: RelLookupTableConverterPass
+; CHECK-O-NEXT: Running pass: EmitChangedFuncDebugInfoPass
; CHECK-O-NEXT: Running pass: AnnotationRemarksPass on foo
; CHECK-O-NEXT: Running pass: PrintModulePass
diff --git a/llvm/test/Other/new-pm-thinlto-postlink-samplepgo-defaults.ll b/llvm/test/Other/new-pm-thinlto-postlink-samplepgo-defaults.ll
index 2f6fa4b27d354..2ec67d01424e7 100644
--- a/llvm/test/Other/new-pm-thinlto-postlink-samplepgo-defaults.ll
+++ b/llvm/test/Other/new-pm-thinlto-postlink-samplepgo-defaults.ll
@@ -201,6 +201,7 @@
; CHECK-O-NEXT: Running pass: ConstantMergePass
; CHECK-O-NEXT: Running pass: CGProfilePass
; CHECK-O-NEXT: Running pass: RelLookupTableConverterPass
+; CHECK-O-NEXT: Running pass: EmitChangedFuncDebugInfoPass
; CHECK-O-NEXT: Running pass: AnnotationRemarksPass on foo
; CHECK-O-NEXT: Running pass: PrintModulePass
More information about the llvm-commits
mailing list