Skip to content

[lldb][swift] Filter unnecessary funclets when setting line breakpoints #10259

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

Prior to this commit, line breakpoints that match multiple funclets were being filtered out to remove "Q" funclets from them. This generally works for simple "await foo()" expressions, but it is not a comprehensive solution, as it does not address the patterns emerging from async let and await <async_let_variable> statements.

This commit generalizes the filtering algorithm:

  1. Locations are bundled together based on the async function that generated them.
  2. For each bundle, choose the funclet with the smallest "number" as per its mangling.

To see why this is helpful, consider an async let statement like:
async let timestamp3 = getTimestamp(i: 44)
It creates 4 funclets:

2.1:
  function = (3) suspend resume partial function for test.some_other_async() async -> ()
  mangled function = $s4test16some_other_asyncyyYaFTY2_
2.2:
  function = implicit closure #1 @Sendable () async -> Swift.Int in test.some_other_async() async -> ()
  mangled function = $s4test16some_other_asyncyyYaFSiyYaYbcfu_
2.3:
  function = (1) await resume partial function for implicit closure #1 @Sendable () async -> Swift.Int in test.some_other_async() async -> ()
  mangled function = $s4test16some_other_asyncyyYaFSiyYaYbcfu_TQ0_
2.4:
  function = (2) suspend resume partial function for implicit closure #1 @Sendable () async -> Swift.Int in test.some_other_async() async -> ()
  mangled function = $s4test16some_other_asyncyyYaFSiyYaYbcfu_TY1_

The first is for the LHS, the others are for the RHS expression and are only executed by the new Task. Only 2.1 and 2.2 should receive breakpoints.

Likewise, a breakpoint on an await <async_let_variable> line would create 3 funclets:

3.1:
  function = (3) suspend resume partial function for test.some_other_async() async -> ()
  mangled function = $s4test16some_other_asyncyyYaFTY2_
3.2:
  function = (4) suspend resume partial function for test.some_other_async() async -> ()
  mangled function = $s4test16some_other_asyncyyYaFTY3_
3.3:
  function = (5) suspend resume partial function for test.some_other_async() async -> ()
  mangled function = $s4test16some_other_asyncyyYaFTY4_

The first is for "before" the await, the other two for "after". Only the first should receive a breakpoint.

rdar://146123772

@felipepiovezan felipepiovezan requested a review from a team as a code owner March 14, 2025 13:56
@felipepiovezan
Copy link
Author

@swift-ci test

std::optional<uint64_t> funclet_number;

/// Helper function for logging.
std::string to_str() const {

Choose a reason for hiding this comment

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

The more idiomatic thing in LLVM is to add a free-standing toString() function.

Choose a reason for hiding this comment

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

which I think will make it work with operator<< as well

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!

/// Map each unique Function in sc_list to a Demangle::NodePointer, or null if
/// demangling is not possible.
llvm::SmallVector<AsyncInfo> GetAsyncInfo(llvm::ArrayRef<SymbolContext> sc_list,
swift::Demangle::Context &ctx) {

Choose a reason for hiding this comment

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

Could this be a function that translates just one symbol context and then you use a std::algorithm to apply that on an entire vector?

Copy link
Author

Choose a reason for hiding this comment

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

Sadly there is no std::transform_if or anything along those lines that help here.
We would need to apply a unique filter based on SymbolContext.function and then std::transform.

Prior to this commit, line breakpoints that match multiple funclets were
being filtered out to remove "Q" funclets from them. This generally
works for simple "await foo()" expressions, but it is not a
comprehensive solution, as it does not address the patterns emerging
from `async let` and `await <async_let_variable>` statements.

This commit generalizes the filtering algorithm:
1. Locations are bundled together based on the async function that
   generated them.
2. For each bundle, choose the funclet with the smallest "number" as per
   its mangling.

To see why this is helpful, consider an `async let` statement like:
  `async let timestamp3 = getTimestamp(i: 44)`
It creates 4 funclets:

```
2.1:
  function = (3) suspend resume partial function for test.some_other_async() async -> ()
  mangled function = $s4test16some_other_asyncyyYaFTY2_
2.2:
  function = implicit closure swiftlang#1 @sendable () async -> Swift.Int in test.some_other_async() async -> ()
  mangled function = $s4test16some_other_asyncyyYaFSiyYaYbcfu_
2.3:
  function = (1) await resume partial function for implicit closure swiftlang#1 @sendable () async -> Swift.Int in test.some_other_async() async -> ()
  mangled function = $s4test16some_other_asyncyyYaFSiyYaYbcfu_TQ0_
2.4:
  function = (2) suspend resume partial function for implicit closure swiftlang#1 @sendable () async -> Swift.Int in test.some_other_async() async -> ()
  mangled function = $s4test16some_other_asyncyyYaFSiyYaYbcfu_TY1_
```

The first is for the LHS, the others are for the RHS expression and are
only executed by the new Task. Only 2.1 and 2.2 should receive
breakpoints.

Likewise, a breakpoint on an `await <async_let_variable>` line would
create 3 funclets:
```
3.1:
  function = (3) suspend resume partial function for test.some_other_async() async -> ()
  mangled function = $s4test16some_other_asyncyyYaFTY2_
3.2:
  function = (4) suspend resume partial function for test.some_other_async() async -> ()
  mangled function = $s4test16some_other_asyncyyYaFTY3_
3.3:
  function = (5) suspend resume partial function for test.some_other_async() async -> ()
  mangled function = $s4test16some_other_asyncyyYaFTY4_
```

The first is for "before" the await, the other two for "after". Only the
first should receive a breakpoint.
@felipepiovezan felipepiovezan force-pushed the felipe/new_filtering_mechanism branch from 2a3a35b to de7263a Compare March 15, 2025 09:48
@felipepiovezan
Copy link
Author

@swift-ci test

@felipepiovezan
Copy link
Author

I am going to merge this to unblock follow up patches, but happy to address any other review feedback in post.

@felipepiovezan felipepiovezan merged commit 52c98fb into swiftlang:stable/20240723 Mar 17, 2025
3 checks passed
@felipepiovezan felipepiovezan deleted the felipe/new_filtering_mechanism branch March 17, 2025 11:22
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.

2 participants