Closed Sandlot19 closed 2 months ago
@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 ud2
instruction should be annotated as "line 0" (compiler generated) like the following instruction.
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
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
: