Skip to content

Commit bfe3e67

Browse files
Merge pull request #9628 from felipepiovezan/felipe/fix_async_let_step_over
[lldb][swift] Add support for async_let step-over
2 parents 7cc116c + 405a791 commit bfe3e67

File tree

4 files changed

+108
-16
lines changed

4 files changed

+108
-16
lines changed

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

Lines changed: 42 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -361,23 +361,13 @@ class ThreadPlanRunToAddressOnAsyncCtx : public ThreadPlan {
361361

362362
/// Given a thread that is stopped at the start of swift_task_switch, create a
363363
/// thread plan that runs to the address of the resume function.
364-
static ThreadPlanSP CreateRunThroughTaskSwitchThreadPlan(Thread &thread) {
365-
// The signature for `swift_task_switch` is as follows:
366-
// SWIFT_CC(swiftasync)
367-
// void swift_task_switch(
368-
// SWIFT_ASYNC_CONTEXT AsyncContext *resumeContext,
369-
// TaskContinuationFunction *resumeFunction,
370-
// ExecutorRef newExecutor);
371-
//
372-
// The async context given as the first argument is not passed using the
373-
// calling convention's first register, it's passed in the platform's async
374-
// context register. This means the `resumeFunction` parameter uses the
375-
// first ABI register (ex: x86-64: rdi, arm64: x0).
364+
static ThreadPlanSP
365+
CreateRunThroughTaskSwitchThreadPlan(Thread &thread,
366+
unsigned resume_fn_generic_regnum) {
376367
RegisterContextSP reg_ctx =
377368
thread.GetStackFrameAtIndex(0)->GetRegisterContext();
378-
constexpr unsigned resume_fn_regnum = LLDB_REGNUM_GENERIC_ARG1;
379369
unsigned resume_fn_reg = reg_ctx->ConvertRegisterKindToRegisterNumber(
380-
RegisterKind::eRegisterKindGeneric, resume_fn_regnum);
370+
RegisterKind::eRegisterKindGeneric, resume_fn_generic_regnum);
381371
uint64_t resume_fn_ptr = reg_ctx->ReadRegisterAsUnsigned(resume_fn_reg, 0);
382372
if (!resume_fn_ptr)
383373
return {};
@@ -397,6 +387,41 @@ static ThreadPlanSP CreateRunThroughTaskSwitchThreadPlan(Thread &thread) {
397387
thread, resume_fn_ptr, async_ctx);
398388
}
399389

390+
/// Creates a thread plan to step over swift runtime functions that can trigger
391+
/// a task switch, like `async_task_switch` or `swift_asyncLet_get`.
392+
static ThreadPlanSP
393+
CreateRunThroughTaskSwitchingTrampolines(Thread &thread,
394+
StringRef trampoline_name) {
395+
// The signature for `swift_task_switch` is as follows:
396+
// SWIFT_CC(swiftasync)
397+
// void swift_task_switch(
398+
// SWIFT_ASYNC_CONTEXT AsyncContext *resumeContext,
399+
// TaskContinuationFunction *resumeFunction,
400+
// ExecutorRef newExecutor);
401+
//
402+
// The async context given as the first argument is not passed using the
403+
// calling convention's first register, it's passed in the platform's async
404+
// context register. This means the `resumeFunction` parameter uses the
405+
// first ABI register (ex: x86-64: rdi, arm64: x0).
406+
if (trampoline_name == "swift_task_switch")
407+
return CreateRunThroughTaskSwitchThreadPlan(thread,
408+
LLDB_REGNUM_GENERIC_ARG1);
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);
422+
return nullptr;
423+
}
424+
400425
static lldb::ThreadPlanSP GetStepThroughTrampolinePlan(Thread &thread,
401426
bool stop_others) {
402427
// Here are the trampolines we have at present.
@@ -429,8 +454,9 @@ static lldb::ThreadPlanSP GetStepThroughTrampolinePlan(Thread &thread,
429454
Mangled &mangled_symbol_name = symbol->GetMangled();
430455
const char *symbol_name = mangled_symbol_name.GetMangledName().AsCString();
431456

432-
if (mangled_symbol_name.GetDemangledName() == "swift_task_switch")
433-
return CreateRunThroughTaskSwitchThreadPlan(thread);
457+
if (ThreadPlanSP thread_plan = CreateRunThroughTaskSwitchingTrampolines(
458+
thread, mangled_symbol_name.GetDemangledName()))
459+
return thread_plan;
434460

435461
ThunkKind thunk_kind = GetThunkKind(symbol);
436462
ThunkAction thunk_action = GetThunkAction(thunk_kind);
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)