swiftlang / llvm-project

The LLVM Project is a collection of modular and reusable compiler and toolchain technologies. This fork is used to manage Swift’s stable releases of Clang as well as support the Swift project.
https://llvm.org
Other
1.12k stars 331 forks source link

[lldb][swift] Fix StackID comparisons to enable stepping out of async functions #9162

Closed felipepiovezan closed 1 month ago

felipepiovezan commented 2 months ago

This should be the final two fixes we need in order to enable stepping out of async functions.

The first commit makes the stack comparison operation symmetric, so that if IsYounger(A, B) returns true, then IsYounger(B, A) returns false.

The second commit implements StackID comparisons in the case where both CFAs are on the heap.

Pasted the commit messages below for ease of reading.


[lldb] Make StackID comparison symmetric

Currently, if we're comparing a StackID on the stack with one StackID on the
heap, we perform a special kind of IsYounger comparison. However, if
`IsYounger(A, b)` returns true, it must be the case that `IsYounger(B, A)`
returns false. But this is not necessarily the case today because, before this
patch, we would fallback to the traditional "stack" comparison, which is wrong
if one of the StackIDs is on the heap.

[lldb] Make StackID comparison search parent heap CFAs

For two StackIDs whose CFAs are on the heap, i.e., they are both async frames,
we can decide which one is younger by finding one async context in the
continuation chain of the other.

A lot of stepping algorithms rely on frame comparisons. In particular, this
patch fixes stepping out of async functions, and makes strides towards fixing
other stepping algorithms.

The explanation below is not needed to understand this patch, but it sheds some
light into why the most common kind of step out fails.

Consider the case where a breakpoint was set inside some async function, the
program hit that breakpoint, and then a "step out" operation is performed. This
is a very common use case.

The thread plan looks like this:

0. Single step past breakpoint.
1. Step out

LLDB performs a single step, and thread plan 0 says "continue running, I'm
done". Before continuing program execution, LLDB asks all thread plans, in this
case there's only the step out plan, if it should stop.

To answer the "should stop" question, the step out plan compares the current
frame (which is the same as when step out was pushed, since LLDB has only
performed a single step so far) with its target frame (which is just one frame
above). In this scenario, the current frame is async, and so the parent frame
must also be async. The StepOut plan is going to compare two async StackIDs.

Using the incorrect comparison, StepOut concludes the current frame is _older_
than the target frame. In other words, the program reached an older frame
without ever stopping on the target frame. Something weird must have happened,
and the plan concludes it is done.
felipepiovezan commented 2 months ago

Fixed typo in commit message

felipepiovezan commented 2 months ago

@jasonmolenda I've added a commit fixing the default return value of "IsCFAOnHeap" to conservatively say no if we don't have information.

Addressed all review comments