Changeset View
Standalone View
clang/lib/CodeGen/CodeGenModule.cpp
Show First 20 Lines • Show All 5,324 Lines • ▼ Show 20 Lines | if (auto *OID = dyn_cast<ObjCImplDecl>(I)) { | ||||
EmitTopLevelDecl(M); | EmitTopLevelDecl(M); | ||||
} | } | ||||
EmitTopLevelDecl(I); | EmitTopLevelDecl(I); | ||||
} | } | ||||
} | } | ||||
/// EmitTopLevelDecl - Emit code for a single top level declaration. | /// EmitTopLevelDecl - Emit code for a single top level declaration. | ||||
void CodeGenModule::EmitTopLevelDecl(Decl *D) { | void CodeGenModule::EmitTopLevelDecl(Decl *D) { | ||||
dblaikie: Since this is only for top-level decls, have you checked this works for types inside namespaces… | |||||
class declared in namespace works with this patch. I'll add a test. local type in function, ex. void foo(void) { struct bar {}; } is not in the current diff, but is supported by gcc. Let me fix that and add a test. Great suggestion. nickdesaulniers: class declared in namespace works with this patch. I'll add a test.
local type in function… | |||||
// Ignore dependent declarations. | // Ignore dependent declarations. | ||||
if (D->isTemplated()) | if (D->isTemplated()) | ||||
return; | return; | ||||
// Consteval function shouldn't be emitted. | // Consteval function shouldn't be emitted. | ||||
if (auto *FD = dyn_cast<FunctionDecl>(D)) | if (auto *FD = dyn_cast<FunctionDecl>(D)) | ||||
if (FD->isConsteval()) | if (FD->isConsteval()) | ||||
return; | return; | ||||
Show All 34 Lines | void CodeGenModule::EmitTopLevelDecl(Decl *D) { | ||||
case Decl::ClassTemplateSpecialization: { | case Decl::ClassTemplateSpecialization: { | ||||
const auto *Spec = cast<ClassTemplateSpecializationDecl>(D); | const auto *Spec = cast<ClassTemplateSpecializationDecl>(D); | ||||
if (CGDebugInfo *DI = getModuleDebugInfo()) | if (CGDebugInfo *DI = getModuleDebugInfo()) | ||||
if (Spec->getSpecializationKind() == | if (Spec->getSpecializationKind() == | ||||
TSK_ExplicitInstantiationDefinition && | TSK_ExplicitInstantiationDefinition && | ||||
Spec->hasDefinition()) | Spec->hasDefinition()) | ||||
DI->completeTemplateDefinition(*Spec); | DI->completeTemplateDefinition(*Spec); | ||||
} LLVM_FALLTHROUGH; | } LLVM_FALLTHROUGH; | ||||
case Decl::CXXRecord: | case Decl::CXXRecord: { | ||||
if (CGDebugInfo *DI = getModuleDebugInfo()) | CXXRecordDecl *CRD = cast<CXXRecordDecl>(D); | ||||
if (CGDebugInfo *DI = getModuleDebugInfo()) { | |||||
if (CRD->hasDefinition()) | |||||
DI->EmitAndRetainType(getContext().getRecordType(cast<RecordDecl>(D))); | |||||
if (auto *ES = D->getASTContext().getExternalSource()) | if (auto *ES = D->getASTContext().getExternalSource()) | ||||
if (ES->hasExternalDefinitions(D) == ExternalASTSource::EK_Never) | if (ES->hasExternalDefinitions(D) == ExternalASTSource::EK_Never) | ||||
DI->completeUnusedClass(cast<CXXRecordDecl>(*D)); | DI->completeUnusedClass(*CRD); | ||||
} | |||||
The difference between using DebugInfo vs getModuleDebugInfo in this method is *killing* me. Same with return vs break. nickdesaulniers: The difference between using `DebugInfo` vs `getModuleDebugInfo` in this method is *killing* me. | |||||
Feel free to send separate patches to clean these things up. dblaikie: Feel free to send separate patches to clean these things up. | |||||
nickdesaulniers: https://reviews.llvm.org/D80840 | |||||
Perhaps this could be modified to use the same general purpose utility like the other type emissions (as speculated in another comment "EmitUnusedType" or the like) dblaikie: Perhaps this could be modified to use the same general purpose utility like the other type… | |||||
Ping on this ^ dblaikie: Ping on this ^ | |||||
// Emit any static data members, they may be definitions. | // Emit any static data members, they may be definitions. | ||||
Not Done ReplyInline ActionsI think this might not work as intended if the type trips over the other class deduplication optimizations (try manually testing with a type that has an external VTable?) - and perhaps this codepath should use the EmitAndRetain functionality. dblaikie: I think this might not work as intended if the type trips over the other class deduplication… | |||||
Not Done ReplyInline ActionscompleteUnusedClass eventually calls into CGDebugInfo::completeClass which makes use of the TypeCache. What does it mean for a class to have "an external vtable?" Can you give me an example? nickdesaulniers: completeUnusedClass eventually calls into CGDebugInfo::completeClass which makes use of the… | |||||
Not Done ReplyInline ActionsHere's some sample code that uses a type in the DWARF but due to the vtable being external (an external vtable is one that's guaranteed to be emitted in some other object file - due to the key function optimization in the Itanium ABI - since all virtual functions must be defined if a type is ODR used, the compiler can make a shared assumption that the vtable will only be emitted with a single consistently chosen virtual function (ideally: the first non-inline one) and emit the vtable only in that object and nowhere else) the type is emitted as a declaration (when using "-fno-standalone-debug"): struct t1 { virtual void f1(); }; t1 v1; dblaikie: Here's some sample code that uses a type in the DWARF but due to the vtable being external (an… | |||||
Not Done ReplyInline ActionsCool, thanks for the example. For your example: ... DIGlobalVariable(name: "v1" ... DICompositeType(tag: DW_TAG_structure_type, name: "t1" ... clang -g -fno-eliminate-unused-debug-types -emit-llvm -S foo.cpp -o -: ... DIGlobalVariable(name: "v1" ... DICompositeType(tag: DW_TAG_structure_type, name: "t1" ... DIDerivedType(tag: DW_TAG_member, name: "_vptr$t1" DIDerivedType(tag: DW_TAG_pointer_type DIDerivedType(tag: DW_TAG_pointer_type, name: "__vtbl_ptr_type" DIBasicType(name: "int" DISubprogram(name: "f1" DISubroutineType DIDerivedType(tag: DW_TAG_pointer_type ... So even though f1 was not defined, we still emit debug info for it. Comparing the output of llvm-dwarfdump on $CC -g -fno-eliminate-unused-debug-types -c <example.cpp> for g++ vs clang++; we're definitely producing more debug info (the vtable info). That said, the creation of v1 technically means that t1 is no longer an "unused type." If we comment out its creation, then compare $CC -g -fno-eliminate-unused-debug-types -c <example.cpp> for g++ vs clang++, g++ produces no debug info (That seems like a bug in g++ to me), while clang++ with this patch produces the debug info for the CXXRecord, and additional info for the vtable ptr and signature of f1. I'm not sure what's expected in this case (we should at least emit the DW_TAG_structure_type for t1, but I'm not sure about the DW_TAG_subprogram for f1. I assume that's because we've enabled more-than-full-debuginfo behavior? WDYT? nickdesaulniers: Cool, thanks for the example. For your example:
`clang -g -emit-llvm -S foo.cpp -o -`:
```
... | |||||
Not Done ReplyInline ActionsI think it'd be nice to handle this the same as GCC (though technically we don't offer the same customization that GCC offers - I imagine the GCC behavior of -femit-class-debug-always, which Clang doesn't have a configuration option for (well, not specifically - again, comes back to that "independent configuration" or "overriding behavior" implementation choice) - I'd sort of think of it like that flag is always enabled) But I guess if we think of it as a series of escalations, then, yeah, it makes sense in the Clang way of thinking of it, as -fno-eliminate-unused-debug-types as overriding the default -fno-emit-class-debug-always... *shrug* don't feel /super/ strongly either way I guess. If the current implementation is such that -fno-eliminate-unused-debug-types overrides all other type homing techniques (vtable based, explicit template specialization decl/def, ctor based, and not-required-to-be-complete (define a type but only use pouinters to that type and never dereference them)) that's fine by me. dblaikie: I think it'd be nice to handle this the same as GCC (though technically we don't offer the same… | |||||
for (auto *I : cast<CXXRecordDecl>(D)->decls()) | for (auto *I : CRD->decls()) | ||||
if (isa<VarDecl>(I) || isa<CXXRecordDecl>(I)) | if (isa<VarDecl>(I) || isa<CXXRecordDecl>(I)) | ||||
EmitTopLevelDecl(I); | EmitTopLevelDecl(I); | ||||
break; | break; | ||||
} | |||||
// No code generation needed. | // No code generation needed. | ||||
case Decl::UsingShadow: | case Decl::UsingShadow: | ||||
case Decl::ClassTemplate: | case Decl::ClassTemplate: | ||||
case Decl::VarTemplate: | case Decl::VarTemplate: | ||||
case Decl::Concept: | case Decl::Concept: | ||||
case Decl::VarTemplatePartialSpecialization: | case Decl::VarTemplatePartialSpecialization: | ||||
case Decl::FunctionTemplate: | case Decl::FunctionTemplate: | ||||
case Decl::TypeAliasTemplate: | case Decl::TypeAliasTemplate: | ||||
▲ Show 20 Lines • Show All 169 Lines • ▼ Show 20 Lines | void CodeGenModule::EmitTopLevelDecl(Decl *D) { | ||||
case Decl::OMPDeclareMapper: | case Decl::OMPDeclareMapper: | ||||
EmitOMPDeclareMapper(cast<OMPDeclareMapperDecl>(D)); | EmitOMPDeclareMapper(cast<OMPDeclareMapperDecl>(D)); | ||||
break; | break; | ||||
case Decl::OMPRequires: | case Decl::OMPRequires: | ||||
EmitOMPRequiresDecl(cast<OMPRequiresDecl>(D)); | EmitOMPRequiresDecl(cast<OMPRequiresDecl>(D)); | ||||
break; | break; | ||||
case Decl::Typedef: | |||||
case Decl::TypeAlias: // using foo = bar; [C++11] | |||||
Probably test this within the implementation of CGDebugInfo? & rename the EmitTypedef function to something that clarifies that it's for an otherwise unused type? But that function might need to be generalized further, rather than only having it for typedefs. (see general comment above) dblaikie: Probably test this within the implementation of CGDebugInfo? & rename the EmitTypedef function… | |||||
I think it makes sense at this point to rename EmitExplicitCastType to RetainType or EmitType. WDYT? nickdesaulniers: I think it makes sense at this point to rename `EmitExplicitCastType` to `RetainType` or… | |||||
Yep - now that I understand the confusingly named hasReducedDebugInfo "RetainType" sounds good to me, maybe "EmitAndRetainType" might be worthwhile for the extra clarity/symmetry with other "Emit" functions. dblaikie: Yep - now that I understand the confusingly named hasReducedDebugInfo
"RetainType" sounds good… | |||||
Ping on this ^ dblaikie: Ping on this ^ | |||||
if (CGDebugInfo *DI = getModuleDebugInfo()) | |||||
DI->EmitAndRetainType( | |||||
Rather than testing DebugUnusedType for every call - might be best to add a "EmitUnusedType" function that tests the flag and does the emission? (same way EmitExplicitCastType already checks for a certain level of debug info before emitting) dblaikie: Rather than testing DebugUnusedType for every call - might be best to add a "EmitUnusedType"… | |||||
It can be; what I don't like about that approach is that we have to determine the QualType here to pass in, at which point such function might just return without doing anything. (ie. we have to do slightly more work in the case where the debugging of unused types was *not* requested). But that's a minor nit and we can live with it? nickdesaulniers: It can be; what I don't like about that approach is that we have to determine the `QualType`… | |||||
I don't believe getting the QualType from a TypeDecl is an expensive operation/enough to try to avoid it here. If it is I'd rather have a "EmitUnusedType(TypeDecl*)" function, and then the conditional QualType retrieval could be in there, written once rather than several times. dblaikie: I don't believe getting the QualType from a TypeDecl is an expensive operation/enough to try to… | |||||
Ping on this^ dblaikie: Ping on this^ | |||||
getContext().getTypedefType(cast<TypedefNameDecl>(D))); | |||||
break; | |||||
case Decl::Record: | |||||
if (CGDebugInfo *DI = getModuleDebugInfo()) | |||||
if (cast<RecordDecl>(D)->getDefinition()) | |||||
DI->EmitAndRetainType(getContext().getRecordType(cast<RecordDecl>(D))); | |||||
break; | |||||
case Decl::Enum: | |||||
if (CGDebugInfo *DI = getModuleDebugInfo()) | |||||
if (cast<EnumDecl>(D)->getDefinition()) | |||||
DI->EmitAndRetainType(getContext().getEnumType(cast<EnumDecl>(D))); | |||||
break; | |||||
default: | default: | ||||
// Make sure we handled everything we should, every other kind is a | // Make sure we handled everything we should, every other kind is a | ||||
// non-top-level decl. FIXME: Would be nice to have an isTopLevelDeclKind | // non-top-level decl. FIXME: Would be nice to have an isTopLevelDeclKind | ||||
// function. Need to recode Decl::Kind to do that easily. | // function. Need to recode Decl::Kind to do that easily. | ||||
assert(isa<TypeDecl>(D) && "Unsupported decl kind"); | assert(isa<TypeDecl>(D) && "Unsupported decl kind"); | ||||
break; | break; | ||||
} | } | ||||
} | } | ||||
▲ Show 20 Lines • Show All 476 Lines • Show Last 20 Lines |
Since this is only for top-level decls, have you checked this works for types inside namespaces, local types in functions (well, that one might not be supported by GCC - so probably good to test/compare GCC's behavior here too), etc?