This is an archive of the discontinued LLVM Phabricator instance.

Handle link of NoDebug CU with a CU that has debug emission enabled
ClosedPublic

Authored by tejohnson on Feb 9 2017, 7:54 AM.

Details

Summary

This is an issue both with regular and Thin LTO. When we link together
a DICompileUnit that is marked NoDebug (e.g when compiling with -g0
but applying an AutoFDO profile, which requires location tracking
in the compiler) and a DICompileUnit with debug emission enabled,
we can have failures during dwarf debug generation. Specifically,
when we have inlined from the NoDebug compile unit into the debug
compile unit, we can fail during construction of the abstract and
inlined scope DIEs. This is because the SPMap does not include NoDebug
CUs (they are skipped in the debug_compile_units_iterator).

This patch fixes the failures by skipping SPs from NoDebug CUs when
generating these DIEs. I am not sure if this is the right fix from
the debug generation standpoint, however. I don't know if we should
instead either upgrade NoDebug CU on link with a debug CU, or enable
adding these NoDebug CUs into the SPMap.

Event Timeline

tejohnson created this revision.Feb 9 2017, 7:54 AM
dblaikie edited edge metadata.Feb 9 2017, 9:26 AM

Superficially (I haven't looked at all the codepaths, etc) the code change looks OK.

Testing could probably be a lot simpler - can you take the merged IR and test that, rather than running LTO steps in the test? It seems the resulting IR is what we want to test/what's being fixed, not the path that was used to reach it.

Superficially (I haven't looked at all the codepaths, etc) the code change looks OK.

Testing could probably be a lot simpler - can you take the merged IR and test that, rather than running LTO steps in the test? It seems the resulting IR is what we want to test/what's being fixed, not the path that was used to reach it.

Great idea. Uploading patch with new test now.

tejohnson updated this revision to Diff 87834.Feb 9 2017, 10:26 AM

Change to a test that is already combined.

Are the locations properly respected/ignored in this case? (what does the line table end up looking like for the instructions in the nodebug inlined code?)

Would it be worth/possible omitting these scopes from the LexicalScope tree in the first place, rather than filtering out here after-the-fact?

test/DebugInfo/Generic/debug_and_nodebug_CUs.ll
4

Include comments about the source code and commands to reproduce the IR (since debug info metadata is a bit hard to read, this can help with understanding exactly what the test is testing, reproducing, etc)

Are the locations properly respected/ignored in this case? (what does the line table end up looking like for the instructions in the nodebug inlined code?)

Here is the result of objdump -WL (let me know if there is something else you wanted to see):

$ objdump -WL a.out

a.out: file format elf64-x86-64

Decoded dump of debug contents of section .debug_line:

CU: debug.c:
File name Line number Starting address
debug.c 2 0

./nodebug.c:[++]
nodebug.c 3 0x4

./debug.c:[++]
debug.c 2 0x10

Would it be worth/possible omitting these scopes from the LexicalScope tree in the first place, rather than filtering out here after-the-fact?

That I am not sure about, as I'm not really familiar with this code. I am hoping you will tell me. =)

test/DebugInfo/Generic/debug_and_nodebug_CUs.ll
4

Will do.

Are the locations properly respected/ignored in this case? (what does the line table end up looking like for the instructions in the nodebug inlined code?)

Here is the result of objdump -WL (let me know if there is something else you wanted to see):

$ objdump -WL a.out

a.out: file format elf64-x86-64

Decoded dump of debug contents of section .debug_line:

CU: debug.c:
File name Line number Starting address
debug.c 2 0

./nodebug.c:[++]
nodebug.c 3 0x4

./debug.c:[++]
debug.c 2 0x10

Ah, I meant more the content of the line table (llvm-dwarfdump -debug-dump=line should show you this).

Would it be worth/possible omitting these scopes from the LexicalScope tree in the first place, rather than filtering out here after-the-fact?

That I am not sure about, as I'm not really familiar with this code. I am hoping you will tell me. =)

I think the right place is /probably/ LexicalScopes::extractLexicalScopes (lib/CodeGen/LexicalScopes.cpp) - if it'

Oops, sent incomplete message.

But, yes, in LexicalScopes::extractLexicalScopes ideally, I think, it would not consider locations that aren't intended for debug info.

Are the locations properly respected/ignored in this case? (what does the line table end up looking like for the instructions in the nodebug inlined code?)

Here is the result of objdump -WL (let me know if there is something else you wanted to see):

$ objdump -WL a.out

a.out: file format elf64-x86-64

Decoded dump of debug contents of section .debug_line:

CU: debug.c:
File name Line number Starting address
debug.c 2 0

./nodebug.c:[++]
nodebug.c 3 0x4

./debug.c:[++]
debug.c 2 0x10

Ah, I meant more the content of the line table (llvm-dwarfdump -debug-dump=line should show you this).

Here is the output of running the above on the new a.out generated from the included test case (with the new version of the patch I will upload momentarily):

a.out: file format ELF64-x86-64

.debug_line contents:
Line table prologue:

total_length: 0x0000004d
     version: 2

prologue_length: 0x0000002b
min_inst_length: 1
default_is_stmt: 1

  line_base: -5
 line_range: 14
opcode_base: 13

standard_opcode_lengths[DW_LNS_copy] = 0
standard_opcode_lengths[DW_LNS_advance_pc] = 1
standard_opcode_lengths[DW_LNS_advance_line] = 1
standard_opcode_lengths[DW_LNS_set_file] = 1
standard_opcode_lengths[DW_LNS_set_column] = 1
standard_opcode_lengths[DW_LNS_negate_stmt] = 0
standard_opcode_lengths[DW_LNS_set_basic_block] = 0
standard_opcode_lengths[DW_LNS_const_add_pc] = 0
standard_opcode_lengths[DW_LNS_fixed_advance_pc] = 1
standard_opcode_lengths[DW_LNS_set_prologue_end] = 0
standard_opcode_lengths[DW_LNS_set_epilogue_begin] = 0
standard_opcode_lengths[DW_LNS_set_isa] = 1

Dir  Mod Time   File Len   File Name
---- ---------- ---------- ---------------------------

file_names[ 1] 0 0x00000000 0x00000000 debug.c
file_names[ 2] 0 0x00000000 0x00000000 nodebug.c

Address Line Column File ISA Discriminator Flags


0x0000000000000000 2 0 1 0 0 is_stmt
0x0000000000000004 3 1 2 0 0 is_stmt prologue_end
0x0000000000000010 2 21 1 0 0 is_stmt
0x0000000000000014 2 21 1 0 0 is_stmt end_sequence

Would it be worth/possible omitting these scopes from the LexicalScope tree in the first place, rather than filtering out here after-the-fact?

That I am not sure about, as I'm not really familiar with this code. I am hoping you will tell me. =)

I think the right place is /probably/ LexicalScopes::extractLexicalScopes (lib/CodeGen/LexicalScopes.cpp) - if it'

Uploading new patch that does that instead.

tejohnson updated this revision to Diff 88285.Feb 13 2017, 5:20 PM

Address comments

dblaikie added inline comments.Feb 14 2017, 9:34 AM
lib/CodeGen/LexicalScopes.cpp
86

This probably won't catch all cases - if the location was in a subscope within the function (eg: try adding a {} to the function which should add an extra scope). Walking the scope chain until a DISubprogram is probably needed here - I forget if we have a nice wrapped up utility for that. Don't see one at a glance.

It should be an invariant/guaranteed that a DISubprogram is reached (& is the end of the scope chain), so something like:

oh, wait, no, here it is: http://llvm.org/docs/doxygen/html/classllvm_1_1DILocalScope.html#a747dd1690fc477a8d9638fa37a30ced8

so, I think, maybe:

if (cast<DILocalScope>(MIDL->getScope())->getSubprogram()->getUnit()->getEmissionKind() == DICompileUnit::NoDebug) {
  ...
test/DebugInfo/Generic/debug_and_nodebug_CUs.ll
5

Include the steps for reproducing this test case (basically the RUN lines, and the original C source/steps for producing the IR, from the first version of the test) - helps to visualize/document/reproduce the situation.

tejohnson added inline comments.Feb 14 2017, 9:41 AM
lib/CodeGen/LexicalScopes.cpp
86

I had just found the DILocalScope::getSubprogram() helper, since I was still getting the original seg fault when I tried the original failing internal code with this change. So now I have:

// Ignore locations linked from a NoDebug compile unit.
auto *SP = MIDL->getScope()->getSubprogram();
if (SP->getUnit()->getEmissionKind() == DICompileUnit::NoDebug) {
  PrevMI = &MInsn;
  continue;
}

(Don't need to cast MIDL->getScope() since it returns a DILocalScope). But I am *still* getting the original seg fault. Any idea what else I could be missing?

test/DebugInfo/Generic/debug_and_nodebug_CUs.ll
5

Will do

One drive by comment while I'm looking at it.

test/DebugInfo/Generic/debug_and_nodebug_CUs.ll
26

Clean up attributes :)

tejohnson updated this revision to Diff 88597.Feb 15 2017, 1:15 PM

Address comments and apply discussed fixes. Update test.

I was hoping to simplify this a bit by having DwarfDebug (well, DebugHandlerBase) filter and never try to build a LexicalScopes for a non-debug-having function (so it wouldn't need the filter at the start of LexicalScopes::initialize), except LiveDebugValues also creates LexicalScopes and doesn't have any existing filtering/probably shouldn't have any.

So I'm back around to basically this change, with a couple of questions.

lib/CodeGen/LexicalScopes.cpp
87โ€“94

Which test case/behavior does this address that the change in getOrCreateLexicalScope wasn't able to handle?

182โ€“184

What about asserting in LexicalScope's ctor?

This was what I tried & seemed to provide the checking desired?

diff --git include/llvm/CodeGen/LexicalScopes.h include/llvm/CodeGen/LexicalScopes.h
index 7d7e48af2a0..b6d524fce44 100644

  • include/llvm/CodeGen/LexicalScopes.h

+++ include/llvm/CodeGen/LexicalScopes.h
@@ -49,7 +49,11 @@ public:

         bool A)
: Parent(P), Desc(D), InlinedAtLocation(I), AbstractScope(A),
  LastInsn(nullptr), FirstInsn(nullptr), DFSIn(0), DFSOut(0) {
  • assert((!D || D->isResolved()) && "Expected resolved node");

+ assert(D);
+ assert(D->getSubprogram()->getUnit()->getEmissionKind() !=
+ DICompileUnit::NoDebug &&
+ "Don't build lexical scopes for non-debug locations");
+ assert(D->isResolved() && "Expected resolved node");

assert((!I || I->isResolved()) && "Expected resolved node");
if (Parent)
  Parent->addChild(this);
tejohnson added inline comments.Feb 16 2017, 11:38 AM
lib/CodeGen/LexicalScopes.cpp
87โ€“94

Good point, I think it doesn't add anything now (and now that I am looking at this I realize I forgot to change it to use getSubprogram() rather than use the dyn_cast). Let me try removing that.

182โ€“184

Good idea, will do that instead.

tejohnson updated this revision to Diff 88763.Feb 16 2017, 11:57 AM

Address review comments

dblaikie accepted this revision.Feb 16 2017, 1:05 PM

Add the assert to LexicalScope's ctor and (assuming that works) this should be good.

This revision is now accepted and ready to land.Feb 16 2017, 1:05 PM

Add the assert to LexicalScope's ctor and (assuming that works) this should be good.

It works - it is there in the new patch I uploaded.

Add the assert to LexicalScope's ctor and (assuming that works) this should be good.

It works - it is there in the new patch I uploaded.

Ah, yeah - blindness on my side. (commit whenever you're ready)

This revision was automatically updated to reflect the committed changes.

LGTM as well. Thanks for the thorough work here both of you. :)