[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