Skip to content

Commit 405a791

Browse files
[lldb][swift] Add support for stepping async let statements
The use of `async let` in swift introduces two new trampolines that may trigger a task switch: 1. `swift_asyncLet_get`, which gets called upon `await`ing an `async let` variable. 2. `swift_asyncLet_finish`, which gets called when the `async let` variable goes out of scope. We need step through plans for both of those.
1 parent 92b6c3c commit 405a791

File tree

4 files changed

+79
-1
lines changed

4 files changed

+79
-1
lines changed

lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntimeNames.cpp

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,19 @@ CreateRunThroughTaskSwitchingTrampolines(Thread &thread,
406406
if (trampoline_name == "swift_task_switch")
407407
return CreateRunThroughTaskSwitchThreadPlan(thread,
408408
LLDB_REGNUM_GENERIC_ARG1);
409-
409+
// The signature for `swift_asyncLet_get` and `swift_asyncLet_finish` are the
410+
// same. Like `task_switch`, the async context (first argument) uses the async
411+
// context register, and not the arg1 register; as such, the continuation
412+
// funclet can be found in arg3.
413+
//
414+
// swift_asyncLet_get(SWIFT_ASYNC_CONTEXT AsyncContext *,
415+
// AsyncLet *,
416+
// void *,
417+
// TaskContinuationFunction *,
418+
if (trampoline_name == "swift_asyncLet_get" ||
419+
trampoline_name == "swift_asyncLet_finish")
420+
return CreateRunThroughTaskSwitchThreadPlan(thread,
421+
LLDB_REGNUM_GENERIC_ARG3);
410422
return nullptr;
411423
}
412424

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
SWIFT_SOURCES := main.swift
2+
SWIFTFLAGS_EXTRAS := -parse-as-library
3+
include Makefile.rules
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import lldb
2+
from lldbsuite.test.decorators import *
3+
import lldbsuite.test.lldbtest as lldbtest
4+
import lldbsuite.test.lldbutil as lldbutil
5+
6+
7+
@skipIfAsan # rdar://138777205
8+
class TestCase(lldbtest.TestBase):
9+
10+
def check_is_in_line(self, thread, linenum):
11+
frame = thread.frames[0]
12+
line_entry = frame.GetLineEntry()
13+
self.assertEqual(linenum, line_entry.GetLine())
14+
15+
@swiftTest
16+
@skipIf(oslist=["windows", "linux"])
17+
def test(self):
18+
"""Test conditions for async step-over."""
19+
self.build()
20+
21+
source_file = lldb.SBFileSpec("main.swift")
22+
target, process, thread, bkpt = lldbutil.run_to_source_breakpoint(
23+
self, "BREAK HERE", source_file
24+
)
25+
26+
# Step over should reach every line in the interval [10, 20]
27+
expected_line_nums = [10, 11, 12, 13, 14, 15]
28+
# FIXME: for some reason we loop back to the start of the do block after the last statement.
29+
# rdar://140159600
30+
expected_line_nums += [8]
31+
expected_line_nums += [17, 18, 19, 20]
32+
for expected_line_num in expected_line_nums:
33+
thread.StepOver()
34+
stop_reason = thread.GetStopReason()
35+
self.assertStopReason(stop_reason, lldb.eStopReasonPlanComplete)
36+
self.check_is_in_line(thread, expected_line_num)
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
func getTimestamp(x: Int) async -> Int {
2+
return 40 + x
3+
}
4+
5+
func work() {}
6+
7+
func foo() async {
8+
do {
9+
work() // BREAK HERE
10+
async let timestamp1 = getTimestamp(x:1)
11+
work()
12+
async let timestamp2 = getTimestamp(x:2)
13+
work()
14+
let timestamps = await [timestamp1, timestamp2]
15+
print(timestamps)
16+
}
17+
async let timestamp3 = getTimestamp(x:3)
18+
work()
19+
let actual_timestamp3 = await timestamp3
20+
print(actual_timestamp3)
21+
}
22+
23+
@main enum entry {
24+
static func main() async {
25+
await foo()
26+
}
27+
}

0 commit comments

Comments
 (0)