Skip to content

[lldb][Swift] Small steps towards fixing StepOut in async functions #9138

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

Conversation

felipepiovezan
Copy link

This PR adds tests demonstrating where the debugger fails, as well as exposing swift customizations that are not properly tagged.

The two final fixes we need are listed below. I have not included them in this PR because I am missing unittests for the StackID changes in them and because this PR stands on its own regardless of whether the fixes below are considered appropriate or not.

  1. [lldb] Make StackID comparison symmetric
  2. [lldb] Make StackID comparison search parent heap CFAs

This work builds on top the previously merged PRs improving the swift debugging story:

#9112
#9107
swiftlang/swift#75881
#9025

@felipepiovezan felipepiovezan changed the title [lldb] Small steps towards fixing StepOut in async functions [lldb][Swift] Small steps towards fixing StepOut in async functions Aug 16, 2024
@kastiglione
Copy link

This commit message has a mistake:
felipepiovezan@a9fe88e

For two StackIDs whose CFAs are on the stack, i.e., they are both async frames,

should be "whose CFAs are on the heap,"

@felipepiovezan
Copy link
Author

This commit message has a mistake: felipepiovezan@a9fe88e

For two StackIDs whose CFAs are on the stack, i.e., they are both async frames,

should be "whose CFAs are on the heap,"

Oops, good catch! I'll update it before posting the real PR

Comment on lines 83 to 84
if (lhs_cfa_on_stack && rhs_cfa_on_stack)
return {};

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this early exit is redundant to the final return {}.

also, this function could return bool, since it currently only ever returns two values: true or nullopt. But if it can only return two values, shouldn't it be true/false?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is true that this function only returns two values (on this patch, subsequent patches will change this though).

However, nullopt here really means: "the question isYoungerHeapCFAs is not applicable". So returning "false" would give the false impression that "isYoungerHeapCFAs" is returning "it's not younger".

The call site needs a way of knowing that it needs to try other comparisons, which is what nullopt represents here.

Copy link

@kastiglione kastiglione Aug 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

subsequent patches will change this though

imo that's where this change should happen

I also think this would be best served by an enum, which can be explicit about the case meanings. I think optional<bool> should be avoided in any language.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

another option: instead of mixing two predicates into one, have one predicate which checks if a CFA is heap, and another that checks if one heap CFAs is younger than another heap CFA.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

one predicate which checks if a CFA is heap, and another that checks if one heap CFAs is younger than another heap CFA.

Are you suggesting something like this?

if (are both heap cfas(cfa1, cfa2))
   return compare heap cfas (cfa1, cfa2)

The problem with that is that we still need to handle the case where one of them is on the heap and the other one is not. We could do:

if (at least one is heap cfaa(cfa1, cfa2))
   return compare heap cfas (cfa1, cfa2)

But then the "compare heap cfas" function would need to repeat the check to figure out which one is on the heap.

The other problem with this approach is that there will be another third case where we "don't have an opinion", which is when we were chasing continuation pointers and decided to give up because we reached some upper limit on the number of tries.

I'll try the enumeration approach if you believe that makes sense, though IMO the optional<bool> is quite expressive here

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed in the newest version


@swiftTest
@skipIf(oslist=["windows", "linux"])
@skipIf("rdar://133849022")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given the PR description:

This PR adds tests demonstrating where the debugger fails

should this be an expectedFailure?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was debating this but we would risk having sporadic xpasses: if the stars align and the "heap cfas" numeric values are such that certain StackIDs comparisons behave in some pattern, the test could pass.

@felipepiovezan
Copy link
Author

@adrian-prantl have you had a chance to look at this?
@kastiglione any other comments?

# Expected frames:
# ASYNC___0___ <- ASYNC___1___ <- ASYNC___2___ <- ASYNC___3___ <- Main
num_frames = 5
func_names = list(map(lambda frame: frame.GetFunctionName(), frames[:num_frames]))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a more pythonic formation:

Suggested change
func_names = list(map(lambda frame: frame.GetFunctionName(), frames[:num_frames]))
func_names = [frame.GetFunctionName() for frame in frames[:num_frames]]

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

func_names = list(map(lambda frame: frame.GetFunctionName(), frames[:num_frames]))
for idx in range(num_frames - 1):
self.assertRegex(func_names[idx], f"ASYNC___{idx}___")
self.assertRegex(func_names[num_frames - 1], "Main")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these assertRegex can be assertIn since there's no regular expressions in play.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point!

@felipepiovezan felipepiovezan force-pushed the felipe/step_out_preliminaries branch from da5facc to 52f2fbc Compare August 20, 2024 18:21
@felipepiovezan
Copy link
Author

Addressed review comments

@felipepiovezan felipepiovezan force-pushed the felipe/step_out_preliminaries branch from 52f2fbc to f44c46e Compare August 20, 2024 20:07
@felipepiovezan
Copy link
Author

Changed "IsYounger" function to return an enumeration

if (lhs.IsCFAOnStack(process) && !rhs.IsCFAOnStack(process))
if (lhs_cfa_on_stack && !rhs_cfa_on_stack)
return HeapCFAComparisonResult::Younger;
return {};

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be NoOpinion?

(I'm not sure what {} evaluates to for an enum, but I presume the first case)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oooops.

num_frames = 5
func_names = [frame.GetFunctionName() for frame in frames[:num_frames]]
for idx in range(num_frames - 1):
self.assertRegex(func_names[idx], f"ASYNC___{idx}___")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this can also be assertIn if you want

…rison

The code moved in this patch is a swift customization, even though it is missing
the customization markers. This simple refactor should also make it clear that
there is a bug in this function: it is not symmetric.
While this could, theoretically, be sent upstream, we have unfortunately renamed
some of the core APIs downstream ("IsYounger" doesn't exist upstream, nor
anything "heap cfa" related).
@felipepiovezan felipepiovezan force-pushed the felipe/step_out_preliminaries branch from f44c46e to 874b20b Compare August 20, 2024 21:51
@felipepiovezan
Copy link
Author

Update test and address error in the previous update

@felipepiovezan felipepiovezan merged commit 53abe0d into swiftlang:stable/20240723 Aug 20, 2024
@felipepiovezan felipepiovezan deleted the felipe/step_out_preliminaries branch August 20, 2024 22:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants