[clang] 25ac0d3 - DebugInfo: Implement the -gsimple-template-names functionality
David Blaikie via cfe-commits
cfe-commits at lists.llvm.org
Thu Sep 23 19:58:43 PDT 2021
Author: David Blaikie
Date: 2021-09-23T19:58:32-07:00
New Revision: 25ac0d3c73d68c017546eb622ba7632c6b581bfb
URL: https://github.com/llvm/llvm-project/commit/25ac0d3c73d68c017546eb622ba7632c6b581bfb
DIFF: https://github.com/llvm/llvm-project/commit/25ac0d3c73d68c017546eb622ba7632c6b581bfb.diff
LOG: DebugInfo: Implement the -gsimple-template-names functionality
This excludes certain names that can't be rebuilt from the available
DWARF:
* Atomic types - no DWARF differentiating int from atomic int.
* Vector types - enough DWARF (an attribute on the array type) to do
this, but I haven't written the extra code to add the attributes
required for this
* Lambdas - ambiguous with any other unnamed class
* Unnamed classes/enums - would need column info for the type in
addition to file/line number
* noexcept function types - not encoded in DWARF
Added:
clang/test/CodeGenCXX/debug-info-simple-template-names.cpp
Modified:
clang/lib/CodeGen/CGDebugInfo.cpp
Removed:
################################################################################
diff --git a/clang/lib/CodeGen/CGDebugInfo.cpp b/clang/lib/CodeGen/CGDebugInfo.cpp
index 5889647e2de96..7119402dcb64f 100644
--- a/clang/lib/CodeGen/CGDebugInfo.cpp
+++ b/clang/lib/CodeGen/CGDebugInfo.cpp
@@ -25,6 +25,7 @@
#include "clang/AST/DeclTemplate.h"
#include "clang/AST/Expr.h"
#include "clang/AST/RecordLayout.h"
+#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/Basic/CodeGenOptions.h"
#include "clang/Basic/FileManager.h"
#include "clang/Basic/SourceManager.h"
@@ -4855,11 +4856,173 @@ llvm::DIGlobalVariableExpression *CGDebugInfo::CollectAnonRecordDecls(
return GVE;
}
+namespace {
+struct ReconstitutableType : public RecursiveASTVisitor<ReconstitutableType> {
+ bool Reconstitutable = true;
+ bool VisitVectorType(VectorType *FT) {
+ Reconstitutable = false;
+ return false;
+ }
+ bool VisitAtomicType(AtomicType *FT) {
+ Reconstitutable = false;
+ return false;
+ }
+ bool TraverseEnumType(EnumType *ET) {
+ // Unnamed enums can't be reconstituted due to a lack of column info we
+ // produce in the DWARF, so we can't get Clang's full name back.
+ if (const auto *ED = dyn_cast<EnumDecl>(ET->getDecl())) {
+ if (!ED->getIdentifier()) {
+ Reconstitutable = false;
+ return false;
+ }
+ }
+ return true;
+ }
+ bool VisitFunctionProtoType(FunctionProtoType *FT) {
+ // noexcept is not encoded in DWARF, so the reversi
+ Reconstitutable &= !isNoexceptExceptionSpec(FT->getExceptionSpecType());
+ return !Reconstitutable;
+ }
+ bool TraverseRecordType(RecordType *RT) {
+ // Unnamed classes/lambdas can't be reconstituted due to a lack of column
+ // info we produce in the DWARF, so we can't get Clang's full name back.
+ // But so long as it's not one of those, it doesn't matter if some sub-type
+ // of the record (a template parameter) can't be reconstituted - because the
+ // un-reconstitutable type itself will carry its own name.
+ const auto *RD = dyn_cast<CXXRecordDecl>(RT->getDecl());
+ if (!RD)
+ return true;
+ if (RD->isLambda() || !RD->getIdentifier()) {
+ Reconstitutable = false;
+ return false;
+ }
+ return true;
+ }
+};
+} // anonymous namespace
+
+// Test whether a type name could be rebuilt from emitted debug info.
+static bool IsReconstitutableType(QualType QT) {
+ ReconstitutableType T;
+ T.TraverseType(QT);
+ return T.Reconstitutable;
+}
+
std::string CGDebugInfo::GetName(const Decl *D, bool Qualified) const {
std::string Name;
llvm::raw_string_ostream OS(Name);
- if (const NamedDecl *ND = dyn_cast<NamedDecl>(D))
- ND->getNameForDiagnostic(OS, getPrintingPolicy(), Qualified);
+ const NamedDecl *ND = dyn_cast<NamedDecl>(D);
+ if (!ND)
+ return Name;
+ codegenoptions::DebugTemplateNamesKind TemplateNamesKind =
+ CGM.getCodeGenOpts().getDebugSimpleTemplateNames();
+ Optional<TemplateArgs> Args;
+
+ bool IsOperatorOverload = false; // isa<CXXConversionDecl>(ND);
+ if (auto *RD = dyn_cast<CXXRecordDecl>(ND)) {
+ Args = GetTemplateArgs(RD);
+ } else if (auto *FD = dyn_cast<FunctionDecl>(ND)) {
+ Args = GetTemplateArgs(FD);
+ auto NameKind = ND->getDeclName().getNameKind();
+ IsOperatorOverload |=
+ NameKind == DeclarationName::CXXOperatorName ||
+ NameKind == DeclarationName::CXXConversionFunctionName;
+ } else if (auto *VD = dyn_cast<VarDecl>(ND)) {
+ Args = GetTemplateArgs(VD);
+ }
+ std::function<bool(ArrayRef<TemplateArgument>)> HasReconstitutableArgs =
+ [&](ArrayRef<TemplateArgument> Args) {
+ return llvm::all_of(Args, [&](const TemplateArgument &TA) {
+ switch (TA.getKind()) {
+ case TemplateArgument::Template:
+ // Easy to reconstitute - the value of the parameter in the debug
+ // info is the string name of the template. (so the template name
+ // itself won't benefit from any name rebuilding, but that's a
+ // representational limitation - maybe DWARF could be
+ // changed/improved to use some more structural representation)
+ return true;
+ case TemplateArgument::Declaration:
+ // Reference and pointer non-type template parameters point to
+ // variables, functions, etc and their value is, at best (for
+ // variables) represented as an address - not a reference to the
+ // DWARF describing the variable/function/etc. This makes it hard,
+ // possibly impossible to rebuild the original name - looking up the
+ // address in the executable file's symbol table would be needed.
+ return false;
+ case TemplateArgument::NullPtr:
+ // These could be rebuilt, but figured they're close enough to the
+ // declaration case, and not worth rebuilding.
+ return false;
+ case TemplateArgument::Pack:
+ // A pack is invalid if any of the elements of the pack are invalid.
+ return HasReconstitutableArgs(TA.getPackAsArray());
+ case TemplateArgument::Integral:
+ // Larger integers get encoded as DWARF blocks which are a bit
+ // harder to parse back into a large integer, etc - so punting on
+ // this for now. Re-parsing the integers back into APInt is probably
+ // feasible some day.
+ return TA.getAsIntegral().getBitWidth() <= 64;
+ case TemplateArgument::Type:
+ return IsReconstitutableType(TA.getAsType());
+ default:
+ llvm_unreachable("Other, unresolved, template arguments should "
+ "not be seen here");
+ }
+ });
+ };
+ // A conversion operator presents complications/ambiguity if there's a
+ // conversion to class template that is itself a template, eg:
+ // template<typename T>
+ // operator ns::t1<T, int>();
+ // This should be named, eg: "operator ns::t1<float, int><float>"
+ // (ignoring clang bug that means this is currently "operator t1<float>")
+ // but if the arguments were stripped, the consumer couldn't
diff erentiate
+ // whether the template argument list for the conversion type was the
+ // function's argument list (& no reconstitution was needed) or not.
+ // This could be handled if reconstitutable names had a separate attribute
+ // annotating them as such - this would remove the ambiguity.
+ //
+ // Alternatively the template argument list could be parsed enough to check
+ // whether there's one list or two, then compare that with the DWARF
+ // description of the return type and the template argument lists to determine
+ // how many lists there should be and if one is missing it could be assumed(?)
+ // to be the function's template argument list & then be rebuilt.
+ //
+ // Other operator overloads that aren't conversion operators could be
+ // reconstituted but would require a bit more nuance about detecting the
+ //
diff erence between these
diff erent operators during that rebuilding.
+ bool Reconstitutable =
+ Args && HasReconstitutableArgs(Args->Args) && !IsOperatorOverload;
+
+ PrintingPolicy PP = getPrintingPolicy();
+
+ if (TemplateNamesKind == codegenoptions::DebugTemplateNamesKind::Full ||
+ !Reconstitutable) {
+ ND->getNameForDiagnostic(OS, PP, Qualified);
+ } else {
+ bool Mangled =
+ TemplateNamesKind == codegenoptions::DebugTemplateNamesKind::Mangled;
+ // check if it's a template
+ if (Mangled)
+ OS << "_STN";
+
+ OS << ND->getDeclName();
+ std::string EncodedOriginalName;
+ llvm::raw_string_ostream EncodedOriginalNameOS(EncodedOriginalName);
+ EncodedOriginalNameOS << ND->getDeclName();
+
+ if (Mangled) {
+ OS << "|";
+ printTemplateArgumentList(OS, Args->Args, PP);
+ printTemplateArgumentList(EncodedOriginalNameOS, Args->Args, PP);
+#ifndef NDEBUG
+ std::string CanonicalOriginalName;
+ llvm::raw_string_ostream OriginalOS(CanonicalOriginalName);
+ ND->getNameForDiagnostic(OriginalOS, PP, Qualified);
+ assert(EncodedOriginalNameOS.str() == OriginalOS.str());
+#endif
+ }
+ }
return Name;
}
diff --git a/clang/test/CodeGenCXX/debug-info-simple-template-names.cpp b/clang/test/CodeGenCXX/debug-info-simple-template-names.cpp
new file mode 100644
index 0000000000000..a73c2982791e5
--- /dev/null
+++ b/clang/test/CodeGenCXX/debug-info-simple-template-names.cpp
@@ -0,0 +1,93 @@
+// RUN: %clang_cc1 -emit-llvm -triple x86_64-unknown_unknown -debug-info-kind=limited -gsimple-template-names=mangled %s -o - -w -std=c++17 | FileCheck %s
+// RUN: %clang_cc1 -emit-llvm -triple x86_64-unknown_unknown -debug-info-kind=limited -gsimple-template-names=simple %s -o - -w -std=c++17 | FileCheck --check-prefix=SIMPLE --implicit-check-not=_STN %s
+// RUN: %clang_cc1 -emit-llvm -triple x86_64-unknown_unknown -debug-info-kind=limited %s -o - -w -std=c++17 | FileCheck --check-prefix=FULL --implicit-check-not=_STN %s
+
+template <typename... T>
+void f1() {}
+template <typename T, T V>
+void f2() {}
+template <typename... T>
+struct t1 {};
+extern int x;
+int x;
+struct t2 {
+ template <typename T = float>
+ operator t1<int>() { __builtin_unreachable(); }
+};
+template <template <typename...> class T>
+void f3() {}
+void f() {
+ // Basic examples of simplifiable/rebuildable names
+ f1<>();
+ // CHECK: !DISubprogram(name: "_STNf1|<>",
+ // SIMPLE: !DISubprogram(name: "f1",
+ // FULL: !DISubprogram(name: "f1<>",
+ f1<int>();
+ // CHECK: !DISubprogram(name: "_STNf1|<int>",
+ f1<void()>();
+ // CHECK: !DISubprogram(name: "_STNf1|<void ()>",
+ f2<int, 42>();
+ // CHECK: !DISubprogram(name: "_STNf2|<int, 42>",
+
+ // Check that even though the nested name can't be rebuilt, it'll carry its
+ // full name and the outer name can be rebuilt from that.
+ f1<t1<void() noexcept>>();
+ // CHECK: !DISubprogram(name: "_STNf1|<t1<void () noexcept> >",
+
+ // Vector array types are encoded in DWARF but the decoding in llvm-dwarfdump
+ // isn't implemented yet.
+ f1<__attribute__((__vector_size__((sizeof(int) * 2)))) int>();
+ // CHECK: !DISubprogram(name: "f1<__attribute__((__vector_size__(2 * sizeof(int)))) int>",
+
+ // noexcept is part of function types in C++17 onwards, but not encoded in
+ // DWARF
+ f1<void() noexcept>();
+ // CHECK: !DISubprogram(name: "f1<void () noexcept>",
+
+ // Unnamed entities (lambdas, structs/classes, enums) can't be fully rebuilt
+ // since we don't emit the column number. Also lambdas and unnamed classes are
+ // ambiguous with each other - there's no DWARF that designates a lambda as
+ // anything other than another unnamed class/struct.
+ auto A = [] {};
+ f1<decltype(A)>();
+ // CHECK: !DISubprogram(name: "f1<(lambda at {{.*}}debug-info-simple-template-names.cpp:[[# @LINE - 2]]:12)>",
+ struct {
+ } unnamed_struct;
+ f1<decltype(unnamed_struct)>();
+ // CHECK: !DISubprogram(name: "f1<(unnamed struct at {{.*}}CodeGenCXX/debug-info-simple-template-names.cpp:[[# @LINE - 3]]:3)>",
+ enum {} unnamed_enum;
+ f1<decltype(unnamed_enum)>();
+ // CHECK: !DISubprogram(name: "f1<(unnamed enum at {{.*}}debug-info-simple-template-names.cpp:[[# @LINE - 2]]:3)>",
+
+ // Declarations can't readily be reversed as the value in the DWARF only
+ // contains the address of the value - we'd have to do symbol lookup to find
+ // the name of that value (& rely on it not having been stripped out, etc).
+ f2<int *, &x>();
+ // CHECK: !DISubprogram(name: "f2<int *, &x>",
+
+ // We could probably handle \/ this case, but since it's a small subset of
+ // pointer typed non-type-template parameters which can't be handled it
+ // doesn't seem high priority.
+ f2<decltype(nullptr), nullptr>();
+ // CHECK: !DISubprogram(name: "f2<std::nullptr_t, nullptr>",
+
+ // These larger constants are encoded as data blocks which makes them a bit
+ // harder to re-render. I think they might be missing sign information, or at
+ // maybe it's just a question of doing APInt things to render such large
+ // values. Punting on this for now.
+ f2<__int128, ((__int128)9223372036854775807) * 2>();
+ // CHECK: !DISubprogram(name: "f2<__int128, (__int128)18446744073709551614>",
+
+ t2().operator t1<int>();
+ // FIXME: This should be something like "operator t1<int><float>"
+ // CHECK: !DISubprogram(name: "operator t1<float>",
+
+ // Function pointer non-type-template parameters currently don't get any DWARF
+ // value (GCC doesn't provide one either) and even if there was a value, if
+ // it's like variable/pointer non-type template parameters, it couldn't be
+ // rebuilt anyway (see the note above for details on that) so we don't have to
+ // worry about seeing conversion operators as parameters to other templates.
+
+ f3<t1>();
+ // CHECK: !DISubprogram(name: "_STNf3|<t1>",
+}
More information about the cfe-commits
mailing list