[PATCH] D96035: [WIP][dsymutil][DWARFlinker] implement separate multi-thread processing for compile units.

David Blaikie via llvm-commits llvm-commits at lists.llvm.org
Thu Feb 4 16:16:16 PST 2021


Snipping a fragment here:

> A lot of stuff the compiler emits these days also causes many of the types to not be correctly removed even though they aren't used.
> So there are many C++ compiler things that seem to be keeping things alive in the DWARF output by dsymutil these days that used to not be there before.

Depends on your definition of "used" I guess, and I don't think it's
much related to dsymutil. at least the example you give is
specifically about imported_declarations - I implemented those (... 7
years ago https://github.com/llvm/llvm-project/commit/bd4837665b2ee1e77dc9337e11ee2aed52a00a93
) & yeah, they're not great. Specifically the compiler doesn't track
if they are used for name lookup, so we don't have a way to only emit
those that were used - and even if we did emit only the used ones,
they aren't used in the DWARF sense (if you have a "using namespace
x;" in code - references to names in namespace 'x' don't refer to that
using declaration in the DWARF (unlike a typedef, or a using
declaration "using x = y;")) so you wouldn't be able to strategically
drop them in dsymutil. (eg: if a certain using directive was only used
by one function body, but that function was optimized away or not
linked - there's no chain of liveness between that function and the
using directive - no way to know which using directives should be kept
around).

& even using declarations for functions (unlike for types) - again, no
chain of connection. So if you call a function via a using declaration
("void f1(); using f2 = f1; void f3() { f2(); }") there's nothing in
the DWARF that says f2 is used by f3 - with call_site debug info we
could describe the call-site, but it wouldn't cover all references to
f2 (indirect calls, for instance).

So, yeah, compiler improvements to track the used-ness of using
declarations and directives would be nice. Oh, but even if we knew
they were used, we'd have to know /where/ they were used from (eg: if
the were used in an inline function that was never called, that
wouldn't be much use to us/we wouldn't want to emit them in the
resulting DWARF).

But even if we did have that information, unfortunately I'm not sure
it'd help dsymutil greatly - because currently those entities don't
always form chains of ownership within the DWARF, so you just sort of
have to keep carrying them.

Though they'd still benefit from all the usual type deduplication
stuff - so this is probably orthogonal to the discussion.

- Dave

On Thu, Feb 4, 2021 at 2:44 PM Greg Clayton via Phabricator
<reviews at reviews.llvm.org> wrote:
>
> clayborg added a comment.
>
> > Handling inter-CU references: inter-CU references are hard to process using only one pass. f.e. if CU1 references CU100 and CU100 references CU1, we could not finish handling of CU1 until we finished CU100. Thus we either need to load all CUs into the memory, either load CUs several times.
>
> What kind of inter-CU references do you see? There really shouldn't be any unless they are all in the same .o file and were produced by an LTO pass. Are there any inter CU references that I am forgetting about?
>
> > a) No common abbreviation table. Each compile unit has its own abbreviation table. Generating common abbreviation table slowdowns parallel execution (This is a resource that is accessed many times from many threads). Abbreviation table does not take a lot of space, so it looks cheap to have separate abbreviations tables. Later, it might be optimized a bit(by removing equal abbreviations tables).
>
> Can't the linking process create on large uniqued abbrev table as it links each .o file's DWARF into the final output? This info isn't that large that is true, but it'd be nice to have just one table as long as we are optimizing the DWARF.
>
> > b) Removed check for ambiguous type declarations. Existed check does not solve the problem in general (current check does not work if there are ambiguous  types in different compile units).
>
> I am assuming you didn't pull out ODR type uniquing all together right? That is very important and one of the main reasons for using dsymutil. There are many things that can make this ODR type uniquing fail to work correctly, but those are bugs that should be fixed. One of the main things is if a class or struct has a forward declaration to another class inside of it. If this is the case the llvm dsymutil doesn't try and see if everyone has the same full class definition with a forward decl on it, it just punts and emits another copy of the type. I saw this when I would have a "FILE" type from the unix fopen(...) function. If multiple files had this type in a C++ source file, they wouldn't get uniqued because one of the members had a forward declaration. The reason it doesn't get uniqued is dsymutil worries someone might have a "FILE" type info in the DWARF that _does_ have a full definition for that forward declaration. A lot of stuff the compiler emits these days also causes many of the types to not be correctly removed even though they aren't used.
>
> So there are many C++ compiler things that seem to be keeping things alive in the DWARF output by dsymutil these days that used to not be there before. When ODR works correctly, the first instance of a type gets emitted from the first object file file that has it, and then all other .o files that have this same type remove it and have all CU references within changed to a DW_FORM_ref_addr inter CU reference. We should make sure that this isn't happening to many clang types and causing us to not unique types.
>
> When the type uniquing works correctly it really reduces the output DWARF size. Back at Apple the WebCore.framework DWARF debug info went from 1.8GB down to 450MB.
>
> As an example of C++ compiler type bloat, you can see this if you do "#include <cstdio>". If you compile this with a recent clang:
>
>   #include <stdio.h>
>
>   int main() {
>       printf("hello\n");
>       return 0;
>   }
>
> The DWARF looks very small:
>
>   0x0000000b: DW_TAG_compile_unit
>                 DW_AT_producer  ("Apple clang version 12.0.0 (clang-1200.0.32.29)")
>                 DW_AT_language  (DW_LANG_C_plus_plus_11)
>                 DW_AT_name      ("main.cpp")
>                 DW_AT_LLVM_sysroot      ("/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk")
>                 DW_AT_APPLE_sdk ("MacOSX.sdk")
>                 DW_AT_stmt_list (0x00000000)
>                 DW_AT_comp_dir  ("/Users/gclayton/Documents/src/args")
>                 DW_AT_low_pc    (0x0000000100003f60)
>                 DW_AT_high_pc   (0x0000000100003f8a)
>
>   0x00000032:   DW_TAG_subprogram
>                   DW_AT_low_pc  (0x0000000100003f60)
>                   DW_AT_high_pc (0x0000000100003f8a)
>                   DW_AT_frame_base      (DW_OP_reg6 RBP)
>                   DW_AT_name    ("main")
>                   DW_AT_decl_file       ("/Users/gclayton/Documents/src/args/main.cpp")
>                   DW_AT_decl_line       (4)
>                   DW_AT_type    (0x000000000000004b "int")
>                   DW_AT_external        (true)
>
>   0x0000004b:   DW_TAG_base_type
>                   DW_AT_name    ("int")
>                   DW_AT_encoding        (DW_ATE_signed)
>                   DW_AT_byte_size       (0x04)
>
>   0x00000052:   NULL
>
> But if you include the "cstdio" file instead:
>
>   #include <cstdio>
>
>   int main() {
>       printf("hello\n");
>       return 0;
>   }
>
> This ends up having tons of extra type stuff that really bloats the DWARF. The C++ header ends up with full definitions for every function in stdio.h and data types (like "FILE"!) for functions that are not used in the .o file. In this case the C++ code add DW_TAG_imported_declaration to all these types and that ends up keeping all of these extra types alive since something refers to it and ends up with way toe much stuff in the final dSYM. We can see the DW_TAG_imported_declaration below:
>
>   0x00000032:   DW_TAG_namespace
>                   DW_AT_name    ("std")
>
>   0x00000037:     DW_TAG_namespace
>                     DW_AT_name  ("__1")
>                     DW_AT_export_symbols        (true)
>
>   0x0000003c:       DW_TAG_imported_declaration
>                       DW_AT_decl_file   ("/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/cstdio")
>                       DW_AT_decl_line   (109)
>                       DW_AT_import      (0x0000000000000195)
>   `
>
> And later we see:
>
>   0x00000193:       NULL
>
>   0x00000194:     NULL
>
>   0x00000195:   DW_TAG_typedef
>                   DW_AT_type    (0x00000000000001a0 "__sFILE")
>                   DW_AT_name    ("FILE")
>                   DW_AT_decl_file       ("/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/_stdio.h")
>                   DW_AT_decl_line       (157)
>
>   0x000001a0:   DW_TAG_structure_type
>                   DW_AT_calling_convention      (DW_CC_pass_by_value)
>                   DW_AT_name    ("__sFILE")
>                   DW_AT_byte_size       (0x98)
>                   DW_AT_decl_file       ("/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/_stdio.h")
>                   DW_AT_decl_line       (126)
>
>   0x000001a9:     DW_TAG_member
>                     DW_AT_name  ("_p")
>                     DW_AT_type  (0x000000000000029a "unsigned char*")
>                     DW_AT_decl_file     ("/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/_stdio.h")
>                     DW_AT_decl_line     (127)
>                     DW_AT_data_member_location  (0x00)
>   ....
>
>
>
> > c) .debug_frame. Already generated CIE records are not reused between object files
>
> Can't we unique these when the .o file gets linked on the main thread?
>
> > d) In multi-thread mode there is no predictable order of how compile units/dies are processed. That leads to selecting "canonical" dies, or adding strings into the debug_str table in some unpredictable order. It is semantically correct but creates not-matching references:
> >
> >
> >   DW_AT_name [DW_FORM_strp]     ( .debug_str[0x00000095] = "AnonC")
> >   DW_AT_name [DW_FORM_strp]     ( .debug_str[0x0000007f] = "AnonC")
> >
> >   DW_AT_type [DW_FORM_ref_addr] (0x000000000000014f "U")
> >   DW_AT_type [DW_FORM_ref_addr] (0x0000000000000170 "U")
>
> Yes this will happen and it is ok as long as "--num-threads 1" allows consistent builds. Any clients that require dSYM files to be the same can use this slower method
>
> > e) .debug_names is not supported by this patch.
>
> Any reason why this can't be done? I believe that dsymutil just generates the .debug_names (or Apple accelerator tables) after you have written all of the DWARF out by traversing it no? If this doesn't happen, it can be added with the --update option to dsymutil, but again, this takes time.
>
> One of the main benefits of running dsymutil is the accelerator tables that speed up debugging. If you are solely looking for a way to reduce link times, then this succeeds. But if there are no accelerator tables, each time you debug this the debugger is going to have to manually index all of the DWARF and cause long delays each time you debug something. You might as well just not run the dsymutil and leave the DWARF in the .o files as that is the workflow that is designed to be used during the edit/compile/debug workflow.
>
> I haven't looked at the code itself yet, just wanted to get some more overall info on the approach.
>
>
> Repository:
>   rG LLVM Github Monorepo
>
> CHANGES SINCE LAST ACTION
>   https://reviews.llvm.org/D96035/new/
>
> https://reviews.llvm.org/D96035
>


More information about the llvm-commits mailing list