From 4909814c08fdf4ec8bd9dad4f157d03de7c3c800 Mon Sep 17 00:00:00 2001 From: Michael Buch Date: Mon, 6 Nov 2023 10:23:26 +0000 Subject: [PATCH] [clang][DebugInfo] Emit global variable definitions for static data members with constant initializers (#70639) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When an LLDB user asks for the value of a static data member, LLDB starts by searching the Names accelerator table for the corresponding variable definition DIE. For static data members with out-of-class definitions that works fine, because those get represented as global variables with a location and making them eligible to be added to the Names table. However, in-class definitions won’t get indexed because we usually don't emit global variables for them. So in DWARF we end up with a single `DW_TAG_member` that usually holds the constant initializer. But we don't get a corresponding CU-level `DW_TAG_variable` like we do for out-of-class definitions. To make it more convenient for debuggers to get to the value of inline static data members, this patch makes sure we emit definitions for static variables with constant initializers the same way we do for other static variables. This also aligns Clang closer to GCC, which produces CU-level definitions for inline statics and also emits these into `.debug_pubnames`. The implementation keeps track of newly created static data members. Then in `CGDebugInfo::finalize`, we emit a global `DW_TAG_variable` with a `DW_AT_const_value` for any of those declarations that didn't end up with a definition in the `DeclCache`. The newly emitted `DW_TAG_variable` will look as follows: ``` 0x0000007b: DW_TAG_structure_type DW_AT_calling_convention (DW_CC_pass_by_value) DW_AT_name ("Foo") ... 0x0000008d: DW_TAG_member DW_AT_name ("i") DW_AT_type (0x00000062 "const int") DW_AT_external (true) DW_AT_declaration (true) DW_AT_const_value (4) Newly added vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv 0x0000009a: DW_TAG_variable DW_AT_specification (0x0000008d "i") DW_AT_const_value (4) DW_AT_linkage_name ("_ZN2t2IiE1iIfEE") ``` This patch also drops the `DW_AT_const_value` off of the declaration since we now always have it on the definition. This ensures that the `DWARFParallelLinker` can type-merge class with static members where we couldn't attach the constant on the declaration in some CUs. --- clang/lib/CodeGen/CGDebugInfo.cpp | 58 +++++++++--- clang/lib/CodeGen/CGDebugInfo.h | 6 ++ clang/test/CodeGenCXX/debug-info-class.cpp | 13 ++- .../debug-info-static-inline-member.cpp | 94 +++++++++++++++++++ .../CodeGenCXX/debug-info-static-member.cpp | 52 ++++++---- .../TestConstStaticIntegralMember.py | 7 +- .../lldb-dap/variables/TestDAP_variables.py | 9 +- 7 files changed, 197 insertions(+), 42 deletions(-) create mode 100644 clang/test/CodeGenCXX/debug-info-static-inline-member.cpp diff --git a/clang/lib/CodeGen/CGDebugInfo.cpp b/clang/lib/CodeGen/CGDebugInfo.cpp index 84a166d3ac3659..410c8f522b1017 100644 --- a/clang/lib/CodeGen/CGDebugInfo.cpp +++ b/clang/lib/CodeGen/CGDebugInfo.cpp @@ -1677,22 +1677,13 @@ CGDebugInfo::CreateRecordStaticField(const VarDecl *Var, llvm::DIType *RecordTy, unsigned LineNumber = getLineNumber(Var->getLocation()); StringRef VName = Var->getName(); - llvm::Constant *C = nullptr; - if (Var->getInit()) { - const APValue *Value = Var->evaluateValue(); - if (Value) { - if (Value->isInt()) - C = llvm::ConstantInt::get(CGM.getLLVMContext(), Value->getInt()); - if (Value->isFloat()) - C = llvm::ConstantFP::get(CGM.getLLVMContext(), Value->getFloat()); - } - } llvm::DINode::DIFlags Flags = getAccessFlag(Var->getAccess(), RD); auto Align = getDeclAlignIfRequired(Var, CGM.getContext()); llvm::DIDerivedType *GV = DBuilder.createStaticMemberType( - RecordTy, VName, VUnit, LineNumber, VTy, Flags, C, Align); + RecordTy, VName, VUnit, LineNumber, VTy, Flags, /* Val */ nullptr, Align); StaticDataMemberCache[Var->getCanonicalDecl()].reset(GV); + StaticDataMemberDefinitionsToEmit.push_back(Var->getCanonicalDecl()); return GV; } @@ -5596,6 +5587,39 @@ void CGDebugInfo::EmitGlobalVariable(const ValueDecl *VD, const APValue &Init) { TemplateParameters, Align)); } +void CGDebugInfo::EmitGlobalVariable(const VarDecl *VD) { + assert(VD->hasInit()); + assert(CGM.getCodeGenOpts().hasReducedDebugInfo()); + if (VD->hasAttr()) + return; + + auto &GV = DeclCache[VD]; + if (GV) + return; + + auto const *InitVal = VD->evaluateValue(); + if (!InitVal) + return; + + llvm::DIFile *Unit = nullptr; + llvm::DIScope *DContext = nullptr; + unsigned LineNo; + StringRef DeclName, LinkageName; + QualType T; + llvm::MDTuple *TemplateParameters = nullptr; + collectVarDeclProps(VD, Unit, LineNo, T, DeclName, LinkageName, + TemplateParameters, DContext); + + auto Align = getDeclAlignIfRequired(VD, CGM.getContext()); + llvm::DINodeArray Annotations = CollectBTFDeclTagAnnotations(VD); + llvm::DIExpression *InitExpr = createConstantValueExpression(VD, *InitVal); + + GV.reset(DBuilder.createGlobalVariableExpression( + TheCU, DeclName, LinkageName, Unit, LineNo, getOrCreateType(T, Unit), + true, true, InitExpr, getOrCreateStaticDataMemberDeclarationOrNull(VD), + TemplateParameters, Align, Annotations)); +} + void CGDebugInfo::EmitExternalVariable(llvm::GlobalVariable *Var, const VarDecl *D) { assert(CGM.getCodeGenOpts().hasReducedDebugInfo()); @@ -5866,6 +5890,18 @@ void CGDebugInfo::finalize() { DBuilder.replaceTemporary(std::move(FwdDecl), cast(Repl)); } + for (auto const *VD : StaticDataMemberDefinitionsToEmit) { + assert(VD->isStaticDataMember()); + + if (DeclCache.contains(VD)) + continue; + + if (!VD->hasInit()) + continue; + + EmitGlobalVariable(VD); + } + // We keep our own list of retained types, because we need to look // up the final type in the type cache. for (auto &RT : RetainedTypes) diff --git a/clang/lib/CodeGen/CGDebugInfo.h b/clang/lib/CodeGen/CGDebugInfo.h index 7b60e94555d060..3e4c133b7f2b9f 100644 --- a/clang/lib/CodeGen/CGDebugInfo.h +++ b/clang/lib/CodeGen/CGDebugInfo.h @@ -161,6 +161,9 @@ class CGDebugInfo { llvm::DenseMap> StaticDataMemberCache; + /// Keeps track of static data members for which we should emit a definition. + std::vector StaticDataMemberDefinitionsToEmit; + using ParamDecl2StmtTy = llvm::DenseMap; using Param2DILocTy = llvm::DenseMap; @@ -526,6 +529,9 @@ class CGDebugInfo { /// Emit a constant global variable's debug info. void EmitGlobalVariable(const ValueDecl *VD, const APValue &Init); + /// Emit debug-info for a variable with a constant initializer. + void EmitGlobalVariable(const VarDecl *VD); + /// Emit information about an external variable. void EmitExternalVariable(llvm::GlobalVariable *GV, const VarDecl *Decl); diff --git a/clang/test/CodeGenCXX/debug-info-class.cpp b/clang/test/CodeGenCXX/debug-info-class.cpp index ef84b91b67821a..a3111cd7c3640a 100644 --- a/clang/test/CodeGenCXX/debug-info-class.cpp +++ b/clang/test/CodeGenCXX/debug-info-class.cpp @@ -116,11 +116,19 @@ int main(int argc, char **argv) { // CHECK-SAME: DIFlagFwdDecl // CHECK-NOT: identifier: // CHECK-SAME: ){{$}} + +// CHECK: !DIGlobalVariableExpression(var: ![[HDR_VAR:[0-9]+]], expr: !DIExpression(DW_OP_constu, 52, DW_OP_stack_value)) +// CHECK: ![[HDR_VAR]] = distinct !DIGlobalVariable(name: "HdrSize", +// CHECK-SAME: isLocal: true, isDefinition: true, declaration: ![[HDR_VAR_DECL:[0-9]+]]) +// CHECK: ![[INT:[0-9]+]] = !DIBasicType(name: "int" +// CHECK: ![[HDR_VAR_DECL]] = !DIDerivedType(tag: DW_TAG_member, name: "HdrSize" + +// CHECK: !DICompositeType(tag: DW_TAG_structure_type, name: "A" + // CHECK: !DICompositeType(tag: DW_TAG_structure_type, name: "I" // CHECK-NOT: DIFlagFwdDecl // CHECK-SAME: ){{$}} -// CHECK: ![[INT:[0-9]+]] = !DIBasicType(name: "int" // CHECK: !DICompositeType(tag: DW_TAG_structure_type, name: "foo" // CHECK: !DICompositeType(tag: DW_TAG_class_type, name: "bar" // CHECK: !DICompositeType(tag: DW_TAG_union_type, name: "baz" @@ -186,8 +194,5 @@ int main(int argc, char **argv) { // CHECK: [[G_INNER_I]] = !DIDerivedType(tag: DW_TAG_member, name: "j" // CHECK-SAME: baseType: ![[INT]] -// CHECK: !DICompositeType(tag: DW_TAG_structure_type, name: "A" -// CHECK: !DIDerivedType(tag: DW_TAG_member, name: "HdrSize" -// // CHECK: ![[EXCEPTLOC]] = !DILocation(line: 100, // CHECK: ![[RETLOC]] = !DILocation(line: 99, diff --git a/clang/test/CodeGenCXX/debug-info-static-inline-member.cpp b/clang/test/CodeGenCXX/debug-info-static-inline-member.cpp new file mode 100644 index 00000000000000..16872b752167ad --- /dev/null +++ b/clang/test/CodeGenCXX/debug-info-static-inline-member.cpp @@ -0,0 +1,94 @@ +// RUN: %clangxx -target arm64-apple-macosx11.0.0 -g %s -emit-llvm -S -o - | FileCheck --check-prefixes=CHECK %s + +enum class Enum : int { + VAL = -1 +}; + +struct Empty {}; + +constexpr auto func() { return 25; } + +struct Foo { + static constexpr int cexpr_int_with_addr = func(); + static constexpr int cexpr_int2 = func() + 1; + static constexpr float cexpr_float = 2.0 + 1.0; + static constexpr Enum cexpr_enum = Enum::VAL; + static constexpr Empty cexpr_struct_with_addr{}; + static inline Enum inline_enum = Enum::VAL; + + template + static constexpr T cexpr_template{}; +}; + +int main() { + Foo f; + + // Force global variable definitions to be emitted. + (void)&Foo::cexpr_int_with_addr; + (void)&Foo::cexpr_struct_with_addr; + + return Foo::cexpr_int_with_addr + Foo::cexpr_float + + (int)Foo::cexpr_enum + Foo::cexpr_template; +} + +// CHECK: @{{.*}}cexpr_int_with_addr{{.*}} = +// CHECK-SAME: !dbg ![[INT_GLOBAL:[0-9]+]] + +// CHECK: @{{.*}}cexpr_struct_with_addr{{.*}} = +// CHECK-SAME !dbg ![[EMPTY_GLOBAL:[0-9]+]] + +// CHECK: !DIGlobalVariableExpression(var: ![[INT_VAR:[0-9]+]], expr: !DIExpression()) +// CHECK: ![[INT_VAR]] = distinct !DIGlobalVariable(name: "cexpr_int_with_addr", linkageName: +// CHECK-SAME: isLocal: false, isDefinition: true, declaration: ![[INT_DECL:[0-9]+]]) + +// CHECK: ![[INT_DECL]] = !DIDerivedType(tag: DW_TAG_member, name: "cexpr_int_with_addr", +// CHECK-SAME: flags: DIFlagStaticMember +// CHECK-NOT: extraData: + +// CHECK: ![[INT_DECL2:[0-9]+]] = !DIDerivedType(tag: DW_TAG_member, name: "cexpr_int2", +// CHECK-SAME: flags: DIFlagStaticMember +// CHECK-NOT: extraData: + +// CHECK: ![[FLOAT_DECL:[0-9]+]] = !DIDerivedType(tag: DW_TAG_member, name: "cexpr_float", +// CHECK-SAME: flags: DIFlagStaticMember +// CHECK-NOT: extraData: + +// CHECK: ![[ENUM_DECL:[0-9]+]] = !DIDerivedType(tag: DW_TAG_member, name: "cexpr_enum", +// CHECK-SAME: flags: DIFlagStaticMember +// CHECK-NOT: extraData: + +// CHECK: ![[EMPTY_DECL:[0-9]+]] = !DIDerivedType(tag: DW_TAG_member, name: "cexpr_struct_with_addr", +// CHECK-SAME: flags: DIFlagStaticMember +// CHECK-NOT: extraData: + +// CHECK: ![[IENUM_DECL:[0-9]+]] = !DIDerivedType(tag: DW_TAG_member, name: "inline_enum", +// CHECK-SAME: flags: DIFlagStaticMember +// CHECK-NOT: extraData: + +// CHECK: ![[TEMPLATE_DECL:[0-9]+]] = !DIDerivedType(tag: DW_TAG_member, name: "cexpr_template", +// CHECK-SAME: flags: DIFlagStaticMember +// CHECK-NOT: extraData: + +// CHECK: !DIGlobalVariableExpression(var: ![[EMPTY_VAR:[0-9]+]], expr: !DIExpression()) +// CHECK: ![[EMPTY_VAR]] = distinct !DIGlobalVariable(name: "cexpr_struct_with_addr", linkageName: +// CHECK-SAME: isLocal: false, isDefinition: true, declaration: ![[EMPTY_DECL]]) + +// CHECK: !DIGlobalVariableExpression(var: ![[INT_VAR2:[0-9]+]], expr: !DIExpression(DW_OP_constu, 26, DW_OP_stack_value)) +// CHECK: ![[INT_VAR2]] = distinct !DIGlobalVariable(name: "cexpr_int2", linkageName: +// CHECK-SAME: isLocal: true, isDefinition: true, declaration: ![[INT_DECL2]]) + +// CHECK: !DIGlobalVariableExpression(var: ![[FLOAT_VAR:[0-9]+]], expr: !DIExpression(DW_OP_constu, {{.*}}, DW_OP_stack_value)) +// CHECK: ![[FLOAT_VAR]] = distinct !DIGlobalVariable(name: "cexpr_float", linkageName: +// CHECK-SAME: isLocal: true, isDefinition: true, declaration: ![[FLOAT_DECL]]) + +// CHECK: !DIGlobalVariableExpression(var: ![[ENUM_VAR:[0-9]+]], expr: !DIExpression(DW_OP_constu, {{.*}}, DW_OP_stack_value)) +// CHECK: ![[ENUM_VAR]] = distinct !DIGlobalVariable(name: "cexpr_enum", linkageName: +// CHECK-SAME: isLocal: true, isDefinition: true, declaration: ![[ENUM_DECL]]) + +// CHECK: !DIGlobalVariableExpression(var: ![[IENUM_VAR:[0-9]+]], expr: !DIExpression(DW_OP_constu, {{.*}}, DW_OP_stack_value)) +// CHECK: ![[IENUM_VAR]] = distinct !DIGlobalVariable(name: "inline_enum", linkageName: +// CHECK-SAME: isLocal: true, isDefinition: true, declaration: ![[IENUM_DECL]]) + +// CHECK: !DIGlobalVariableExpression(var: ![[TEMPLATE_VAR:[0-9]+]], expr: !DIExpression(DW_OP_constu, 0, DW_OP_stack_value)) +// CHECK: ![[TEMPLATE_VAR]] = distinct !DIGlobalVariable(name: "cexpr_template", linkageName: +// CHECK-SAME: isLocal: true, isDefinition: true, declaration: ![[TEMPLATE_DECL]], templateParams: ![[TEMPLATE_PARMS:[0-9]+]]) diff --git a/clang/test/CodeGenCXX/debug-info-static-member.cpp b/clang/test/CodeGenCXX/debug-info-static-member.cpp index 260a3afdd6524f..578995801c6400 100644 --- a/clang/test/CodeGenCXX/debug-info-static-member.cpp +++ b/clang/test/CodeGenCXX/debug-info-static-member.cpp @@ -63,19 +63,19 @@ int C::a = 4; // CHECK-NOT: offset: // CHECK-SAME: flags: DIFlagStaticMember) // -// CHECK: !DIDerivedType(tag: DW_TAG_member, name: "const_a" -// CHECK-NOT: size: -// CHECK-NOT: align: -// CHECK-NOT: offset: -// CHECK-SAME: flags: DIFlagStaticMember, -// CHECK-SAME: extraData: i1 true) - -// CHECK: !DIDerivedType(tag: DW_TAG_member, name: "const_b" -// CHECK-NOT: size: -// CHECK-NOT: align: -// CHECK-NOT: offset: -// CHECK-SAME: flags: DIFlagProtected | DIFlagStaticMember, -// CHECK-SAME: extraData: float 0x{{.*}}) +// CHECK: ![[CONST_A_DECL:[0-9]+]] = !DIDerivedType(tag: DW_TAG_member, name: "const_a" +// CHECK-NOT: size: +// CHECK-NOT: align: +// CHECK-NOT: offset: +// CHECK-SAME: flags: DIFlagStaticMember +// CHECK-NOT: extraData: + +// CHECK: ![[CONST_B_DECL:[0-9]+]] = !DIDerivedType(tag: DW_TAG_member, name: "const_b" +// CHECK-NOT: size: +// CHECK-NOT: align: +// CHECK-NOT: offset: +// CHECK-SAME: flags: DIFlagProtected | DIFlagStaticMember +// CHECK-NOT: extraData: // CHECK: ![[DECL_C:[0-9]+]] = !DIDerivedType(tag: DW_TAG_member, name: "c" // CHECK-NOT: size: @@ -83,12 +83,12 @@ int C::a = 4; // CHECK-NOT: offset: // CHECK-SAME: flags: DIFlagPublic | DIFlagStaticMember) // -// CHECK: !DIDerivedType(tag: DW_TAG_member, name: "const_c" -// CHECK-NOT: size: -// CHECK-NOT: align: -// CHECK-NOT: offset: -// CHECK-SAME: flags: DIFlagPublic | DIFlagStaticMember, -// CHECK-SAME: extraData: i32 18) +// CHECK: ![[CONST_C_DECL:[0-9]+]] = !DIDerivedType(tag: DW_TAG_member, name: "const_c" +// CHECK-NOT: size: +// CHECK-NOT: align: +// CHECK-NOT: offset: +// CHECK-SAME: flags: DIFlagPublic | DIFlagStaticMember +// CHECK-NOT: extraData: // // CHECK: !DIDerivedType(tag: DW_TAG_member, name: "x_a" // CHECK-SAME: flags: DIFlagPublic | DIFlagStaticMember) @@ -144,7 +144,7 @@ struct V { // const_va is not emitted for MS targets. // NOT-MS: !DIDerivedType(tag: DW_TAG_member, name: "const_va", // NOT-MS-SAME: line: [[@LINE-5]] -// NOT-MS-SAME: extraData: i32 42 +// NOT-MS-NOT: extraData: const int V::const_va; namespace x { @@ -156,3 +156,15 @@ struct y { }; int y::z; } + +// CHECK: !DIGlobalVariableExpression(var: ![[CONST_A_VAR:[0-9]+]], expr: !DIExpression(DW_OP_constu, 1, DW_OP_stack_value)) +// CHECK: ![[CONST_A_VAR]] = distinct !DIGlobalVariable(name: "const_a" +// CHECK-SAME: isLocal: true, isDefinition: true, declaration: ![[CONST_A_DECL]]) + +// CHECK: !DIGlobalVariableExpression(var: ![[CONST_B_VAR:[0-9]+]], expr: !DIExpression(DW_OP_constu, {{.*}}, DW_OP_stack_value)) +// CHECK: ![[CONST_B_VAR]] = distinct !DIGlobalVariable(name: "const_b" +// CHECK-SAME: isLocal: true, isDefinition: true, declaration: ![[CONST_B_DECL]]) + +// CHECK: !DIGlobalVariableExpression(var: ![[CONST_C_VAR:[0-9]+]], expr: !DIExpression(DW_OP_constu, 18, DW_OP_stack_value)) +// CHECK: ![[CONST_C_VAR]] = distinct !DIGlobalVariable(name: "const_c" +// CHECK-SAME: isLocal: true, isDefinition: true, declaration: ![[CONST_C_DECL]]) diff --git a/lldb/test/API/lang/cpp/const_static_integral_member/TestConstStaticIntegralMember.py b/lldb/test/API/lang/cpp/const_static_integral_member/TestConstStaticIntegralMember.py index 530191e8a37ba1..78ea23ac8f7061 100644 --- a/lldb/test/API/lang/cpp/const_static_integral_member/TestConstStaticIntegralMember.py +++ b/lldb/test/API/lang/cpp/const_static_integral_member/TestConstStaticIntegralMember.py @@ -102,9 +102,10 @@ def test(self): # it does not crash. self.expect("image lookup -t A") - # dsymutil strips the debug info for classes that only have const static - # data members without a definition namespace scope. - @expectedFailureAll(debug_info=["dsym"]) + # For debug-info produced by older versions of clang, dsymutil strips the + # debug info for classes that only have const static data members without + # definitions. + @expectedFailureAll(compiler=["clang"], compiler_version=["<", "18.0"]) def test_class_with_only_const_static(self): self.build() lldbutil.run_to_source_breakpoint( diff --git a/lldb/test/API/tools/lldb-dap/variables/TestDAP_variables.py b/lldb/test/API/tools/lldb-dap/variables/TestDAP_variables.py index 11d9bf18db7a6e..d2a21ad3cd1d44 100644 --- a/lldb/test/API/tools/lldb-dap/variables/TestDAP_variables.py +++ b/lldb/test/API/tools/lldb-dap/variables/TestDAP_variables.py @@ -87,10 +87,11 @@ def verify_values(self, verify_dict, actual, varref_dict=None, expression=None): def verify_variables(self, verify_dict, variables, varref_dict=None): for variable in variables: name = variable["name"] - self.assertIn( - name, verify_dict, 'variable "%s" in verify dictionary' % (name) - ) - self.verify_values(verify_dict[name], variable, varref_dict) + if not name.startswith("std::"): + self.assertIn( + name, verify_dict, 'variable "%s" in verify dictionary' % (name) + ) + self.verify_values(verify_dict[name], variable, varref_dict) def darwin_dwarf_missing_obj(self, initCommands): self.build(debug_info="dwarf")