This is an archive of the discontinued LLVM Phabricator instance.

Don't store the NULL DIEs in the DIE array in the DWARFUnit.
AbandonedPublic

Authored by clayborg on Dec 21 2016, 4:47 PM.

Details

Summary

Storing a DWARFDebugInfoEntry for each NULL DIE that terminates a sibling chain takes up memory. We can get away with not storing the NULL DIEs as long as we still can dump the DWARF correctly and also get the sibling DIEs correctly. This patch implements this and removes the "bool DWARFDie::IsNULL()" function as it is no longer needed.

llvm-dsymutil performance improved very slightly when making a dSYM file for a debug version of clang with over 1200 .o files.

Diff Detail

Event Timeline

clayborg updated this revision to Diff 82290.Dec 21 2016, 4:47 PM
clayborg retitled this revision from to Don't store the NULL DIEs in the DIE array in the DWARFUnit..
clayborg updated this object.
aprantl added inline comments.Dec 21 2016, 5:04 PM
lib/DebugInfo/DWARF/DWARFDie.cpp
354–356

Is there a better alternative to returning a magic value?

tools/dsymutil/DwarfLinker.cpp
1799

Might make sense to add a method that returns something like a SiblingIterator at some point in the future?

unittests/DebugInfo/DWARF/DWARFDebugInfoTest.cpp
1065–1067

Should we introduce something like a hasSibling() method? This almost sounds like it has a corrupted sibling DIE.

dblaikie added inline comments.Dec 21 2016, 5:07 PM
include/llvm/DebugInfo/DWARF/DWARFDebugInfoEntry.h
37–38

Comment seems temporal - it describes the change ("are /now/ removed") rather than the current state, which might be confusing for future readers.

It also might be easier if the member was called "isLastChild".

(similar comment on other comments - and a comment below might remove the need for this HasNullSibling/isLastChild member)

39

This should be a bool.

lib/DebugInfo/DWARF/DWARFDie.cpp
275–276

Is this necessary? (similarly for collectChildrenAddressRanges, dump, etc) - or should we just require clients not to call these operations on invalid DIEs?

392–399

I'm still not sure it should be the responsibility of this node to print the NULL entry here. Perhaps it should be handled when printing children instead? Between line 392 and 393 seems pretty ideal.

& it doesn't look like the 'getHasNULLSibling' property would be needed to correctly emit the NULL element at that location.

394

Seems unnecessary to call 'getDebugInfoEntry' from within DWARFDie - rather than accessing the 'Die' member directly.

unittests/DebugInfo/DWARF/DWARFDebugInfoTest.cpp
1048–1059

As mentioned in previous comments - I'd suggest just dropping more of these checks. Do they add substantial value over the existing checks? (eg: are there bugs that are likely to be found by examining B & C's parentage that wouldn't be found by testing A's parentage?)

aprantl edited edge metadata.Dec 21 2016, 5:07 PM

Otherwise I think this looks nice. Does the memory usage go down measureably?

Added inline comments and responses. New patch coming with fixes.

include/llvm/DebugInfo/DWARF/DWARFDebugInfoEntry.h
39

If you change the type it won't be part of the uint32_t for HasNullSibling. I compiled a quick example:

#include <stdint.h>

struct Test {
  uint32_t Depth : 31;
  bool IsLastChild : 1;
};

int main (int argc, char const *argv[])
{
  Test t = { 123, false };
  if (t.Depth)
    return 0;
  return 1;
}

And I got the following DWARF:

0x000000b0:     TAG_structure_type [8] *
                 AT_name( "Test" )
                 AT_byte_size( 0x04 )
                 AT_decl_file( "/private/tmp/main.c" )
                 AT_decl_line( 3 )

0x000000b8:         TAG_member [9]  
                     AT_name( "Depth" )
                     AT_type( {0x00000000000000e3} ( uint32_t ) )
                     AT_decl_file( "/private/tmp/main.c" )
                     AT_decl_line( 4 )
                     AT_byte_size( 0x04 )
                     AT_bit_size( 0x1f )
                     AT_bit_offset( 0x01 )
                     AT_data_member_location( +0 )

0x000000cd:         TAG_member [9]  
                     AT_name( "IsLastChild" )
                     AT_type( {0x00000000000000f9} ( bool ) )
                     AT_decl_file( "/private/tmp/main.c" )
                     AT_decl_line( 5 )
                     AT_byte_size( 0x01 )
                     AT_bit_size( 0x01 )
                     AT_bit_offset( 0x00 )
                     AT_data_member_location( +3 )

0x000000e2:         NULL

Note that IsLastChild has a AT_data_member_location that is +3 so it is in its own block. The type has to be the same in order to share storage. Changing IsLastChild to a uint32_t we see things are right:

0x000000b0:     TAG_structure_type [8] *
                 AT_name( "Test" )
                 AT_byte_size( 0x04 )
                 AT_decl_file( "/private/tmp/main.c" )
                 AT_decl_line( 3 )

0x000000b8:         TAG_member [9]  
                     AT_name( "Depth" )
                     AT_type( {0x00000000000000e3} ( uint32_t ) )
                     AT_decl_file( "/private/tmp/main.c" )
                     AT_decl_line( 4 )
                     AT_byte_size( 0x04 )
                     AT_bit_size( 0x1f )
                     AT_bit_offset( 0x01 )
                     AT_data_member_location( +0 )

0x000000cd:         TAG_member [9]  
                     AT_name( "IsLastChild" )
                     AT_type( {0x00000000000000e3} ( uint32_t ) )
                     AT_decl_file( "/private/tmp/main.c" )
                     AT_decl_line( 5 )
                     AT_byte_size( 0x04 )
                     AT_bit_size( 0x01 )
                     AT_bit_offset( 0x00 )
                     AT_data_member_location( +0 )

0x000000e2:         NULL
lib/DebugInfo/DWARF/DWARFDie.cpp
354–356

We should probably just assert this object is valid. Clients should check prior to calling.

395–399

I believe this does belong here. A DIE could claim to have children but only have a NULL inside of it, then we would fail to print the NULL if we try to do it with children at line 392. Also, if RecurseDepth is zero, we still need to print the NULL die since it is at the current recurse level. Given these reasons, it seems that we still need the getHasNULLSibling() property and this code belongs where it currently is.

tools/dsymutil/DwarfLinker.cpp
1799

I agree. A later commit we can add a function like:

DieIteratorRange DWARFDie::Children();

So we can then so:

for (auto Child: DIE.Children()) {
unittests/DebugInfo/DWARF/DWARFDebugInfoTest.cpp
1049–1059

I argue this one is needed. A was the first child of CU, B was the second child after A had no children, and C is the third child that follows B which had children. I do think all are valid tests that we can't regress on. This is testing the DWARF parser and making sure it they can track down their relations correctly. If we significantly change DWARFDebugInfoEntry at any point, we should verify everything still works.

Knowing the current algorithm to find the parent when we find the parent for A the depth decreases only as you walk backwards. The depth for B does, but it also has A at the same depth. The depth for C will go up when it sees the children for B and then go back down again, so I do believe all these tests are valid.

1065–1067

Now when we ask for a sibling and it doesn't have one we get an invalid DWARFDie back so this is the new way to check for that. I am not sure adding a hasSibling() method will make the code any clearer? If you wrote a loop using the current code, it currently looks like:

for (auto Child = Die.getFirstChild(); Child; Child = Child.getSibling())

We the "Child;" test is using the operator bool. If we add the hasSibling() would code actually use it? Does this look clearer:

for (auto Child = Die.getFirstChild(); Child; Child = Child.hasSibling() ? Child.getSibling() : DWARFDie())

So if we introduced it, who would actually use it? We could switch the code to use operator bool:

EXPECT_FALSE((bool)B2.getSibling());

But I am not sure that makes things clearer.

clayborg updated this revision to Diff 82344.Dec 22 2016, 9:04 AM
clayborg edited edge metadata.
clayborg marked 4 inline comments as done.

Marked things as done.

tools/dsymutil/DwarfLinker.cpp
1799

Let me know if you guys want me to do the iterator thing in this patch?

My current DWARF (dSYM) file for clang has 3448988 NULL dies. This means if we parsed all Dies and had them in memory, we would save 53MB of memory.

Converting email only comments to be here with responses.

include/llvm/DebugInfo/DWARF/DWARFDebugInfoEntry.h
38

dblaike commented in email:

I think you might be misreading the DWARF (& it looks like the DWARF is a bit confusing, as well).

For starters - in both your examples, the byte_size of the struct is 4 bytes. So it seems storage is shared, or at least that using the same type doesn't improve/reduce the size of the struct - so on that basis I'd probably suggest using the bool type.

Beyond that, though. The AT_data_member_location is +3, as you say - not +4, so it is still using the top (or bottom - the counting here is a bit weird imho, but seems to work out) byte of the previous 4 byte value.

Also - we have /lots/ of similar mixed type bitfields in llvm, I think (have a grep around - it looks like it to me, at least) - so if this isn't doing the right thing I'd be /really/ surprised.

I did misread the DWARF, it is correct. I will switch this to bool.

lib/DebugInfo/DWARF/DWARFDie.cpp
399

dblaike said:

How would we succeed to print the NULL if we aren't printing children? There would be no 'last child' & so we wouldn't print the NULL in that case either.

So if you tell the CU DIE to print itself and have a recurse depth of 1 for DWARF like:

0x0: CU
0x1:  A
0x2    A1
0x3:   NULL
0x4:  B
0x5:   B1 
0x6:   NULL
0x7:  NULL

you would expect to see:

0x0: CU
0x1:  A
0x4:  B
0x7:  NULL

You're right though, we wouldn't print it in 392, but perhaps we should change the condition here from "if (Die->isLastChild())" to "if (Die->hasChildren())" ?

I think the misunderstanding here is that the NULL is actually terminating the current DIEs sibling chain, not the children's sibling chain. If we use "if (Die->hasChildren())" then we would print a NULL after every Die that has children, and since the NULL is for the current DIEs sibling, not the children, that would do the wrong thing:

0x0: CU
0x1:  A
0x2:  NULL // these would be incorrect
0x4:  B
0x5:  NULL // these would be incorrect
0x7:  NULL

Also, a test case for an empty child list would be good.

Agreed, I can add it.

unittests/DebugInfo/DWARF/DWARFDebugInfoTest.cpp
1049–1059

dblaike said:

Just to be clear - I'm never suggesting there are test cases we "can regress on", just asking about whether the test cases pull their weight - whether they exercise sufficiently distinct/interesting codepaths that make them important tests compared to the existing coverage.

It looks like C is perhaps a superset - it demonstrates we can go down into children, and back up. But, yes, good point that A and B are boundary/corner cases that are probably reasonable to test for separately.

Please add comments describing what makes each of these paths interesting. (eg: " verify an immediate parent", " verify skipping preceding siblings", "// verify skipping preceding niblings" (luls: https://en.wiktionary.org/wiki/nibling ) - those probably aren't the perfect comments, but a rough idea)

Will do.

probinson added inline comments.Dec 23 2016, 12:26 PM
include/llvm/DebugInfo/DWARF/DWARFDebugInfoEntry.h
38

No, please leave it as uint32_t. MSVC will do what you thought the DWARF said, and make the struct bigger unnecessarily.

@dblaikie yes we have mixed-type bitfields in llvm, but we also have places that deliberately use the same type when space savings matter. I see commits to change bitfield base types now and again for exactly that reason.

clayborg updated this revision to Diff 82906.Jan 3 2017, 9:40 AM

Updated the DWARF tests to make sure we are testing the parent finding code correctly. The previous test was testing children that were at a depth of 1, but that was a special case in the test where any child with a depth of 1 has a parent of the compile/type unit DIE. By increasing the depth we are now testing the ability to find the parent by looking for the depth that is 1 less than the current depth.

clayborg abandoned this revision.Jan 3 2017, 3:30 PM

There are test failures with the obj2yaml and yaml2obj. After thinking about this patch more, we should probably leave in the NULL DIEs. My reasoning is
the NULLs correctly show the structure of the DWARF without needing any magic to reconstruct the NULL dies like we needed to do when dumping the DWARF. This was easy to work around for the dumping since we were using the getFirstChild()/getSibling() calls, but other clients that just want to walk through the DWARFDebugInfoEntry array, like the obj2yaml, would need to magically reconstruct these extra NULL dies with the correct offset.

So in order to not ruin the accurate representation that we currently have I will leave the NULL dies in.