-
Notifications
You must be signed in to change notification settings - Fork 12.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Incorrect line table generation for await points #123341
Comments
@rustbot labels +AsyncAwait-Triaged We discussed this in the async triage call and, based on the report, it sounds like a possible bug that will need some careful attention by the right person or persons familiar with this infrastructure. |
My initial impression is that the |
After deduplication the block conceptually belongs to multiple locations in the source. Although these blocks are unreachable, in rust-lang#123341 we did come across a real side effect, an unreachable block that survives into the compiled code can cause a debugger to set a breakpoint on the wrong instruction. Erasing the source information ensures that a debugger will never be misled into thinking that the unreachable block is worth setting a breakpoint on, especially after rust-lang#128627. Technically we don't need to erase the source information if all the deduplicated blocks have identical source information, but tracking that seems like more effort than it's worth.
There are arguably a number of bugs here (that the instruction is marked with is_stmt=1, that LLVM doesn't remove the unreachable ud2, that the ud2 is attributed to a later line), but #128628 fixes this. During desugaring the line with the await is desugared into a call to the relevant poll implementation followed by a match on its result. That match has an unreachable block that handles the catchall case and that unreachable block is assigned the location of the await. Desugaring also breaks down the containing async function into a closure that matches on the state of the generated future. That match also has an unreachable block that handles the catchall case. That match goes at the very top of the resume function, but the new unreachable block is appended to the end. Then SimplifyCFG merges the two unreachable blocks. The unreachable block that was created for the await survives because it's first in basic block order, and the match that was placed in the very first basic block is modified to use it for its catchall case. Later the ud2 from the unreachable block is pulled forwards to fall after the match in the very first basic block. This unreachable block still has the source information for the await call, and this ends up causing the issue where the first code attributed to the await line in program order is an unreachable ud2 at the top of the function. A solution to this is simply to erase the source information when we merge unreachable blocks and not attribute them to any line at all. |
@rustbot claim |
…, r=nnethercote When deduplicating unreachable blocks, erase the source information. After deduplication the block conceptually belongs to multiple locations in the source. Although these blocks are unreachable, in rust-lang#123341 we did come across a real side effect, an unreachable block that survives into the compiled code can cause a debugger to set a breakpoint on the wrong instruction. Erasing the source information ensures that a debugger will never be misled into thinking that the unreachable block is worth setting a breakpoint on, especially after rust-lang#128627. Technically we don't need to erase the source information if all the deduplicated blocks have identical source information, but tracking that seems like more effort than it's worth. I'll let njn redirect this one too. r? `@nnethercote`
Rollup merge of rust-lang#128628 - khuey:simply-cfg-erase-source-info, r=nnethercote When deduplicating unreachable blocks, erase the source information. After deduplication the block conceptually belongs to multiple locations in the source. Although these blocks are unreachable, in rust-lang#123341 we did come across a real side effect, an unreachable block that survives into the compiled code can cause a debugger to set a breakpoint on the wrong instruction. Erasing the source information ensures that a debugger will never be misled into thinking that the unreachable block is worth setting a breakpoint on, especially after rust-lang#128627. Technically we don't need to erase the source information if all the deduplicated blocks have identical source information, but tracking that seems like more effort than it's worth. I'll let njn redirect this one too. r? `@nnethercote`
I don't have the power to close issues but this is fixed now. |
I’m a developer for the Fuchsia Debugger (zxdb), and noticed an issue with setting a breakpoint on a line of rust code containing an await. In particular, this issue was filed: Breakpoint set on await line does not stop. There are additional details in that bug report with my investigation, and the workaround for zxdb will be similar to what LLDB does.
While debugging some async code, I noticed that a breakpoint I set on a particular line number wasn’t being hit. It turned out that the line table for the async function call site looked incorrect. Namely, the line table entry for a line containing an
await
generated anis_stmt
on aud2
instruction, which is never actually executed. The typical algorithm debuggers use for placing breakpoints uses the lowest address for the function that is markedis_stmt
in the line table, which in this case is incorrect, since the first code either isn’t associated with the callsite, or shouldn’t be markedis_stmt
.This code is a minimal reproducer:
Here is the relevant section of the line table, the above source is located in file index 3 in the fourth column, and the line indicated above is 18 in the second column (
llvm-dwarfdump-15 --debug-line
):LLDB sets a breakpoint in several locations:
Luckily, this is generally correct. The breakpoint hits the call instruction for function. The thread is stopped again when you hit the third location when the Future is dropped, which is wacky. But that’s an issue with LLDB.
GDB has better results, since it just sets the breakpoint addresses on the ud2 instruction and on the call site, avoiding the double stoppage that LLDB has.
rustc --version --verbose
:The text was updated successfully, but these errors were encountered: