Skip to content

Commit

Permalink
[lldb][swift] Filter await-resume funclets when setting breakpoints
Browse files Browse the repository at this point in the history
These funclets only serve to `task_dealloc` previously allocated tasks when
returning from an async call, and immediately `task_switch` to the next
await-suspend funclet (which contains real user code).

By not filtering out these funclets, any breakpoint on a line with an async call
will cause execution to pause 3 times: once before the call, twice when
"returning" from the call, which makes for a confusing experience.

The patch does the filtering on `BreakpointResolver::SetSCMatchesByLine`, which
is the common code between BreakpointResolverFileLine and
BreakpointResolverFileRegex.

We also considered changing the debug line information in any of the many
different lowering stages the swift compiler, but this turned out to be very
complex to do in a targeted way; more often than not, a handful of early-IR
coroutine instructions get expanded into multiple function clones, all
inheriting the same debug line information. The current approach also has the
advantaged of being easily reversible if we decide to do so.
  • Loading branch information
felipepiovezan committed Mar 14, 2024
1 parent 265ab8d commit b98b5a3
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 0 deletions.
11 changes: 11 additions & 0 deletions lldb/source/Plugins/Language/Swift/SwiftLanguage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1794,6 +1794,17 @@ SwiftLanguage::GetDemangledFunctionNameWithoutArguments(Mangled mangled) const {
return mangled_name;
}

bool SwiftLanguage::IgnoreForLineBreakpoints(const SymbolContext &sc) const {
// If we don't have a function, conservatively return false.
if (!sc.function)
return false;
StringRef name = sc.function->GetMangled().GetMangledName().GetStringRef();
// In async functions, ignore "await resume" funclets, these only deallocate
// the async context and task_switch back to user code.
return SwiftLanguageRuntime::IsSwiftAsyncAwaitResumePartialFunctionSymbol(
name);
}

//------------------------------------------------------------------
// Static Functions
//------------------------------------------------------------------
Expand Down
2 changes: 2 additions & 0 deletions lldb/source/Plugins/Language/Swift/SwiftLanguage.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ class SwiftLanguage : public Language {

llvm::StringRef GetInstanceVariableName() override { return "self"; }

bool IgnoreForLineBreakpoints(const SymbolContext &sc) const override;

//------------------------------------------------------------------
// PluginInterface protocol
//------------------------------------------------------------------
Expand Down
3 changes: 3 additions & 0 deletions lldb/test/API/lang/swift/async_breakpoints/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
SWIFT_SOURCES := main.swift

include Makefile.rules
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import lldb
from lldbsuite.test.decorators import *
import lldbsuite.test.lldbtest as lldbtest
import lldbsuite.test.lldbutil as lldbutil


class TestSwiftAsyncBreakpoints(lldbtest.TestBase):
@swiftTest
@skipIfWindows
@skipIfLinux
@skipIf(archs=no_match(["arm64", "arm64e", "x86_64"]))
def test(self):
"""Test async breakpoints"""
self.build()
filespec = lldb.SBFileSpec("main.swift")
target, process, thread, breakpoint1 = lldbutil.run_to_source_breakpoint(
self, "Breakpoint1", filespec
)
breakpoint2 = target.BreakpointCreateBySourceRegex("Breakpoint2", filespec)
breakpoint3 = target.BreakpointCreateBySourceRegex("Breakpoint3", filespec)
self.assertEquals(breakpoint1.GetNumLocations(), 2)
self.assertEquals(breakpoint2.GetNumLocations(), 1)
self.assertEquals(breakpoint3.GetNumLocations(), 2)

location11 = breakpoint1.GetLocationAtIndex(0)
location12 = breakpoint1.GetLocationAtIndex(1)
self.assertEquals(location11.GetHitCount(), 1)
self.assertEquals(location12.GetHitCount(), 0)

self.assertEquals(thread.GetStopDescription(128), "breakpoint 1.1")
process.Continue()
self.assertEquals(thread.GetStopDescription(128), "breakpoint 1.2")

thread.StepOver()
self.assertEquals(thread.GetStopDescription(128), "breakpoint 2.1")
self.expect("expr timestamp1", substrs=["42"])

thread.StepOver()
self.assertIn("breakpoint 3.1", thread.GetStopDescription(128))
self.expect("expr timestamp1", substrs=["42"])

process.Continue()
self.assertIn("breakpoint 3.2", thread.GetStopDescription(128))
self.expect("expr timestamp1", substrs=["42"])

thread.StepOver()
self.expect("expr timestamp1", substrs=["42"])
self.expect("expr timestamp2", substrs=["43"])

self.runCmd("settings set language.enable-filter-for-line-breakpoints false")
breakpoint1_no_filter = target.BreakpointCreateBySourceRegex(
"Breakpoint1", filespec
)
self.assertEquals(breakpoint1_no_filter.GetNumLocations(), 3)
15 changes: 15 additions & 0 deletions lldb/test/API/lang/swift/async_breakpoints/main.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
func getTimestamp(i:Int) async -> Int {
return i
}

func work() {}

func foo() async {
work()
let timestamp1 = await getTimestamp(i:42) // Breakpoint1
work() // Breakpoint2
let timestamp2 = await getTimestamp(i:43) // Breakpoint3
work()
}

await foo()

0 comments on commit b98b5a3

Please sign in to comment.