Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reapply [lldb][DWARF] Delay struct/class/union definition DIE searching when parsing declaration DIEs. #92328

Merged
merged 4 commits into from
May 28, 2024

Conversation

ZequanWu
Copy link
Contributor

@ZequanWu ZequanWu commented May 15, 2024

This reapplies 9a7262c (#90663) and added #91808 as a fix.

It was causing tests on macos to fail because SymbolFileDWARF::GetForwardDeclCompilerTypeToDIE returned the map owned by this symol file. When there were two symbol files, two different maps were created for caching from compiler type to DIE even if they are for the same module. The solution is to do the same as SymbolFileDWARF::GetUniqueDWARFASTTypeMap: inquery SymbolFileDWARFDebugMap first to get the shared underlying SymbolFile so the map is shared among multiple SymbolFileDWARF.

ZequanWu added 2 commits May 15, 2024 13:58
…ng when parsing declaration DIEs.

This reapplies 9a7262c and a7eff59 with a fix.

It was causing tests on macos to fail because `SymbolFileDWARF::GetForwardDeclCompilerTypeToDIE` returned the map owned by this symol file. When there were two symbol files, two different maps were created for caching from compiler type to DIE even if they are for the same module. The solution is to do the same as `SymbolFileDWARF::GetUniqueDWARFASTTypeMap`: inquery SymbolFileDWARFDebugMap first to get the shared underlying SymbolFile so the map is shared among multiple SymbolFileDWARF.
…get the shared underlying SymbolFile when calling GetForwardDeclCompilerTypeToDIE
@ZequanWu
Copy link
Contributor Author

The second commit is the fix.

@llvmbot llvmbot added the lldb label May 15, 2024
@llvmbot
Copy link
Member

llvmbot commented May 15, 2024

@llvm/pr-subscribers-lldb

Author: Zequan Wu (ZequanWu)

Changes

This reapplies 9a7262c and a7eff59 with a fix.

It was causing tests on macos to fail because SymbolFileDWARF::GetForwardDeclCompilerTypeToDIE returned the map owned by this symol file. When there were two symbol files, two different maps were created for caching from compiler type to DIE even if they are for the same module. The solution is to do the same as SymbolFileDWARF::GetUniqueDWARFASTTypeMap: inquery SymbolFileDWARFDebugMap first to get the shared underlying SymbolFile so the map is shared among multiple SymbolFileDWARF.


Patch is 60.92 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/92328.diff

11 Files Affected:

  • (modified) lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParser.h (+2)
  • (modified) lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp (+223-179)
  • (modified) lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.h (+88-109)
  • (modified) lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp (+32-19)
  • (modified) lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h (+8-7)
  • (modified) lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.h (+9)
  • (modified) lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDwo.cpp (+1-1)
  • (modified) lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDwo.h (+2-1)
  • (modified) lldb/source/Plugins/SymbolFile/DWARF/UniqueDWARFASTType.cpp (+54-53)
  • (modified) lldb/source/Plugins/SymbolFile/DWARF/UniqueDWARFASTType.h (+13-23)
  • (added) lldb/test/Shell/SymbolFile/DWARF/delayed-definition-die-searching.test (+36)
diff --git a/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParser.h b/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParser.h
index 66db396279e06..e144cf0f9bd94 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParser.h
+++ b/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParser.h
@@ -60,6 +60,8 @@ class DWARFASTParser {
 
   virtual ConstString GetDIEClassTemplateParams(const DWARFDIE &die) = 0;
 
+  virtual lldb_private::Type *FindDefinitionTypeForDIE(const DWARFDIE &die) = 0;
+
   static std::optional<SymbolFile::ArrayInfo>
   ParseChildArrayInfo(const DWARFDIE &parent_die,
                       const ExecutionContext *exe_ctx = nullptr);
diff --git a/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp b/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp
index f8101aba5c627..2a46be9216121 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp
+++ b/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp
@@ -154,6 +154,26 @@ static bool TagIsRecordType(dw_tag_t tag) {
   }
 }
 
+static bool IsForwardDeclaration(const DWARFDIE &die,
+                                 const ParsedDWARFTypeAttributes &attrs,
+                                 LanguageType cu_language) {
+  if (attrs.is_forward_declaration)
+    return true;
+
+  // Work around an issue with clang at the moment where forward
+  // declarations for objective C classes are emitted as:
+  //  DW_TAG_structure_type [2]
+  //  DW_AT_name( "ForwardObjcClass" )
+  //  DW_AT_byte_size( 0x00 )
+  //  DW_AT_decl_file( "..." )
+  //  DW_AT_decl_line( 1 )
+  //
+  // Note that there is no DW_AT_declaration and there are no children,
+  // and the byte size is zero.
+  return attrs.byte_size && *attrs.byte_size == 0 && attrs.name &&
+         !die.HasChildren() && cu_language == eLanguageTypeObjC;
+}
+
 TypeSP DWARFASTParserClang::ParseTypeFromClangModule(const SymbolContext &sc,
                                                      const DWARFDIE &die,
                                                      Log *log) {
@@ -249,11 +269,9 @@ static void ForcefullyCompleteType(CompilerType type) {
 /// This function serves a similar purpose as RequireCompleteType above, but it
 /// avoids completing the type if it is not immediately necessary. It only
 /// ensures we _can_ complete the type later.
-static void PrepareContextToReceiveMembers(TypeSystemClang &ast,
-                                           ClangASTImporter &ast_importer,
-                                           clang::DeclContext *decl_ctx,
-                                           DWARFDIE die,
-                                           const char *type_name_cstr) {
+void DWARFASTParserClang::PrepareContextToReceiveMembers(
+    clang::DeclContext *decl_ctx, const DWARFDIE &decl_ctx_die,
+    const DWARFDIE &die, const char *type_name_cstr) {
   auto *tag_decl_ctx = clang::dyn_cast<clang::TagDecl>(decl_ctx);
   if (!tag_decl_ctx)
     return; // Non-tag context are always ready.
@@ -268,7 +286,8 @@ static void PrepareContextToReceiveMembers(TypeSystemClang &ast,
   // gmodules case), we can complete the type by doing a full import.
 
   // If this type was not imported from an external AST, there's nothing to do.
-  CompilerType type = ast.GetTypeForDecl(tag_decl_ctx);
+  CompilerType type = m_ast.GetTypeForDecl(tag_decl_ctx);
+  ClangASTImporter &ast_importer = GetClangASTImporter();
   if (type && ast_importer.CanImport(type)) {
     auto qual_type = ClangUtil::GetQualType(type);
     if (ast_importer.RequireCompleteType(qual_type))
@@ -279,6 +298,13 @@ static void PrepareContextToReceiveMembers(TypeSystemClang &ast,
         type_name_cstr ? type_name_cstr : "", die.GetOffset());
   }
 
+  // By searching for the definition DIE of the decl_ctx type, we will either:
+  // 1. Found the the definition DIE and start its definition with
+  // TypeSystemClang::StartTagDeclarationDefinition.
+  // 2. Unable to find it, then need to forcefully complete it.
+  FindDefinitionTypeForDIE(decl_ctx_die);
+  if (tag_decl_ctx->isCompleteDefinition() || tag_decl_ctx->isBeingDefined())
+    return;
   // We don't have a type definition and/or the import failed. We must
   // forcefully complete the type to avoid crashes.
   ForcefullyCompleteType(type);
@@ -620,10 +646,11 @@ DWARFASTParserClang::ParseTypeModifier(const SymbolContext &sc,
   if (tag == DW_TAG_typedef) {
     // DeclContext will be populated when the clang type is materialized in
     // Type::ResolveCompilerType.
-    PrepareContextToReceiveMembers(
-        m_ast, GetClangASTImporter(),
-        GetClangDeclContextContainingDIE(die, nullptr), die,
-        attrs.name.GetCString());
+    DWARFDIE decl_ctx_die;
+    clang::DeclContext *decl_ctx =
+        GetClangDeclContextContainingDIE(die, &decl_ctx_die);
+    PrepareContextToReceiveMembers(decl_ctx, decl_ctx_die, die,
+                                   attrs.name.GetCString());
 
     if (attrs.type.IsValid()) {
       // Try to parse a typedef from the (DWARF embedded in the) Clang
@@ -1103,32 +1130,6 @@ DWARFASTParserClang::ParseSubroutine(const DWARFDIE &die,
         // struct and see if this is actually a C++ method
         Type *class_type = dwarf->ResolveType(decl_ctx_die);
         if (class_type) {
-          if (class_type->GetID() != decl_ctx_die.GetID() ||
-              IsClangModuleFwdDecl(decl_ctx_die)) {
-
-            // We uniqued the parent class of this function to another
-            // class so we now need to associate all dies under
-            // "decl_ctx_die" to DIEs in the DIE for "class_type"...
-            DWARFDIE class_type_die = dwarf->GetDIE(class_type->GetID());
-
-            if (class_type_die) {
-              std::vector<DWARFDIE> failures;
-
-              CopyUniqueClassMethodTypes(decl_ctx_die, class_type_die,
-                                         class_type, failures);
-
-              // FIXME do something with these failures that's
-              // smarter than just dropping them on the ground.
-              // Unfortunately classes don't like having stuff added
-              // to them after their definitions are complete...
-
-              Type *type_ptr = dwarf->GetDIEToType()[die.GetDIE()];
-              if (type_ptr && type_ptr != DIE_IS_BEING_PARSED) {
-                return type_ptr->shared_from_this();
-              }
-            }
-          }
-
           if (attrs.specification.IsValid()) {
             // We have a specification which we are going to base our
             // function prototype off of, so we need this type to be
@@ -1263,6 +1264,39 @@ DWARFASTParserClang::ParseSubroutine(const DWARFDIE &die,
               }
             }
           }
+          // By here, we should have already completed the c++ class_type
+          // because if either specification or abstract_origin is present, we
+          // call GetClangDeclContextForDIE to resolve the DW_TAG_subprogram
+          // refered by this one until we reached the DW_TAG_subprogram without
+          // specification or abstract_origin (the else branch above). Then the
+          // above GetFullCompilerType() will complete the class_type if it's
+          // not completed yet. After that, we will have the mapping from DIEs
+          // in class_type_die to DeclContexts in m_die_to_decl_ctx.
+          if (class_type->GetID() != decl_ctx_die.GetID() ||
+              IsClangModuleFwdDecl(decl_ctx_die)) {
+
+            // We uniqued the parent class of this function to another
+            // class so we now need to associate all dies under
+            // "decl_ctx_die" to DIEs in the DIE for "class_type"...
+            DWARFDIE class_type_die = dwarf->GetDIE(class_type->GetID());
+
+            if (class_type_die) {
+              std::vector<DWARFDIE> failures;
+
+              CopyUniqueClassMethodTypes(decl_ctx_die, class_type_die,
+                                         class_type, failures);
+
+              // FIXME do something with these failures that's
+              // smarter than just dropping them on the ground.
+              // Unfortunately classes don't like having stuff added
+              // to them after their definitions are complete...
+
+              Type *type_ptr = dwarf->GetDIEToType()[die.GetDIE()];
+              if (type_ptr && type_ptr != DIE_IS_BEING_PARSED) {
+                return type_ptr->shared_from_this();
+              }
+            }
+          }
         }
       }
     }
@@ -1635,6 +1669,93 @@ DWARFASTParserClang::GetCPlusPlusQualifiedName(const DWARFDIE &die) {
   return qualified_name;
 }
 
+lldb_private::Type *
+DWARFASTParserClang::FindDefinitionTypeForDIE(const DWARFDIE &die) {
+  SymbolFileDWARF *dwarf = die.GetDWARF();
+  ParsedDWARFTypeAttributes attrs(die);
+  bool is_forward_declaration = IsForwardDeclaration(
+      die, attrs, SymbolFileDWARF::GetLanguage(*die.GetCU()));
+  if (!is_forward_declaration)
+    return dwarf->GetDIEToType()[die.GetDIE()];
+
+  const dw_tag_t tag = die.Tag();
+  TypeSP type_sp;
+  Log *log = GetLog(DWARFLog::TypeCompletion | DWARFLog::Lookups);
+  if (log) {
+    dwarf->GetObjectFile()->GetModule()->LogMessage(
+        log,
+        "SymbolFileDWARF({0:p}) - {1:x16}: {2} type \"{3}\" is a "
+        "forward declaration DIE, trying to find definition DIE",
+        static_cast<void *>(this), die.GetOffset(), DW_TAG_value_to_name(tag),
+        attrs.name.GetCString());
+  }
+  // We haven't parse definition die for this type, starting to search for it.
+  // After we found the definition die, the GetDeclarationDIEToDefinitionDIE()
+  // map will have the new mapping from this declaration die to definition die.
+  if (attrs.class_language == eLanguageTypeObjC ||
+      attrs.class_language == eLanguageTypeObjC_plus_plus) {
+    if (!attrs.is_complete_objc_class &&
+        die.Supports_DW_AT_APPLE_objc_complete_type()) {
+      // We have a valid eSymbolTypeObjCClass class symbol whose name
+      // matches the current objective C class that we are trying to find
+      // and this DIE isn't the complete definition (we checked
+      // is_complete_objc_class above and know it is false), so the real
+      // definition is in here somewhere
+      type_sp =
+          dwarf->FindCompleteObjCDefinitionTypeForDIE(die, attrs.name, true);
+
+      if (!type_sp) {
+        SymbolFileDWARFDebugMap *debug_map_symfile =
+            dwarf->GetDebugMapSymfile();
+        if (debug_map_symfile) {
+          // We weren't able to find a full declaration in this DWARF,
+          // see if we have a declaration anywhere else...
+          type_sp = debug_map_symfile->FindCompleteObjCDefinitionTypeForDIE(
+              die, attrs.name, true);
+        }
+      }
+
+      if (type_sp && log) {
+        dwarf->GetObjectFile()->GetModule()->LogMessage(
+            log,
+            "SymbolFileDWARF({0:p}) - {1:x16}: {2} ({3}) type \"{4}\" is an "
+            "incomplete objc type, complete type is {5:x8}",
+            static_cast<void *>(this), die.GetOffset(),
+            DW_TAG_value_to_name(tag), tag, attrs.name.GetCString(),
+            type_sp->GetID());
+      }
+    }
+  }
+
+  type_sp = dwarf->FindDefinitionTypeForDWARFDeclContext(die);
+  if (!type_sp) {
+    SymbolFileDWARFDebugMap *debug_map_symfile = dwarf->GetDebugMapSymfile();
+    if (debug_map_symfile) {
+      // We weren't able to find a full declaration in this DWARF, see
+      // if we have a declaration anywhere else...
+      type_sp = debug_map_symfile->FindDefinitionTypeForDWARFDeclContext(die);
+    }
+    if (type_sp && log) {
+      dwarf->GetObjectFile()->GetModule()->LogMessage(
+          log,
+          "SymbolFileDWARF({0:p}) - {1:x16}: {2} type \"{3}\" is a "
+          "forward declaration, complete type is {4:x8}",
+          static_cast<void *>(this), die.GetOffset(), DW_TAG_value_to_name(tag),
+          attrs.name.GetCString(), type_sp->GetID());
+    }
+  }
+
+  if (!type_sp && log) {
+    dwarf->GetObjectFile()->GetModule()->LogMessage(
+        log,
+        "SymbolFileDWARF({0:p}) - {1:x16}: {2} type \"{3}\" is a "
+        "forward declaration, unable to find definition DIE for it",
+        static_cast<void *>(this), die.GetOffset(), DW_TAG_value_to_name(tag),
+        attrs.name.GetCString());
+  }
+  return type_sp.get();
+}
+
 TypeSP
 DWARFASTParserClang::ParseStructureLikeDIE(const SymbolContext &sc,
                                            const DWARFDIE &die,
@@ -1646,14 +1767,10 @@ DWARFASTParserClang::ParseStructureLikeDIE(const SymbolContext &sc,
   LanguageType cu_language = SymbolFileDWARF::GetLanguage(*die.GetCU());
   Log *log = GetLog(DWARFLog::TypeCompletion | DWARFLog::Lookups);
 
-  // UniqueDWARFASTType is large, so don't create a local variables on the
-  // stack, put it on the heap. This function is often called recursively and
-  // clang isn't good at sharing the stack space for variables in different
-  // blocks.
-  auto unique_ast_entry_up = std::make_unique<UniqueDWARFASTType>();
-
   ConstString unique_typename(attrs.name);
   Declaration unique_decl(attrs.decl);
+  uint64_t byte_size = attrs.byte_size.value_or(0);
+  attrs.is_forward_declaration = IsForwardDeclaration(die, attrs, cu_language);
 
   if (attrs.name) {
     if (Language::LanguageIsCPlusPlus(cu_language)) {
@@ -1666,14 +1783,42 @@ DWARFASTParserClang::ParseStructureLikeDIE(const SymbolContext &sc,
       unique_decl.Clear();
     }
 
-    if (dwarf->GetUniqueDWARFASTTypeMap().Find(
-            unique_typename, die, unique_decl, attrs.byte_size.value_or(-1),
-            *unique_ast_entry_up)) {
-      type_sp = unique_ast_entry_up->m_type_sp;
+    if (UniqueDWARFASTType *unique_ast_entry_type =
+            dwarf->GetUniqueDWARFASTTypeMap().Find(
+                unique_typename, die, unique_decl, byte_size,
+                attrs.is_forward_declaration)) {
+      type_sp = unique_ast_entry_type->m_type_sp;
       if (type_sp) {
         dwarf->GetDIEToType()[die.GetDIE()] = type_sp.get();
         LinkDeclContextToDIE(
-            GetCachedClangDeclContextForDIE(unique_ast_entry_up->m_die), die);
+            GetCachedClangDeclContextForDIE(unique_ast_entry_type->m_die), die);
+        if (!attrs.is_forward_declaration) {
+          // If the DIE being parsed in this function is a definition and the
+          // entry in the map is a declaration, then we need to update the entry
+          // to point to the definition DIE.
+          if (unique_ast_entry_type->m_is_forward_declaration) {
+            unique_ast_entry_type->m_die = die;
+            unique_ast_entry_type->m_byte_size = byte_size;
+            unique_ast_entry_type->m_declaration = unique_decl;
+            unique_ast_entry_type->m_is_forward_declaration = false;
+            // Need to update Type ID to refer to the definition DIE. because
+            // it's used in ParseSubroutine to determine if we need to copy cxx
+            // method types from a declaration DIE to this definition DIE.
+            type_sp->SetID(die.GetID());
+            clang_type = type_sp->GetForwardCompilerType();
+            if (attrs.class_language != eLanguageTypeObjC &&
+                attrs.class_language != eLanguageTypeObjC_plus_plus)
+              TypeSystemClang::StartTagDeclarationDefinition(clang_type);
+
+            CompilerType compiler_type_no_qualifiers =
+                ClangUtil::RemoveFastQualifiers(clang_type);
+            auto result = dwarf->GetForwardDeclCompilerTypeToDIE().try_emplace(
+                compiler_type_no_qualifiers.GetOpaqueQualType(),
+                *die.GetDIERef());
+            if (!result.second)
+              result.first->second = *die.GetDIERef();
+          }
+        }
         return type_sp;
       }
     }
@@ -1695,125 +1840,21 @@ DWARFASTParserClang::ParseStructureLikeDIE(const SymbolContext &sc,
     default_accessibility = eAccessPrivate;
   }
 
-  if (attrs.byte_size && *attrs.byte_size == 0 && attrs.name &&
-      !die.HasChildren() && cu_language == eLanguageTypeObjC) {
-    // Work around an issue with clang at the moment where forward
-    // declarations for objective C classes are emitted as:
-    //  DW_TAG_structure_type [2]
-    //  DW_AT_name( "ForwardObjcClass" )
-    //  DW_AT_byte_size( 0x00 )
-    //  DW_AT_decl_file( "..." )
-    //  DW_AT_decl_line( 1 )
-    //
-    // Note that there is no DW_AT_declaration and there are no children,
-    // and the byte size is zero.
-    attrs.is_forward_declaration = true;
-  }
-
-  if (attrs.class_language == eLanguageTypeObjC ||
-      attrs.class_language == eLanguageTypeObjC_plus_plus) {
-    if (!attrs.is_complete_objc_class &&
-        die.Supports_DW_AT_APPLE_objc_complete_type()) {
-      // We have a valid eSymbolTypeObjCClass class symbol whose name
-      // matches the current objective C class that we are trying to find
-      // and this DIE isn't the complete definition (we checked
-      // is_complete_objc_class above and know it is false), so the real
-      // definition is in here somewhere
-      type_sp =
-          dwarf->FindCompleteObjCDefinitionTypeForDIE(die, attrs.name, true);
-
-      if (!type_sp) {
-        SymbolFileDWARFDebugMap *debug_map_symfile =
-            dwarf->GetDebugMapSymfile();
-        if (debug_map_symfile) {
-          // We weren't able to find a full declaration in this DWARF,
-          // see if we have a declaration anywhere else...
-          type_sp = debug_map_symfile->FindCompleteObjCDefinitionTypeForDIE(
-              die, attrs.name, true);
-        }
-      }
-
-      if (type_sp) {
-        if (log) {
-          dwarf->GetObjectFile()->GetModule()->LogMessage(
-              log,
-              "SymbolFileDWARF({0:p}) - {1:x16}: {2} ({3}) type \"{4}\" is an "
-              "incomplete objc type, complete type is {5:x8}",
-              static_cast<void *>(this), die.GetOffset(),
-              DW_TAG_value_to_name(tag), tag, attrs.name.GetCString(),
-              type_sp->GetID());
-        }
-
-        // We found a real definition for this type elsewhere so lets use
-        // it and cache the fact that we found a complete type for this
-        // die
-        dwarf->GetDIEToType()[die.GetDIE()] = type_sp.get();
-        return type_sp;
-      }
-    }
-  }
-
   if (attrs.is_forward_declaration) {
-    // We have a forward declaration to a type and we need to try and
-    // find a full declaration. We look in the current type index just in
-    // case we have a forward declaration followed by an actual
-    // declarations in the DWARF. If this fails, we need to look
-    // elsewhere...
-    if (log) {
-      dwarf->GetObjectFile()->GetModule()->LogMessage(
-          log,
-          "SymbolFileDWARF({0:p}) - {1:x16}: {2} ({3}) type \"{4}\" is a "
-          "forward declaration, trying to find complete type",
-          static_cast<void *>(this), die.GetOffset(), DW_TAG_value_to_name(tag),
-          tag, attrs.name.GetCString());
-    }
-
     // See if the type comes from a Clang module and if so, track down
     // that type.
     type_sp = ParseTypeFromClangModule(sc, die, log);
     if (type_sp)
       return type_sp;
-
-    // type_sp = FindDefinitionTypeForDIE (dwarf_cu, die,
-    // type_name_const_str);
-    type_sp = dwarf->FindDefinitionTypeForDWARFDeclContext(die);
-
-    if (!type_sp) {
-      SymbolFileDWARFDebugMap *debug_map_symfile = dwarf->GetDebugMapSymfile();
-      if (debug_map_symfile) {
-        // We weren't able to find a full declaration in this DWARF, see
-        // if we have a declaration anywhere else...
-        type_sp = debug_map_symfile->FindDefinitionTypeForDWARFDeclContext(die);
-      }
-    }
-
-    if (type_sp) {
-      if (log) {
-        dwarf->GetObjectFile()->GetModule()->LogMessage(
-            log,
-            "SymbolFileDWARF({0:p}) - {1:x16}: {2} ({3}) type \"{4}\" is a "
-            "forward declaration, complete type is {5:x8}",
-            static_cast<void *>(this), die.GetOffset(),
-            DW_TAG_value_to_name(tag), tag, attrs.name.GetCString(),
-            type_sp->GetID());
-      }
-
-      // We found a real definition for this type elsewhere so lets use
-      // it and cache the fact that we found a complete type for this die
-      dwarf->GetDIEToType()[die.GetDIE()] = type_sp.get();
-      clang::DeclContext *defn_decl_ctx =
-          GetCachedClangDeclContextForDIE(dwarf->GetDIE(type_sp->GetID...
[truncated]

@@ -2306,6 +2345,11 @@ bool DWARFASTParserClang::CompleteTypeFromDWARF(const DWARFDIE &die,

if (!die)
return false;
ParsedDWARFTypeAttributes attrs(die);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've been wondering how expensive constructing this object is. On a brief glance it seems to be doing a lot of work, but maybe all of that work is actually not that slow? Do you know why we get here for forward declarations with your patch, but didn't before?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This extra check was added in #91799 to ensure we don't accidentally parse declaration DIE, which was reported at #90663 (comment).

By checking ParsedDWARFTypeAttributes's constructor, looks like it just parses the DIE's attributes, iterates through them, and updates its fields accordingly.

Copy link
Contributor Author

@ZequanWu ZequanWu May 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parsing happens every time when constructing this object, which makes it a bit expensive, should we add a new field DWARFAttributes m_attributes in DWARFBaseDIE, so that we only parse it once? From a glance at calls to DWARFBaseDIE::GetAttributes, there are more than 10 calls to it. The attribute parsing is repetitive.

Probably not, the DWARFAttributes object is large, 336 bytes.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably not, the DWARFAttributes object is large, 336 bytes.

I think that would be very wasteful, since we're just accessing these attributes once (well, maybe twice). After we've constructed the type, we should never need to look at them again, as all the information has been translated to the clang ast.

This extra check was added in #91799 to ensure we don't accidentally parse declaration DIE

How exactly do we get here in that case? Presumably we still go through ParseTypesFromDWARF (where this property is checked already), is that right? Could we make a note somewhere (perhaps by putting a fake/invalid DIE into the type-to-die map) that the die is not a definition?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How exactly do we get here in that case?

From #90663 (comment), .debug_names somehow contains entries that are declarations. This causes SymbolFileDWARF::FindDefinitionTypeForDWARFDeclContext to return a type created from declaration when searching for definition.

A simple idea I have in mind is to make the GetForwardDeclCompilerTypeToDIE's value type to a pair {DIERef, bool}, and the bool indicate if this is a definition or not. So we know that info without extra attribute parsing. How do you think?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How exactly do we get here in that case?

From #90663 (comment), .debug_names somehow contains entries that are declarations. This causes SymbolFileDWARF::FindDefinitionTypeForDWARFDeclContext to return a type created from declaration when searching for definition.

I've re-read that. The debug names situation is troubling, but the thing I don't understand now why is this even specific to debug_names. After this patch, it should be fairly easy to trigger a situation where we're asked to complete a type, and we don't have the definition of that type. Why don't those cases lead to a crash? For example, what would happen if the name was simply not in the index?

If I ignored this entire discussion, I would say that this check makes perfect sense: since we're delaying the search for the definition, it can happen that the we don't have the definition die at the point when we're asked to complete the type. So it seems perfectly reasonable to have this check somewhere. I just want to check whether this is the right place.

A simple idea I have in mind is to make the GetForwardDeclCompilerTypeToDIE's value type to a pair {DIERef, bool}, and the bool indicate if this is a definition or not. So we know that info without extra attribute parsing. How do you think?

Given that this extra bool is going to cause us to use eight more bytes per type, this doesn't necessarily seem like. This would use more memory, the previous implementation would use more time. Hard to say which one is better without some very precise measurements. Choosing blindly, I would stick with the current implementation, as it's easier to optimize the cpu time than it is to reclaim that memory (the real problem here is that ParsedDWARFTypeAttributes was optimized for the use case where one is going to use most of the attributes it has parsed (which is what happens in ParseTypeFromDWARF). Using it to essentially just check for the presence of DW_AT_declaration is wasteful, and you could write a much faster implementation if you were writing it for this use case specifically (but it's also not clear whether it's worthwhile to have a brand new implementation for this use case).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why don't those cases lead to a crash? For example, what would happen if the name was simply not in the index?

That is specific to debug_names because the index gives us a declaration DIE when we are searching for definition DIE in 'FindDefinitionTypeForDWARFDeclContext'. Before, we didn't have the extra check, so we tries to complete the type from declaration DIE, which triggers an assertion in clang. However, it doesn't happen in manual index because we already explicitly checked DW_AT_declaration attributes when creating the manual index. It's guaranteed to find a definition DIE from FindDefinitionTypeForDWARFDeclContext or nothing (early bailout, won't try to complete it).

So it seems perfectly reasonable to have this check somewhere. I just want to check whether this is the right place.

I assume Greg's change at #91808 will also fix this problem by skipping forward declaration DIE when processing it, which is earlier check than this extra check added here.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why don't those cases lead to a crash? For example, what would happen if the name was simply not in the index?

That is specific to debug_names because the index gives us a declaration DIE when we are searching for definition DIE in 'FindDefinitionTypeForDWARFDeclContext'. Before, we didn't have the extra check, so we tries to complete the type from declaration DIE, which triggers an assertion in clang. However, it doesn't happen in manual index because we already explicitly checked DW_AT_declaration attributes when creating the manual index. It's guaranteed to find a definition DIE from FindDefinitionTypeForDWARFDeclContext or nothing (early bailout, won't try to complete it).

Okay, so when I said "specific to debug_names", I was mainly thinking of the case where the index doesn't find anything (i.e., we attempt to complete a type and the index (any kind of index) says we can't, because it doesn't have the definition). Based on what you've said (and some more pondering), I think can now answer that question for myself (please let me know if this is wrong):

The reason why this crash does not happen when attempting to complete a type with no definition is because this function is the actual implementation of "completing a type from a defintion". I.e., it expects to be called with a definition DIE, and will never be called if there is no definition (we will bail out, and possibly "forcefully" complete the type somewhere higher up the stack).

If that is true, then I think Greg's patch is a better fix for this problem, and it also sidesteps all of the performance/memory tradeoff discussions.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason why this crash does not happen when attempting to complete a type with no definition is because this function is the actual implementation of "completing a type from a defintion". I.e., it expects to be called with a definition DIE, and will never be called if there is no definition (we will bail out, and possibly "forcefully" complete the type somewhere higher up the stack).

Yes, it should only be called with definition DIE. This extra check just a double-check for it with a bit performance cost. If there's no definition DIE, SymbolFileDWARF::CompleteType does an early return. This behaviour is unchanged. Forceful completion behaviour is also remained unchanged, happens when failed to find definition DIE of a containing record type.

If that is true, then I think Greg's patch is a better fix for this problem, and it also sidesteps all of the performance/memory tradeoff discussions.

I agree. If we can ensure the DIE is always a definition DIE for both manual index and debug_names index at a earlier time, there's no need for this check. I'll remove this check when Greg's change is in.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SG

// A map from CompilerType to the struct/class/union/enum DIE (might be a
// declaration or a definition) that is used to construct it.
llvm::DenseMap<lldb::opaque_compiler_type_t, DIERef>
m_forward_decl_compiler_type_to_die;
Copy link
Member

@Michael137 Michael137 May 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you elaborate on why this wasn't necessary before? We're now mixing CompilerType's (which originate from different ASTContext's) in the same map, which might not be a problem (since we do this for UniqueDWARFASTTypeMap already, whose Types can also originate from different context's) but I would still like to understand it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TL;DR: This is because this change let UniqueDWARFASTTypeMap to cache created Type from declaration DIE. Before this, it was only used for caching Type created from definition DIE. And UniqueDWARFASTTypeMap is shared among all SymbolFileDWARFs belongs to one SymbolFileDWARFDebugMap, so should m_forward_decl_compiler_type_to_die which interacts with it.

Here's an example with debug map used:
The declaration DIE for bar is in foo.o and the definition DIE is in main.o. ParseStructureLikeDIE was firstly asked to parse the declaration DIE.

Before, it will always find the definition DIE in main.o and insert the CompilerType to definition DIE to m_forward_decl_compiler_type_to_die which belongs to SymbolFileDWARF(main.o). When TypeSystemClang::CompleteTagDecl wants to complete bar, it asks SymbolFileDWARFDebugMap::CompleteType to complete, which iterates all its SymbolFileDWARF(main.o, foo.o) and check if the any of them has the compiler type in their maps:

if (oso_dwarf->HasForwardDeclForCompilerType(compiler_type)) {
oso_dwarf->CompleteType(compiler_type);
success = true;
return IterationAction::Stop;
}
. If exists, then it assumes that symbol file should have the definition DIE and able to complete it. Since bar's compiler type exists in symbol file(main.o)'s map which also has the definition DIE as value, the type completion will success.

If I don't add the fix, we have [bar's compiler type -> bar's declaration DIE] in foo.o's map. When searching for definition DIE, we found that in main.o. Because we have already created its Type from declaration, the UniqueDWARFASTTypeMap will find the entry. Then it updates the entry to points to the definition DIE. It updates main.o's m_forward_decl_compiler_type_to_die to pointing to the definition DIE, which is wrong, since there's no such entry in main.o's map. It should update foo.o's map. The result is that SymbolFileDWARFDebugMap::CompleteType find bar's compiler type exists in foo.o and ask foo.o's symbol file to complete it, but it only has declaration DIE.

The fix is to share one m_forward_decl_compiler_type_to_die among one SymbolFileDWARFDebugMap. With this, when creating compiler type to declaration DIE in the map or updating an entry to point to a definition DIE, we are operating in the same map.

@@ -2306,6 +2345,11 @@ bool DWARFASTParserClang::CompleteTypeFromDWARF(const DWARFDIE &die,

if (!die)
return false;
ParsedDWARFTypeAttributes attrs(die);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SG

@ZequanWu
Copy link
Contributor Author

I removed the checking for DW_AT_declaration attributes when completing type from a DIE and applied #91808 to here to ensure we always have a definition DIE at that point.

Copy link

github-actions bot commented May 28, 2024

✅ With the latest revision this PR passed the C/C++ code formatter.

@ZequanWu ZequanWu merged commit 51dd4ea into llvm:main May 28, 2024
5 checks passed
@ZequanWu ZequanWu deleted the reapply-delay-definition-die-search branch May 28, 2024 15:58
vg0204 pushed a commit to vg0204/llvm-project that referenced this pull request May 29, 2024
…ing when parsing declaration DIEs. (llvm#92328)

This reapplies
llvm@9a7262c
(llvm#90663) and added llvm#91808 as a
fix.

It was causing tests on macos to fail because
`SymbolFileDWARF::GetForwardDeclCompilerTypeToDIE` returned the map
owned by this symol file. When there were two symbol files, two
different maps were created for caching from compiler type to DIE even
if they are for the same module. The solution is to do the same as
`SymbolFileDWARF::GetUniqueDWARFASTTypeMap`: inquery
SymbolFileDWARFDebugMap first to get the shared underlying SymbolFile so
the map is shared among multiple SymbolFileDWARF.
@Michael137
Copy link
Member

Unfortunately this breaks TestCPPAccelerator.py on the macOS buildbots: https://green.lab.llvm.org/job/llvm.org/view/LLDB/job/as-lldb-cmake/4788/execution/node/97/log/

Ran command:
"log enable dwarf lookups -f/Users/ec2-user/jenkins/workspace/llvm.org/as-lldb-cmake/lldb-build/lldb-test-build.noindex/lang/cpp/accelerator-table/TestCPPAccelerator.test_dwarf/dwarf.log"

Got output:


runCmd: frame variable inner_d

Assertion failed: (DD && "queried property of class with no definition"), function data, file DeclCXX.h, line 464.
PLEASE submit a bug report to https://github.com/llvm/llvm-project/issues/ and include the crash backtrace.
Stack dump:
0.	HandleCommand(command = "frame variable inner_d")

The stacktrace I got when locally reproducing this:

Thread 0 Crashed::  Dispatch queue: com.apple.main-thread
0   libsystem_kernel.dylib        	       0x18e0a1540 __pthread_kill + 8
1   libsystem_pthread.dylib       	       0x18e0d9bcc pthread_kill + 288
2   libsystem_c.dylib             	       0x18dfe6964 __abort + 136
3   libsystem_c.dylib             	       0x18dfe68dc abort + 140
4   libsystem_c.dylib             	       0x18dfe5bc8 __assert_rtn + 284
5   liblldb.19.0.0git.dylib       	       0x31fd217c0 clang::CXXRecordDecl::data() const + 112
6   liblldb.19.0.0git.dylib       	       0x31fe9cb8c clang::CXXRecordDecl::hasUserDeclaredMoveConstructor() const + 24
7   liblldb.19.0.0git.dylib       	       0x31fe7edf8 lldb_private::TypeSystemClang::CompleteTagDeclarationDefinition(lldb_private::CompilerType const&) + 256
8   liblldb.19.0.0git.dylib       	       0x31fd142a8 DWARFASTParserClang::CompleteRecordType(lldb_private::plugin::dwarf::DWARFDIE const&, lldb_private::Type*, lldb_private::CompilerType&) + 1024
9   liblldb.19.0.0git.dylib       	       0x31fd14f4c DWARFASTParserClang::CompleteTypeFromDWARF(lldb_private::plugin::dwarf::DWARFDIE const&, lldb_private::Type*, lldb_private::CompilerType&) + 316
10  liblldb.19.0.0git.dylib       	       0x31fd9cc78 lldb_private::plugin::dwarf::SymbolFileDWARF::CompleteType(lldb_private::CompilerType&) + 1160
11  liblldb.19.0.0git.dylib       	       0x31fdf3a64 lldb_private::plugin::dwarf::SymbolFileDWARFDebugMap::CompleteType(lldb_private::CompilerType&)::$_0::operator()(lldb_private::plugin::dwarf::SymbolFileDWARF*) const + 72
12  liblldb.19.0.0git.dylib       	       0x31fdf3a10 decltype(std::declval<lldb_private::plugin::dwarf::SymbolFileDWARFDebugMap::CompleteType(lldb_private::CompilerType&)::$_0&>()(std::declval<lldb_private::plugin::dwarf::SymbolFileDWARF*>())) std::__1::__invoke[abi:nn180100]<lldb_private::plugin::dwarf::SymbolFileDWARFDebugMap::CompleteType(lldb_private::CompilerType&)::$_0&, lldb_private::plugin::dwarf::SymbolFileDWARF*>(lldb_private::plugin::dwarf::SymbolFileDWARFDebugMap::CompleteType(lldb_private::CompilerType&)::$_0&, lldb_private::plugin::dwarf::SymbolFileDWARF*&&) + 36
13  liblldb.19.0.0git.dylib       	       0x31fdf39bc lldb_private::IterationAction std::__1::__invoke_void_return_wrapper<lldb_private::IterationAction, false>::__call[abi:nn180100]<lldb_private::plugin::dwarf::SymbolFileDWARFDebugMap::CompleteType(lldb_private::CompilerType&)::$_0&, lldb_private::plugin::dwarf::SymbolFileDWARF*>(lldb_private::plugin::dwarf::SymbolFileDWARFDebugMap::CompleteType(lldb_private::CompilerType&)::$_0&, lldb_private::plugin::dwarf::SymbolFileDWARF*&&) + 32
14  liblldb.19.0.0git.dylib       	       0x31fdf3990 std::__1::__function::__alloc_func<lldb_private::plugin::dwarf::SymbolFileDWARFDebugMap::CompleteType(lldb_private::CompilerType&)::$_0, std::__1::allocator<lldb_private::plugin::dwarf::SymbolFileDWARFDebugMap::CompleteType(lldb_private::CompilerType&)::$_0>, lldb_private::IterationAction (lldb_private::plugin::dwarf::SymbolFileDWARF*)>::operator()[abi:nn180100](lldb_private::plugin::dwarf::SymbolFileDWARF*&&) + 36
15  liblldb.19.0.0git.dylib       	       0x31fdf28a4 std::__1::__function::__func<lldb_private::plugin::dwarf::SymbolFileDWARFDebugMap::CompleteType(lldb_private::CompilerType&)::$_0, std::__1::allocator<lldb_private::plugin::dwarf::SymbolFileDWARFDebugMap::CompleteType(lldb_private::CompilerType&)::$_0>, lldb_private::IterationAction (lldb_private::plugin::dwarf::SymbolFileDWARF*)>::operator()(lldb_private::plugin::dwarf::SymbolFileDWARF*&&) + 36
16  liblldb.19.0.0git.dylib       	       0x31fde1f84 std::__1::__function::__value_func<lldb_private::IterationAction (lldb_private::plugin::dwarf::SymbolFileDWARF*)>::operator()[abi:nn180100](lldb_private::plugin::dwarf::SymbolFileDWARF*&&) const + 76
17  liblldb.19.0.0git.dylib       	       0x31fde1f2c std::__1::function<lldb_private::IterationAction (lldb_private::plugin::dwarf::SymbolFileDWARF*)>::operator()(lldb_private::plugin::dwarf::SymbolFileDWARF*) const + 36
18  liblldb.19.0.0git.dylib       	       0x31fdde658 lldb_private::plugin::dwarf::SymbolFileDWARFDebugMap::ForEachSymbolFile(std::__1::function<lldb_private::IterationAction (lldb_private::plugin::dwarf::SymbolFileDWARF*)>) + 128
19  liblldb.19.0.0git.dylib       	       0x31fdde58c lldb_private::plugin::dwarf::SymbolFileDWARFDebugMap::CompleteType(lldb_private::CompilerType&) + 112
20  liblldb.19.0.0git.dylib       	       0x31fe9f5cc lldb_private::TypeSystemClang::CompleteTagDecl(clang::TagDecl*) + 120
21  liblldb.19.0.0git.dylib       	       0x321364718 lldb_private::ClangExternalASTSourceCallbacks::CompleteType(clang::TagDecl*) + 36
22  liblldb.19.0.0git.dylib       	       0x31fe82b94 GetCompleteQualType(clang::ASTContext*, clang::QualType, bool) + 516
23  liblldb.19.0.0git.dylib       	       0x31fe8e1f4 lldb_private::TypeSystemClang::GetNumChildren(void*, bool, lldb_private::ExecutionContext const*) + 436
24  liblldb.19.0.0git.dylib       	       0x31f2f7f88 lldb_private::CompilerType::GetNumChildren(bool, lldb_private::ExecutionContext const*) const + 144
25  liblldb.19.0.0git.dylib       	       0x31fe8e60c lldb_private::TypeSystemClang::GetNumChildren(void*, bool, lldb_private::ExecutionContext const*) + 1484
26  liblldb.19.0.0git.dylib       	       0x31f2f7f88 lldb_private::CompilerType::GetNumChildren(bool, lldb_private::ExecutionContext const*) const + 144
27  liblldb.19.0.0git.dylib       	       0x31f0f59cc lldb_private::ValueObjectVariable::CalculateNumChildren(unsigned int) + 232
28  liblldb.19.0.0git.dylib       	       0x31f0cb4d0 lldb_private::ValueObject::GetNumChildren(unsigned int) + 260
29  liblldb.19.0.0git.dylib       	       0x31f1048c8 lldb_private::FormatManager::ShouldPrintAsOneLiner(lldb_private::ValueObject&) + 380
30  liblldb.19.0.0git.dylib       	       0x31f0f8c4c lldb_private::DataVisualization::ShouldPrintAsOneLiner(lldb_private::ValueObject&) + 28
31  liblldb.19.0.0git.dylib       	       0x31f138bdc lldb_private::ValueObjectPrinter::PrintChildrenIfNeeded(bool, bool) + 244
32  liblldb.19.0.0git.dylib       	       0x31f138018 lldb_private::ValueObjectPrinter::PrintValueObject() + 412
33  liblldb.19.0.0git.dylib       	       0x31f0d41cc lldb_private::ValueObject::Dump(lldb_private::Stream&, lldb_private::DumpValueObjectOptions const&) + 76
34  liblldb.19.0.0git.dylib       	       0x32115d344 CommandObjectFrameVariable::DoExecute(lldb_private::Args&, lldb_private::CommandReturnObject&) + 2316
35  liblldb.19.0.0git.dylib       	       0x31f26f4b0 lldb_private::CommandObjectParsed::Execute(char const*, lldb_private::CommandReturnObject&) + 856
36  liblldb.19.0.0git.dylib       	       0x31f2411f4 lldb_private::CommandInterpreter::HandleCommand(char const*, lldb_private::LazyBool, lldb_private::CommandReturnObject&, bool) + 3312
37  liblldb.19.0.0git.dylib       	       0x31ec247d4 lldb::SBCommandInterpreter::HandleCommand(char const*, lldb::SBExecutionContext&, lldb::SBCommandReturnObject&, bool) + 432 (SBCommandInterpreter.cpp:194)
38  liblldb.19.0.0git.dylib       	       0x31ec24574 lldb::SBCommandInterpreter::HandleCommand(char const*, lldb::SBCommandReturnObject&, bool) + 184 (SBCommandInterpreter.cpp:176)
39  liblldb.19.0.0git.dylib       	       0x31ee8a348 _wrap_SBCommandInterpreter_HandleCommand__SWIG_0(_object*, long, _object**) + 732 (LLDBWrapPython.cpp:16242)
40  liblldb.19.0.0git.dylib       	       0x31eda5fc0 _wrap_SBCommandInterpreter_HandleCommand(_object*, _object*) + 820 (LLDBWrapPython.cpp:16469)

Let me know if you need help reproducing this on macOS!

@Michael137
Copy link
Member

Michael137 commented May 30, 2024

Huh this is an interesting one. So it looks like we first parse and insert struct Inner into UniqueDWARFASTTypeMap as a forward declaration. Then when we search the debug-map for the definition of Inner, we find it declared as class Inner (note, class vs. struct...i.e., the DIE tags don't match), and fail to find the entry in said map. So we end up not calling StartTagDeclarationDefinition by the time we get to CompleteType, and hence we assert.

If i hack the DIE tags to align, all is well:

(lldb) n                                                                                                 
Process 61300 stopped                                                                                    
* thread #1, queue = 'com.apple.main-thread', stop reason = step over                                    
    frame #0: 0x00000001204bd8f8 liblldb.19.0.0git.dylib`lldb_private::plugin::dwarf::UniqueDWARFASTTypeList::Find(this=0x0000000107bceb28, die=0x000000016fdfa428, decl=0x000000016fdf98b0, byte_size=4, is_forward_d
eclaration=false) at UniqueDWARFASTType.cpp:21:9                                                         
   18       const int32_t byte_size, bool is_forward_declaration) {                                      
   19     for (UniqueDWARFASTType &udt : m_collection) {                                                 
   20       // Make sure the tags match                                                                                                                                                                               
-> 21       if (udt.m_die.Tag() == die.Tag()) {                                                          
   22         // If they are not both definition DIEs or both declaration DIEs, then                     
   23         // don't check for byte size and declaration location, because declaration                 
   24         // DIEs usually don't have those info.                                                                                                                                                                  
Target 0: (lldb) stopped.                                                    
(lldb) p udt.m_die.Tag()                                                     
(dw_tag_t) DW_TAG_structure_type                                             
(lldb) p die.Tag()                                                           
(dw_tag_t) DW_TAG_class_type                                                 
(lldb) p die.m_die->m_tag = (llvm::dwarf::Tag)0x0013
(dw_tag_t) DW_TAG_structure_type                    
(lldb) c                                             
Process 61300 resuming                               
(D::Inner &) inner_d = 0x000000010000400c (j = 68)   
(lldb) Process 61300 exited with status = 0 (0x00000000) 

It feels a bit off to have situations where we end up succeeding to FindDefinitionTypeForDIE but end up with stale forward declarations in the UniqueDWARFASTTypeMap. Could we safeguard against that? Also, we should probably account for the DW_TAG_structure_type/DW_TAG_class_type equality in UniqueDWARFASTTypeList::Find. @ZequanWu could you take a look at this?

@ZequanWu
Copy link
Contributor Author

For this specific case, we could fix it by making DW_TAG_structure_type equals to DW_TAG_class_type in the UniqueDWARFASTTypeList::Find.

There's few things I noticed with this:

  1. If DW_TAG_structure_type and DW_TAG_class_type are interchangeable, then this comparison on DIE tags also needs to be updated:
    const dw_tag_t parent_arg_tag = parent_arg_die.Tag();
    const dw_tag_t parent_pos_tag = parent_pos_die.Tag();
    if (parent_arg_tag == parent_pos_tag) {
    , because the one parent DIE could be struct another one could be class and they need to be treated as matched.
  2. I wonder why this is not a problem before this change. Before, when ParseStructureLikeDIE sees a struct declaration, it searches for definition DIE from type index, which just checks for the fully qualified name of the types. So, it will find the DIE DW_TAG_class_type as a definition DIE and create one type from the definition DIE. If ParseStructureLikeDIE sees the class definition first. the lldb will be created and UniqueDWARFASTTypeMap will have a cache of the type. Later when ParseStructureLikeDIE parses the struct declaration, it will still failed to find the cache type in the UniqueDWARFASTTypeMap but the call to FindDefinitionTypeForDWARFDeclContext will find the definition DIE using fully qualified name which avoid creating the type twice.

So, basically this PR relies UniqueDWARFASTTypeMap to correctly find the mapping from declaration DIEs to definition DIE and start definition on the clang type (might created from declaration), while it had a backup call to FindDefinitionTypeForDWARFDeclContext to find definition DIE with just fully qualified name before this PR.

In case of we failed to find existing clang type (created from declaration) in UniqueDWARFASTTypeMap, I think it's good to start definition in CompleteRecordType if the clang type hasn't started its definition. Sent #93839 to fix it.

ZequanWu added a commit that referenced this pull request May 30, 2024
…efinition. (#93839)

This fixes
#92328 (comment)
by not differentiating `DW_TAG_class_type` and `DW_TAG_structure_type`
in `UniqueDWARFASTTypeList`, because it's possible that DIE for a type
is `DW_TAG_class_type` in one CU but is `DW_TAG_structure_type` in a
different CU.

---------

Co-authored-by: Michael Buch <michaelbuch12@gmail.com>
@Michael137
Copy link
Member

Michael137 commented Jun 3, 2024

Sorry for the late ping, but the LLDB matrix bot failure has gone unnoticed for a bit. This patch caused following test to fail for DWARFv5:

FAIL: test_inline_static_members_dsym (TestConstStaticIntegralMember.TestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/ec2-user/jenkins/workspace/llvm.org/lldb-cmake-matrix/llvm-project/lldb/packages/Python/lldbsuite/test/lldbtest.py", line 1756, in test_method
    return attrvalue(self)
  File "/Users/ec2-user/jenkins/workspace/llvm.org/lldb-cmake-matrix/llvm-project/lldb/test/API/lang/cpp/const_static_integral_member/TestConstStaticIntegralMember.py", line 132, in test_inline_static_members
    self.check_global_var("A::int_val", "const int", "1")
  File "/Users/ec2-user/jenkins/workspace/llvm.org/lldb-cmake-matrix/llvm-project/lldb/test/API/lang/cpp/const_static_integral_member/TestConstStaticIntegralMember.py", line 118, in check_global_var
    self.assertGreaterEqual(len(var_list), 1)
AssertionError: 0 not greater than or equal to 1
Config=x86_64-/Users/ec2-user/jenkins/workspace/llvm.org/lldb-cmake-matrix/lldb-build/bin/clang
======================================================================
FAIL: test_shadowed_static_inline_members_dsym (TestConstStaticIntegralMember.TestCase)
    Tests that the expression evaluator and SBAPI can both
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/ec2-user/jenkins/workspace/llvm.org/lldb-cmake-matrix/llvm-project/lldb/packages/Python/lldbsuite/test/lldbtest.py", line 1756, in test_method
    return attrvalue(self)
  File "/Users/ec2-user/jenkins/workspace/llvm.org/lldb-cmake-matrix/llvm-project/lldb/test/API/lang/cpp/const_static_integral_member/TestConstStaticIntegralMember.py", line 184, in test_shadowed_static_inline_members
    self.check_global_var("ns::Foo::mem", "const int", "10")
  File "/Users/ec2-user/jenkins/workspace/llvm.org/lldb-cmake-matrix/llvm-project/lldb/test/API/lang/cpp/const_static_integral_member/TestConstStaticIntegralMember.py", line 118, in check_global_var
    self.assertGreaterEqual(len(var_list), 1)
AssertionError: 0 not greater than or equal to 1
Config=x86_64-/Users/ec2-user/jenkins/workspace/llvm.org/lldb-cmake-matrix/lldb-build/bin/clang
----------------------------------------------------------------------
Ran 18 tests in 13.171s

FAILED (failures=2, skipped=6, expected failures=4)

https://green.lab.llvm.org/job/llvm.org/view/LLDB/job/lldb-cmake-matrix/321/execution/node/79/log/

You can repro this by running ./bin/lldb-dotest -p TestConstStaticIntegralMember.py --dwarf-version 5

Could you take a look @ZequanWu ?

@ZequanWu
Copy link
Contributor Author

ZequanWu commented Jun 4, 2024

You can repro this by running ./bin/lldb-dotest -p TestConstStaticIntegralMember.py --dwarf-version 5

Could you take a look @ZequanWu ?

It should be fixed by the following diff. We just need to skip the declaration DIEs that are struct/class/union. This failure you see is caused by skipping a declaration DIE DW_TAG_variable.

diff --git a/lldb/source/Plugins/SymbolFile/DWARF/DebugNamesDWARFIndex.cpp b/lldb/source/Plugins/SymbolFile/DWARF/DebugNamesDWARFIndex.cpp
index 56717bab1ecd..6330470b970e 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/DebugNamesDWARFIndex.cpp
+++ b/lldb/source/Plugins/SymbolFile/DWARF/DebugNamesDWARFIndex.cpp
@@ -87,7 +87,8 @@ bool DebugNamesDWARFIndex::ProcessEntry(
     return true;
   // Clang erroneously emits index entries for declaration DIEs in case when the
   // definition is in a type unit (llvm.org/pr77696). Weed those out.
-  if (die.GetAttributeValueAsUnsigned(DW_AT_declaration, 0))
+  if (die.IsStructUnionOrClass() &&
+      die.GetAttributeValueAsUnsigned(DW_AT_declaration, 0))
     return true;
   return callback(die);
 }

Can you (or anyone with commit access) commit above fix for me? I somehow cannot pull/push from github.

@Michael137
Copy link
Member

You can repro this by running ./bin/lldb-dotest -p TestConstStaticIntegralMember.py --dwarf-version 5
Could you take a look @ZequanWu ?

It should be fixed by the following diff. We just need to skip the declaration DIEs that are struct/class/union. This failure you see is caused by skipping a declaration DIE DW_TAG_variable.

diff --git a/lldb/source/Plugins/SymbolFile/DWARF/DebugNamesDWARFIndex.cpp b/lldb/source/Plugins/SymbolFile/DWARF/DebugNamesDWARFIndex.cpp
index 56717bab1ecd..6330470b970e 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/DebugNamesDWARFIndex.cpp
+++ b/lldb/source/Plugins/SymbolFile/DWARF/DebugNamesDWARFIndex.cpp
@@ -87,7 +87,8 @@ bool DebugNamesDWARFIndex::ProcessEntry(
     return true;
   // Clang erroneously emits index entries for declaration DIEs in case when the
   // definition is in a type unit (llvm.org/pr77696). Weed those out.
-  if (die.GetAttributeValueAsUnsigned(DW_AT_declaration, 0))
+  if (die.IsStructUnionOrClass() &&
+      die.GetAttributeValueAsUnsigned(DW_AT_declaration, 0))
     return true;
   return callback(die);
 }

Can you (or anyone with commit access) commit above fix for me? I somehow cannot pull/push from github.

Ah right, I remember encountering these DW_AT_declarations in the index for DWARFv5 static member variables. Technically this isn't standard conforming right? I thought anything DW_AT_declaration shouldn't be indexed (though in the case of these static variables it's very useful that they do, because we don't emit definitions for these constants that the debugger could look for instead). @dwblaikie @felipepiovezan Should we allow such entries in the index? And does this warrant rephrasing in the DWARF spec?

@felipepiovezan
Copy link
Contributor

felipepiovezan commented Jun 4, 2024

Should we allow such entries in the index? And does this warrant rephrasing in the DWARF spec?

It's fine to have those as nameless entries (which we don't emit today), but the entries that are reaching "process entry" have a name by definition. That if statement is a workaround for incorrect dwarf generation. It seems like this test is providing a case where incorrect dwarf is generated?

@Michael137
Copy link
Member

Should we allow such entries in the index? And does this warrant rephrasing in the DWARF spec?

It's fine to have those as nameless entries (which we don't emit today), but the entries that are reaching "process entry" have a name by definition. That if statement is a workaround for incorrect dwarf generation. It seems like this test is providing a case where incorrect dwarf is generated?

Yea i misremembered, dsymutil is the one indexing these inline statics. @ZequanWu could you put up a PR for the proposed patch?

@labath
Copy link
Collaborator

labath commented Jun 4, 2024

(Zequan is OOO for three weeks, I can take over this (tomorrow) if needed.)

@Michael137
Copy link
Member

(Zequan is OOO for three weeks, I can take over this (tomorrow) if needed.)

Ah no worries! I can put up the patch later today then to unblock the CI

@dwblaikie
Copy link
Collaborator

You can repro this by running ./bin/lldb-dotest -p TestConstStaticIntegralMember.py --dwarf-version 5
Could you take a look @ZequanWu ?

It should be fixed by the following diff. We just need to skip the declaration DIEs that are struct/class/union. This failure you see is caused by skipping a declaration DIE DW_TAG_variable.

diff --git a/lldb/source/Plugins/SymbolFile/DWARF/DebugNamesDWARFIndex.cpp b/lldb/source/Plugins/SymbolFile/DWARF/DebugNamesDWARFIndex.cpp
index 56717bab1ecd..6330470b970e 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/DebugNamesDWARFIndex.cpp
+++ b/lldb/source/Plugins/SymbolFile/DWARF/DebugNamesDWARFIndex.cpp
@@ -87,7 +87,8 @@ bool DebugNamesDWARFIndex::ProcessEntry(
     return true;
   // Clang erroneously emits index entries for declaration DIEs in case when the
   // definition is in a type unit (llvm.org/pr77696). Weed those out.
-  if (die.GetAttributeValueAsUnsigned(DW_AT_declaration, 0))
+  if (die.IsStructUnionOrClass() &&
+      die.GetAttributeValueAsUnsigned(DW_AT_declaration, 0))
     return true;
   return callback(die);
 }

Can you (or anyone with commit access) commit above fix for me? I somehow cannot pull/push from github.

Ah right, I remember encountering these DW_AT_declarations in the index for DWARFv5 static member variables. Technically this isn't standard conforming right? I thought anything DW_AT_declaration shouldn't be indexed (though in the case of these static variables it's very useful that they do, because we don't emit definitions for these constants that the debugger could look for instead). @dwblaikie @felipepiovezan Should we allow such entries in the index? And does this warrant rephrasing in the DWARF spec?

At least we aren't producing that on x86 from the compiler: https://godbolt.org/z/ereKsasWf

...

0x00000029:     DW_TAG_variable
                  DW_AT_name	("i")
                  DW_AT_type	(0x00000033 "const int")
                  DW_AT_decl_file	("/app/example.cpp")
                  DW_AT_decl_line	(2)
                  DW_AT_external	(true)
                  DW_AT_declaration	(true)
                  DW_AT_const_value	(3)
...

and the .debug_names only includes t1, main, and int, nothing for i.

Ah, right - some of the previous context on this is #70639 (which got reverted in #74580 )- though I guess we probably had some discourse and dwarf workgroup discussions about how to do this that aren't likned from the PR... maybe it was all private chat, not sure.

Michael137 added a commit to Michael137/llvm-project that referenced this pull request Jun 4, 2024
…ss/union types

This is a follow-up of llvm#92328 (comment)

Clang attaches `DW_AT_declaration` to static inline data members
and `dsymutil` indexes these constants. Skipping these caused
the expression evaluator to fail to find such constants when
using DWARFv5.

Fixes `TestConstStaticIntegralMember.py` on DWARFv5.
Michael137 added a commit that referenced this pull request Jun 4, 2024
…ss/union types (#94400)

This is a follow-up of
#92328 (comment)

Clang attaches `DW_AT_declaration` to static inline data members and
`dsymutil` indexes these constants. Skipping these caused the expression
evaluator to fail to find such constants when using DWARFv5.

Fixes `TestConstStaticIntegralMember.py` on DWARFv5.
labath added a commit that referenced this pull request Jun 6, 2024
… for class/union types"

and two follow-up commits. The reason is the crash we've discovered when
processing -gsimple-template-names binaries. I'm committing a minimal
reproducer as a separate patch.

This reverts the following commits:
- 51dd4ea (#92328)
- 3d9d485 (#93839)
- afe6ab7 (#94400)
labath added a commit that referenced this pull request Jun 6, 2024
@@ -2345,11 +2345,6 @@ bool DWARFASTParserClang::CompleteTypeFromDWARF(const DWARFDIE &die,

if (!die)
return false;
ParsedDWARFTypeAttributes attrs(die);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In case anyone's wondering, Putting this check back in would prevent the crash from 7fdbc30, but it would not actually make the code correct -- lldb would now just say the class has no definition instead of crashing.

@jeffreytan81
Copy link
Contributor

@ZequanWu, @labath, since this PR got reverted due to crash for --gsimple-template-names, do you guys have a timeline to revise a new version without crashing? I ask this because our internal customers have many forward declarations that are suffering from the slow definition lookup. cc @clayborg

@dwblaikie
Copy link
Collaborator

@ZequanWu, @labath, since this PR got reverted due to crash for --gsimple-template-names, do you guys have a timeline to revise a new version without crashing? I ask this because our internal customers have many forward declarations that are suffering from the slow definition lookup. cc @clayborg

It's certainly stuff that @ZequanWu, @labath, and myself are looking into. We could do a better job of communicating that upstream though :) I think we're meeting next week to chat a bit more about it, and can perhaps formalize that into some tracking bugs, etc.

@labath
Copy link
Collaborator

labath commented Jun 24, 2024

Hi there. Nice to see interest in this patch. Zequan has been OOO for the past couple of weeks, so I've sort of taken this up in the mean time.

The problem with the simplified template names is actually already fixed (by #95905), but in the mean time, I've discovered a very similar problem with type units (basically, just take the test case from #95905, but build it with -fdebug-types-section instead). While this could (and should) be fixed in similar way, this led me to believe that the overall approach in this patch was too fragile. When completing a type, it basically does something like:

  • look up the (declaration) die for the type being completed (using the ForwardDeclCompilerTypeToDIE map)
  • look up the definition die (using the dwarf index)
  • look up the type for the definition die (using the UniqueDWARFASTTypeMap)

The last step, besides being completely unnecessary (we already know the type we're supposed to complete), is also a big liability, because here we are implicitly relying on the the map to return the same type that we started with. If it does not then we will end up creating a new type instead of completing the existing one, and things will quickly go sideways.

The meeting that David alluded to is tomorrow, and I hope we're going to try to figure out who's going to do what and in what order. In the mean time, I've added you as a reviewer to one of my pending patches.

labath added a commit to labath/llvm-project that referenced this pull request Jun 26, 2024
Right now, ParseStructureLikeDIE begins the class definition (which
amounts to parsing the opening "{" of a class and promising to be able
to fill it in later) if it finds a definition DIE.

This makes sense in the current setup, where we eagerly search for the
definition die (so that we will either find it in the beginning or don't
find it at all), but with delayed definition searching (llvm#92328), this
created an (in my view, undesirable) inconsistency, where the final
state of the type (whether it has begun its definition) depended on
whether we happened to start out with a definition DIE or not.

This patch attempts to pre-emptively rectify that by establishing a new
invariant: the definition is never started eagerly. It can only be
started in one of two ways:
- we're completing the type, in which case we will start the definition,
  parse everything and immediately finish it
- we need to parse a member (typedef, nested class, method) of the class
  without needing the definition itself. In this case, we just start the
  definition to insert the member we need.

Besides the delayed definition search, I believe this setup has a couple
of other benefits:
- It treats ObjC and C++ classes the same way (we were never starting
  the definition of those)
- unifies the handling of types that types that have a definition and
  those that do. When adding (e.g.) a nested class we would previously
  be going down a different code path depending on whether we've found a
  definition DIE for that type. Now, we're always taking the
  definition-not-found path (*)
- it reduces the amount of time a class spends in the funny "definition
  started". Aside from the addition of stray addition of nested classes,
  we always finish the definition right after we start it.

(*) Herein lies a danger, where if we're missing some calls to
    PrepareContextToReceiveMembers, we could trigger a crash when
    trying to add a member to the not-yet-started-to-be-defined classes.
    However, this is something that could happen before as well (if we
    did not have a definition for the class), and is something that
    would be exacerbated by llvm#92328 (because it could happen even if we
    the definition exists, but we haven't found it yet). This way, it
    will at least happen consistently, and the fix should consist of
    adding a PrepareContextToReceiveMembers in the appropriate place.
@jeffreytan81
Copy link
Contributor

Thanks for the update! Not sure why but the github notification system seems to fail me recently which I did not get any update emails from this thread until proactively checking now.

Sounds good! Let us know the final stack/PR for the delaying forward declaration is up, we are happy to perform early testing against internal targets. cc @clayborg, @kusmour

labath added a commit that referenced this pull request Jun 28, 2024
…96755)

Right now, ParseStructureLikeDIE begins the class definition (which
amounts to parsing the opening "{" of a class and promising to be able
to fill it in later) if it finds a definition DIE.

This makes sense in the current setup, where we eagerly search for the
definition die (so that we will either find it in the beginning or don't
find it at all), but with delayed definition searching (#92328), this
created an (in my view, undesirable) inconsistency, where the final
state of the type (whether it has begun its definition) depended on
whether we happened to start out with a definition DIE or not.

This patch attempts to pre-emptively rectify that by establishing a new
invariant: the definition is never started eagerly. It can only be
started in one of two ways:
- we're completing the type, in which case we will start the definition,
parse everything and immediately finish it
- we need to parse a member (typedef, nested class, method) of the class
without needing the definition itself. In this case, we just start the
definition to insert the member we need.

Besides the delayed definition search, I believe this setup has a couple
of other benefits:
- It treats ObjC and C++ classes the same way (we were never starting
the definition of those)
- unifies the handling of types that types that have a definition and
those that do. When adding (e.g.) a nested class we would previously be
going down a different code path depending on whether we've found a
definition DIE for that type. Now, we're always taking the
definition-not-found path (*)
- it reduces the amount of time a class spends in the funny "definition
started". Aside from the addition of stray addition of nested classes,
we always finish the definition right after we start it.

(*) Herein lies a danger, where if we're missing some calls to
    PrepareContextToReceiveMembers, we could trigger a crash when
    trying to add a member to the not-yet-started-to-be-defined classes.
    However, this is something that could happen before as well (if we
    did not have a definition for the class), and is something that
    would be exacerbated by #92328 (because it could happen even if we
    the definition exists, but we haven't found it yet). This way, it
    will at least happen consistently, and the fix should consist of
    adding a PrepareContextToReceiveMembers in the appropriate place.
lravenclaw pushed a commit to lravenclaw/llvm-project that referenced this pull request Jul 3, 2024
…lvm#96755)

Right now, ParseStructureLikeDIE begins the class definition (which
amounts to parsing the opening "{" of a class and promising to be able
to fill it in later) if it finds a definition DIE.

This makes sense in the current setup, where we eagerly search for the
definition die (so that we will either find it in the beginning or don't
find it at all), but with delayed definition searching (llvm#92328), this
created an (in my view, undesirable) inconsistency, where the final
state of the type (whether it has begun its definition) depended on
whether we happened to start out with a definition DIE or not.

This patch attempts to pre-emptively rectify that by establishing a new
invariant: the definition is never started eagerly. It can only be
started in one of two ways:
- we're completing the type, in which case we will start the definition,
parse everything and immediately finish it
- we need to parse a member (typedef, nested class, method) of the class
without needing the definition itself. In this case, we just start the
definition to insert the member we need.

Besides the delayed definition search, I believe this setup has a couple
of other benefits:
- It treats ObjC and C++ classes the same way (we were never starting
the definition of those)
- unifies the handling of types that types that have a definition and
those that do. When adding (e.g.) a nested class we would previously be
going down a different code path depending on whether we've found a
definition DIE for that type. Now, we're always taking the
definition-not-found path (*)
- it reduces the amount of time a class spends in the funny "definition
started". Aside from the addition of stray addition of nested classes,
we always finish the definition right after we start it.

(*) Herein lies a danger, where if we're missing some calls to
    PrepareContextToReceiveMembers, we could trigger a crash when
    trying to add a member to the not-yet-started-to-be-defined classes.
    However, this is something that could happen before as well (if we
    did not have a definition for the class), and is something that
    would be exacerbated by llvm#92328 (because it could happen even if we
    the definition exists, but we haven't found it yet). This way, it
    will at least happen consistently, and the fix should consist of
    adding a PrepareContextToReceiveMembers in the appropriate place.
ZequanWu added a commit that referenced this pull request Jul 16, 2024
…ng when parsing declaration DIEs. (#98361)

This is a reapply of #92328 and
#93839.

It now passes the
[test](de3f1b6),
which crashes with the original reverted changes.
sgundapa pushed a commit to sgundapa/upstream_effort that referenced this pull request Jul 23, 2024
…ng when parsing declaration DIEs. (llvm#98361)

This is a reapply of llvm#92328 and
llvm#93839.

It now passes the
[test](llvm@de3f1b6),
which crashes with the original reverted changes.
yuxuanchen1997 pushed a commit that referenced this pull request Jul 25, 2024
…ng when parsing declaration DIEs. (#98361)

Summary:
This is a reapply of #92328 and
#93839.

It now passes the
[test](de3f1b6),
which crashes with the original reverted changes.

Test Plan: 

Reviewers: 

Subscribers: 

Tasks: 

Tags: 


Differential Revision: https://phabricator.intern.facebook.com/D60251550
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants