From e112c90b3dd9d80e190776e2ff39df2ad5f18075 Mon Sep 17 00:00:00 2001 From: Yonghong Song 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 88fbf161058bbbfa256d4b08ba8935d3a7dc5dc6 Mon Sep 17 00:00:00 2001 From: Yonghong Song 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 (not 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 ("arg") 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. There is a problem in the above then, it is not clear what formal parameter 'arg' corresponds to the original parameter. If necessary, the compiler could change 'arg' to e.g. 'arg_offset_8' to indicate it is 8 byte offset from the original struct. Example 3 --------- Source: $ cat test2.c struct t { long a; long b; long c;}; __attribute__((noinline)) long foo(struct t arg) { return arg.a * arg.c; } long bar(struct t arg) { return foo(arg); } 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. Again, the name can be changed e.g. 'arg_ptr' if desired. 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 test2.c -mllvm -enable-changed-func-dbinfo $ llvm-dwarfdump test2.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. 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 fentry/fexit tracing. But if a symbol with ".llvm." has different signatures than the source level "", then a special DW_TAG_inlined_subroutine will be generated like below: 0x10f0793f: DW_TAG_inlined_subroutine DW_AT_name ("flow_offload_fill_route") DW_AT_linkage_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 "int") 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". The tools like pahole may choice to generate two entries with DW_AT_name and DW_AT_linkage_name for vmlinux BTF. Note that it is possible one source symbol may have multiple linkage name's due to potentially (more than one) cloning in llvm. In such cases, multiple DW_TAG_inlined_subroutine instances might be possible. 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 DIType issue so some function may be skipped. Some statistics with linux kernel ================================= I have tested this patch set by building latest bpf-next linux kernel. For no-lto case: 65341 original number of functions 1054 signature changed functions with this patch For thin-lto case: 65595 original number of functions 3150 signature changed functions with this patch Next step ========= With this llvm change, we will be able to do some work in pahole and libbpf. 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 | 701 ++++++++++++++++++ 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 | 72 ++ .../Util/changed-func-dbg-struct-16B.ll | 71 ++ .../changed-func-dbg-struct-large-dwarf.ll | 79 ++ .../Util/changed-func-dbg-struct-large.ll | 94 +++ 20 files changed, 1541 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 { +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 567acf75d1b8d..41c5464b48a55 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() == "") { + 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(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 ArgVars(Args.size()); + for (const DINode *DN : ExtraSP->getRetainedNodes()) { + if (const auto *DV = dyn_cast(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 3c9a27ac24015..c43fc5a215468 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 3f41618b18fcf..e40e2a2d520cc 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" @@ -1637,9 +1638,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 1853cdd45d0ee..91aeab54be333 100644 --- a/llvm/lib/Passes/PassRegistry.def +++ b/llvm/lib/Passes/PassRegistry.def @@ -75,6 +75,7 @@ MODULE_PASS("dfsan", DataFlowSanitizerPass()) MODULE_PASS("dot-callgraph", CallGraphDOTPrinterPass()) MODULE_PASS("dxil-upgrade", DXILUpgradePass()) MODULE_PASS("elim-avail-extern", EliminateAvailableExternallyPass()) +MODULE_PASS("emit-changed-func-debuginfo", EmitChangedFuncDebugInfoPass()) MODULE_PASS("extract-blocks", BlockExtractorPass({}, false)) MODULE_PASS("expand-variadics", ExpandVariadicsPass(ExpandVariadicsMode::Disable)) diff --git a/llvm/lib/Transforms/Utils/CMakeLists.txt b/llvm/lib/Transforms/Utils/CMakeLists.txt index f367ca2fdf56b..72291a0c7d8b0 100644 --- a/llvm/lib/Transforms/Utils/CMakeLists.txt +++ b/llvm/lib/Transforms/Utils/CMakeLists.txt @@ -23,6 +23,7 @@ add_llvm_component_library(LLVMTransformUtils DebugSSAUpdater.cpp DeclareRuntimeLibcalls.cpp DemoteRegToStack.cpp + EmitChangedFuncDebugInfo.cpp DXILUpgrade.cpp EntryExitInstrumenter.cpp EscapeEnumerator.cpp diff --git a/llvm/lib/Transforms/Utils/EmitChangedFuncDebugInfo.cpp b/llvm/lib/Transforms/Utils/EmitChangedFuncDebugInfo.cpp new file mode 100644 index 0000000000000..b51c154a2bff4 --- /dev/null +++ b/llvm/lib/Transforms/Utils/EmitChangedFuncDebugInfo.cpp @@ -0,0 +1,701 @@ +//==- 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 *possibly changed* +// signature for certain optimized functions. The new subprogram lives in a +// dedicated DICompileUnit whose file name is "", and is +// attached to a dummy AvailableExternally function so that the metadata forms +// a valid graph. +// +// When we can recover argument names/types from dbg records in the entry +// block, we do so; otherwise this particular function will be skipped. +// Remarks can show what functions to be skipped. +// +// We *only* run for C-family source languages, only support x86_64/arm64/risc64 +// /s390), skip varargs originals, and skip functions whose return type is a +// large by-value aggregate, 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/IntrinsicInst.h" +#include "llvm/IR/Module.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/TargetParser/Triple.h" + +using namespace llvm; + +#define DEBUG_TYPE "emit-changed-func-debuginfo" + +// Disable/Enable switch. +static cl::opt EnableChangedFuncDBInfo( + "enable-changed-func-dbinfo", cl::Hidden, cl::init(false), + cl::desc("Enable debuginfo emission for changed func signatures")); + +static cl::opt SkipDottedFunc("skip-dotted-func", cl::Hidden, + cl::init(false), + cl::desc("Skip functions with dot")); + +// 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; +} + +// Return the "basename" (prefix before the first '.') of a name. +static StringRef baseBeforeDot(StringRef S) { + return S.take_front(S.find('.')); +} + +// Ensure a variable name is unique among previously recorded parameters. +// If collision, append "__". +static std::string uniquifyParamName(StringRef Candidate, + ArrayRef Existing, + unsigned Idx) { + for (unsigned i = 0; i < Existing.size(); ++i) + if (auto *LV = dyn_cast(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(Prev)) + if (Z->getOperand(0) == Arg && LocV == Prev) + return true; + if (auto *T = dyn_cast(Prev)) + if (T->getOperand(0) == Arg && LocV == Prev) + return true; + if (auto *T = dyn_cast(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(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) { + 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(Elems[i])) { + if (N >= 2 && i < N - 1) { + if (Elem->getOffsetInBits() <= BitOffset && + BitUpLimit <= (Elem->getOffsetInBits() + Elem->getSizeInBits())) + return Elem->getBaseType(); + } else { + if (Elem->getOffsetInBits() <= BitOffset && + BitUpLimit <= DTy->getSizeInBits()) + return Elem->getBaseType(); + } + } + + return createBasicType(DIB, BitSize); + } + return createBasicType(DIB, W); +} + +static DIType *computeParamDIType(DIBuilder &DIB, Type *Ty, DIType *Orig, + unsigned PointerBitWidth, DIExpression *Expr, + bool Coerce16B, bool ByVal) { + DIType *Stripped = stripToBaseOrFirstPointer(Orig); + unsigned TyBitSize = Stripped->getSizeInBits(); + + if (TyBitSize <= 64) + return Stripped; + + if (TyBitSize > 128 || !Coerce16B || ByVal) + return DIB.createPointerType(Stripped, PointerBitWidth); + + auto *Comp = cast(Stripped); + if (Comp->getTag() == dwarf::DW_TAG_union_type) + return nullptr; + + unsigned W = cast(Ty)->getBitWidth(); + return getIntTypeFromExpr(DIB, Expr, Comp, W); +} + +static bool isLargeByValueAggregate(DIType *T, unsigned PtrW) { + DIType *P = stripToBaseOrFirstPointer(T); + if (auto *Comp = dyn_cast_or_null(P)) + return Comp->getSizeInBits() > PtrW; + return false; +} + +static void pushParam(DIBuilder &DIB, DISubprogram *OldSP, + SmallVectorImpl &TypeList, + SmallVectorImpl &ArgList, DIType *Ty, + StringRef VarName, unsigned Idx) { + TypeList.push_back(Ty); + ArgList.push_back(DIB.createParameterVariable( + OldSP, VarName, Idx + 1, OldSP->getFile(), OldSP->getLine(), Ty)); +} + +// Argument collection. +static bool getOneArgDI(unsigned Idx, BasicBlock &Entry, DIBuilder &DIB, + Function *F, DISubprogram *OldSP, + SmallVectorImpl &TypeList, + SmallVectorImpl &ArgList, + unsigned PointerBitWidth, bool Coerce16B) { + 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 ByValUserName; + bool IsByValStruct = true; + if (ArgTy->isPointerTy() && Arg->hasByValAttr()) { + if (Type *ByValTy = F->getParamByValType(Idx)) + if (auto *ST = dyn_cast(ByValTy)) { + auto [Kind, Name] = ST->getName().split('.'); + ByValUserName = 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(&DR); + if (!DVR) + continue; + + auto *VAM = dyn_cast_or_null(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(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; + } + + // Compare base names (before dot) in several cases. + StringRef ArgBase = baseBeforeDot(ArgName); + StringRef VarBase = baseBeforeDot(Var->getName()); + + if (ArgName.empty()) { + if (!ByValUserName.empty()) { + // Match by byval struct DI type’s name/kind. + DIType *Stripped = stripToBaseOrFirstPointer(Var->getType()); + auto *Comp = dyn_cast(Stripped); + if (!Comp) + continue; + bool IsStruct = Comp->getTag() == dwarf::DW_TAG_structure_type; + if (Comp->getName() != ByValUserName || IsStruct != IsByValStruct) + continue; + DIVar = Var; + DIExpr = DVR->getExpression(); + Final = true; + break; + } + + if (isa(LocV)) + continue; + + if (comesFromArgViaCast(LocV, Arg, I)) { + DIVar = Var; + DIExpr = DVR->getExpression(); + Final = true; + break; + } + + if (isa(LocV)) + continue; + } else { + // We do have an IR arg name. + if (isa(LocV)) { + if (Var->getName() != ArgBase) + continue; + DIVar = Var; + DIExpr = DVR->getExpression(); + // Possibly we may find a non poison value later. + } else if (isa(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; + } + + OptimizationRemarkEmitter ORE(F); + + if (!DIVar) { + 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(ArgTy)->getBitWidth()); + pushParam(DIB, OldSP, TypeList, ArgList, Ty, + (Twine("__") + Twine(Idx)).str(), Idx); + + ORE.emit([&]() { + return OptimizationRemark(DEBUG_TYPE, "FindDIVariable", F) + << "create a new int type " << ore::NV("ArgName", Arg->getName()) + << "(" << ore::NV("ArgIndex", Idx) << ")"; + }); + + return true; + } + + ORE.emit([&]() { + return OptimizationRemark(DEBUG_TYPE, "FindDIVariable", F) + << "not found ptr type " << ore::NV("ArgName", Arg->getName()) + << "(" << ore::NV("ArgIndex", Idx) << ")"; + }); + + return false; + } + + // Compute parameter DI type from IR type + original debug type. + DIType *ParamType = + computeParamDIType(DIB, ArgTy, DIVar->getType(), PointerBitWidth, DIExpr, + Coerce16B, !ByValUserName.empty()); + if (!ParamType) { + ORE.emit([&]() { + return OptimizationRemark(DEBUG_TYPE, "GetParamDIType", F) + << "no use union type " << ore::NV("ArgName", Arg->getName()) + << "(" << ore::NV("ArgIndex", Idx) << ")"; + }); + + return false; + } + + // Decide the parameter name (sanitize + uniquify). + std::string VarName; + if (ArgName.empty()) { + VarName = sanitizeDots(DIVar->getName()); + VarName = uniquifyParamName(VarName, ArgList, Idx); + } else { + VarName = sanitizeDots(ArgName); + } + + pushParam(DIB, OldSP, TypeList, ArgList, ParamType, VarName, Idx); + return true; +} + +// Collect return and parameter DI information. +static bool collectReturnAndArgs(DIBuilder &DIB, Function *F, + DISubprogram *OldSP, + SmallVectorImpl &TypeList, + SmallVectorImpl &ArgList, + unsigned PointerBitWidth, bool Coerce16B) { + 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, TypeList, ArgList, + PointerBitWidth, Coerce16B)) + 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(Node); + if (CU->getFile()->getFilename() == "") + return CU; + } + return nullptr; +} + +static bool buildArgsForChangedSignature( + DIBuilder &DIB, Function *F, DISubprogram *OldSP, DITypeRefArray TyArray, + SmallVectorImpl &TypeList, SmallVectorImpl &ArgList, + unsigned PointerBitWidth, bool Coerce16B) { + // Return type. + TypeList.push_back(TyArray[0]); + + unsigned ArgIdx = 0; + for (unsigned i = 1; i < TyArray.size(); ++i) { + Argument *Arg = F->getArg(ArgIdx); + StringRef ArgName = Arg->getName(); + Type *ArgTy = Arg->getType(); + + // For x86_64, a 16-byte argument may become a pointer since only + // limited (6) registers available to pass directly. The rest of + // arguments will pass through stack. + bool ByVal = Arg->hasByValAttr(); + + DILocalVariable *DIVar; + for (DINode *DN : OldSP->getRetainedNodes()) + if (auto *DV = dyn_cast(DN)) + if (DV->getArg() == i) + DIVar = DV; + + DIType *Ty = stripToBaseOrFirstPointer(TyArray[i]); + unsigned TyBitSize = Ty->getSizeInBits(); + + if (ArgName.empty()) + ArgName = DIVar->getName(); + + if (TyBitSize <= 64) { + pushParam(DIB, OldSP, TypeList, ArgList, Ty, ArgName, ArgIdx); + ArgIdx++; + continue; + } + + if (TyBitSize > 128 || ByVal || !Coerce16B) { + Ty = DIB.createPointerType(Ty, PointerBitWidth); + pushParam(DIB, OldSP, TypeList, ArgList, Ty, ArgName, ArgIdx); + ArgIdx++; + continue; + } + + // Two arguments (struct or union) + DICompositeType *CompTy = cast(Ty); + if (CompTy->getTag() == dwarf::DW_TAG_union_type) + return false; + + for (auto Element : CompTy->getElements()) { + // dwarf::DW_TAG_member + auto *DDTy = cast(Element); + if (DDTy->getOffsetInBits() == 0) { + auto *BaseTy = stripToBaseOrFirstPointer(DDTy->getBaseType()); + if (BaseTy->getSizeInBits() == 64) + Ty = BaseTy; + else + Ty = createBasicType(DIB, 64); + + std::string Owned = (llvm::Twine(ArgName) + "__coerce0").str(); + ArgName = Owned; + + pushParam(DIB, OldSP, TypeList, ArgList, Ty, ArgName, ArgIdx); + ArgIdx++; + } else if (DDTy->getOffsetInBits() == 64) { + Arg = F->getArg(ArgIdx); + ArgName = Arg->getName(); + ArgTy = Arg->getType(); + + if (ArgName.empty()) + ArgName = DIVar->getName(); + + auto *BaseTy = stripToBaseOrFirstPointer(DDTy->getBaseType()); + if (BaseTy->getSizeInBits() == 64) + Ty = BaseTy; + else + Ty = createBasicType(DIB, cast(ArgTy)->getBitWidth()); + + std::string Owned = (llvm::Twine(ArgName) + "__coerce1").str(); + ArgName = Owned; + pushParam(DIB, OldSP, TypeList, ArgList, Ty, ArgName, ArgIdx); + ArgIdx++; + break; + } else if (DDTy->getOffsetInBits() > 64) { + Arg = F->getArg(ArgIdx); + ArgName = Arg->getName(); + ArgTy = Arg->getType(); + + if (ArgName.empty()) + ArgName = DIVar->getName(); + + Ty = createBasicType(DIB, cast(ArgTy)->getBitWidth()); + + std::string Owned = (llvm::Twine(ArgName) + "__coerce1").str(); + ArgName = Owned; + pushParam(DIB, OldSP, TypeList, ArgList, Ty, ArgName, ArgIdx); + ArgIdx++; + break; + } + } + } + + return true; +} + +static void generateDebugInfo(Module &M, Function *F, unsigned PointerBitWidth, + bool Coerce16B) { + 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("", OldFile->getDirectory()); + NewCU = DIB.createCompileUnit( + OldCU->getSourceLanguage(), NewFile, OldCU->getProducer(), + OldCU->isOptimized(), OldCU->getFlags(), OldCU->getRuntimeVersion()); + } + + SmallVector TypeList, ArgList; + + bool doCollect = true; + DITypeRefArray TyArray = OldSP->getType()->getTypeArray(); + uint8_t cc = OldSP->getType()->getCC(); + if (cc != dwarf::DW_CC_nocall) { + unsigned i; + unsigned n = TyArray.size(); + for (i = 1; i < n; ++i) { + if (isLargeByValueAggregate(TyArray[i], PointerBitWidth)) + break; + } + if (i == n) + doCollect = false; + } + + // doCollect true means the OldSP signature can be reused for function F. + + DISubroutineType *SubTy; + DINodeArray ArgArray; + StringRef FuncName, LinkageName; + + // Three cases: + // . OldSP signature can be reused, e.g., foo.llvm.() has the same + // signature as source foo(). + // . Signature changed (not-marked with dwarf::DW_CC_nocall) due to + // parameter have more than 8 byte size. + // . Signature changed (marked with dwarf::DW_CC_nocall). + if (cc != dwarf::DW_CC_nocall) { + if (!doCollect) { + SubTy = OldSP->getType(); + ArgArray = OldSP->getRetainedNodes(); + FuncName = F->getName(); + } else { + bool Success = + buildArgsForChangedSignature(DIB, F, OldSP, TyArray, TypeList, + ArgList, PointerBitWidth, Coerce16B); + if (!Success) { + DIB.finalize(); + return; + } + + DITypeRefArray DITypeArray = + DIB.getOrCreateTypeArray(ArrayRef{TypeList}); + SubTy = DIB.createSubroutineType(DITypeArray); + ArgArray = DIB.getOrCreateArray(ArgList); + FuncName = OldSP->getName(); + if (FuncName != F->getName()) + LinkageName = F->getName(); + } + } else { + bool Success = collectReturnAndArgs(DIB, F, OldSP, TypeList, ArgList, + PointerBitWidth, Coerce16B); + if (!Success) { + DIB.finalize(); + return; + } + + DITypeRefArray DITypeArray = + DIB.getOrCreateTypeArray(ArrayRef{TypeList}); + SubTy = DIB.createSubroutineType(DITypeArray); + ArgArray = DIB.getOrCreateArray(ArgList); + FuncName = OldSP->getName(); + if (FuncName != F->getName()) + LinkageName = F->getName(); + } + + DISubprogram *NewSP = DIB.createFunction( + OldSP, FuncName, LinkageName, NewFile, OldSP->getLine(), SubTy, + OldSP->getScopeLine(), DINode::FlagZero, DISubprogram::SPFlagDefinition); + NewSP->replaceRetainedNodes(ArgArray); + + DIB.finalizeSubprogram(NewSP); + + // 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(); +} + +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(); + + // If Coerce16B is true, a 16B byte original parameter will become two + // parameters in argument list. Otherwise, the 16B byte original + // parameter will become a pointer (similar to e.g. 24B struct parameter). + bool Coerce16B = true; + if (T.isSystemZ()) + Coerce16B = false; + + SmallVector ChangedFuncs; + for (Function &F : M) { + if (F.isIntrinsic() || F.isDeclaration()) + continue; + + DISubprogram *SP = F.getSubprogram(); + if (!SP) + continue; + + DITypeRefArray TyArray = SP->getType()->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(RetDI)) + if (Comp->getSizeInBits() > PointerBitWidth) + continue; + + if (SkipDottedFunc && F.getName().contains('.')) + continue; + + // Signature consider not changed if + // . function does not '.' (e.g., foo.llvm.), and + // . function without '.'/dwarf::DW_CC_nocall, and + // . any argument size is less than 8 bytes (PointerBitWidth). + if (!F.getName().contains('.') && + SP->getType()->getCC() != dwarf::DW_CC_nocall) { + n = TyArray.size(); + for (i = 1; i < n; ++i) + if (isLargeByValueAggregate(TyArray[i], PointerBitWidth)) { + break; + } + if (i == n) + continue; + } + + ChangedFuncs.push_back(&F); + } + + for (Function *F : ChangedFuncs) + generateDebugInfo(M, F, PointerBitWidth, Coerce16B); + + 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 62975a3cf8ac4..19208c26cce80 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 012a1ab5802b5..c2d52a50a794e 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 e021ff3124b60..b4454513e1023 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 20f94bc2e0f6c..c09ba09d88b12 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@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@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@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@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@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@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@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@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..3067adf1b5087 --- /dev/null +++ b/llvm/test/Transforms/Util/changed-func-dbg-struct-16B-dwarf.ll @@ -0,0 +1,72 @@ +; 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; }; +; 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 { + #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@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@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) + +; DWARF: DW_TAG_inlined_subroutine +; DWARF-NEXT: DW_AT_name ("foo") +; DWARF-NEXT: DW_AT_type +; DWARF-SAME: "long" +; 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__coerce0") +; DWARF-NEXT: DW_AT_type +; DWARF-SAME: "long" +; DWARF-NEXT: {{^$}} +; DWARF-NEXT: DW_TAG_formal_parameter +; DWARF-NEXT: DW_AT_name ("arg__coerce1") +; 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@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@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..6edd3222dddc3 --- /dev/null +++ b/llvm/test/Transforms/Util/changed-func-dbg-struct-large-dwarf.ll @@ -0,0 +1,79 @@ +; 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;}; +; 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 { + #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@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@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) + +; DWARF: DW_TAG_inlined_subroutine +; DWARF-NEXT: DW_AT_name ("foo") +; DWARF-NEXT: DW_AT_type +; DWARF-SAME: "long" +; 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@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@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) +;.