llvm / llvm-project

The LLVM Project is a collection of modular and reusable compiler and toolchain technologies.
http://llvm.org
Other
28.63k stars 11.83k forks source link

debuginfo assertion when inlining: attachment points at wrong subprogram for function #25938

Closed alexcrichton closed 8 years ago

alexcrichton commented 8 years ago
Bugzilla Link 25564
Resolution FIXED
Resolved on Jan 28, 2016 22:48
Version trunk
OS All
Attachments Failing IR
CC @majnemer,@rnk

Extended Description

When compiling the attached IR with llc, I get:

!dbg attachment points at wrong subprogram for function !​1 = distinct !DISubprogram(name: "main", linkageName: "_ZN19backtrace_debuginfo4mainE", scope: !​3, file: !​2, line: 1, type: !​4, isLocal: true, isDefinition: true, scopeLine: 1, flags: DIFlagPrototyped, isOptimized: true, templateParams: !​5, variables: !​5) void ()* @​foo call void @​bar(), !dbg !​19 !​19 = !DILocation(line: 1219, scope: !​20, inlinedAt: !​21) !​25 = distinct !DILexicalBlock(scope: !​26, file: !​2, line: 1, column: 10) !​26 = distinct !DISubprogram(name: "main", linkageName: "_ZN19backtrace_debuginfo4mainE", scope: !​3, file: !​2, line: 1, type: !​4, isLocal: true, isDefinition: true, scopeLine: 1, flags: DIFlagPrototyped, isOptimized: true, templateParams: !​5, variables: !​5) LLVM ERROR: Broken function found, compilation aborted!

But when the target is changed to x86_64-unknown-linux-gnu the file compiles successfully. I'm not 100% sure that we correctly emit debuginfo in the first place (not my area of expertise), and this may also be related to r252219 but I figured it was odd at least that it failed to compile with an MSVC target yet successfully compiled for a Linux one!

alexcrichton commented 8 years ago

er I meant to indicate that this is fixed, so closing

alexcrichton commented 8 years ago

It looks like this is now failed on LLVM trunk (yay!)

991901f3-cc14-4404-b340-165691b62a58 commented 8 years ago

Minimally reduced: define void @​main() !dbg !​1 { call void @​fails_if_inlined() ret void }

define void @​fails_if_inlined() { unreachable, !dbg !​3 }

!llvm.module.flags = !{#0}

!​0 = !{i32 2, !"Debug Info Version", i32 3} !​1 = distinct !DISubprogram(name: "main", file: !​2, line: 1) !​2 = !DIFile(filename: "foo.rs", directory: "bar") !​3 = !DILocation(line: 1, scope: !​4) !​4 = distinct !DILexicalBlock(scope: !​5, file: !​2, line: 1, column: 1) !​5 = distinct !DISubprogram(name: "drop", file: !​2, line: 1)

alexcrichton commented 8 years ago

I've updated the title to reflect that it doesn't only apply to MSVC any more and I've also managed to narrow down the IR a little bit further to hopefully be even more minimal! It does looks like something odd is going on with inlining yeah. If the call is manually inlined (e.g. !dbg !​3 is added to the call to fails_if_inlined) then the assert will happen as well.

declare i32 @​personality(...)
declare void @​foo()
declare void @​bar(i64*)

define void @​main() personality i32 (...)* @​personality !dbg !​1 {
%slot = alloca i64
invoke void @​foo() to label %normal unwind label %bad

normal:
ret void

bad:
%cleanuppad = cleanuppad []
call void @​fails_if_inlined(i64* %slot)
cleanupret %cleanuppad unwind to caller
}

define void @​fails_if_inlined(i64) {
call void @​bar(i64
%0), !dbg !​3
ret void
}

!llvm.module.flags = !{#0}

!​0 = !{i32 2, !"Debug Info Version", i32 3}
!​1 = distinct !DISubprogram(name: "main", file: !​2, line: 1)
!​2 = !DIFile(filename: "foo.rs", directory: "bar")
!​3 = !DILocation(line: 1, scope: !​4)
!​4 = distinct !DILexicalBlock(scope: !​5, file: !​2, line: 1, column: 1) !​5 = distinct !DISubprogram(name: "drop", file: !​2, line: 1)

rnk commented 8 years ago

Cool, that repro looks like it doesn't have any funclet weirdness in it. If it fails at -O2, the inliner is probably doing something weird with your debug info here.

alexcrichton commented 8 years ago

Hm, and through further tinkering it looks like this isn't MSVC-specific, if the target at the top is commented out it fails as well for Linux. Perhaps a red herring!

alexcrichton commented 8 years ago

Ok, I managed to reduce this a little differently:

https://gist.github.com/alexcrichton/ce1a275616a0eefdab36

There's a comment on there as well, but opt test.ll will pass but opt test.ll -O2 will fail (generating the same error as before). Hope that helps!

alexcrichton commented 8 years ago

Hm although my reduction strategy involved literally deleting large swaths of basic blocks to be replaced with unreachable instructions, so I may have actually deleted something a little too aggressively by accident. I could try a re-reduction though where I avoid deleting those instructions.

rnk commented 8 years ago

I'd be surprised if you accidentally reduced out the cleanupendpad. It's far more likely that the rust frontend never generated it, since Joseph added it to our model after the fact for C# finally blocks.

alexcrichton commented 8 years ago

Ah unfortunately there's actually not really a great translation to C of what this is doing. I've been working to migrate the MSVC target of the Rust compiler to use the new SEH intrinsics for unwinding (in Rust we use it for panics), and this was one of the failures I saw in the test suite for our 64-bit target.

The original test case 1 is significantly more complicated and the IR is just reduced by me by hand, so I may have incorrectly deleted something or it could be legitimate that this is just bogus IR that should throw an error like this. If it's helpful I can also just upload a much larger version of the IR that files (e.g. the one that the Rust compiler actually generates). I basically used insertion of unreachable as a way to strip out tons of unused variables and such.

rnk commented 8 years ago

Adding a cleanupendpad to the attached IR makes the verifier issue go away.

rnk commented 8 years ago

What is the attached IR trying to do? I'm assuming this is attempting to represent code like this:

try { try { bar(); } finally { bar(); } } finally { bar(); unreachable; }

The second call to 'bar' needs to unwind through a cleanupendpad before it unwinds to the second cleanuppad. This gives the backend the lexical scoping information it needs to generate tables. David Majnemer has plans to encode the scoping information in a different way, so cleanupendpad won't be around for too long.

Second, we have a limitation in our C specific handler table emission that makes it hard to lower this correctly: // Break out before we enter into a finally funclet. // FIXME: We need to emit separate EH tables for cleanups. MachineFunction::const_iterator End = MF->end(); MachineFunction::const_iterator Stop = std::next(MF->begin()); while (Stop != End && !Stop->isEHFuncletEntry()) ++Stop;

Aside from all that, I'll take a look at the debug info verifier issue here. :)