From 92b6c3cc93126db02dee9aff88a8a68f2ae0f476 Mon Sep 17 00:00:00 2001 From: Felipe de Azevedo Piovezan Date: Mon, 18 Nov 2024 13:35:58 -0800 Subject: [PATCH 1/2] [lldb][swift][NFC] Move code task_switch to helper function This will enable us to share code and handle more functions similar to swift_task_switch in a subsequent commit. --- .../Swift/SwiftLanguageRuntimeNames.cpp | 46 ++++++++++++------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntimeNames.cpp b/lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntimeNames.cpp index 6cbf9ee64e924..5fc83389b1b0a 100644 --- a/lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntimeNames.cpp +++ b/lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntimeNames.cpp @@ -361,23 +361,13 @@ class ThreadPlanRunToAddressOnAsyncCtx : public ThreadPlan { /// Given a thread that is stopped at the start of swift_task_switch, create a /// thread plan that runs to the address of the resume function. -static ThreadPlanSP CreateRunThroughTaskSwitchThreadPlan(Thread &thread) { - // The signature for `swift_task_switch` is as follows: - // SWIFT_CC(swiftasync) - // void swift_task_switch( - // SWIFT_ASYNC_CONTEXT AsyncContext *resumeContext, - // TaskContinuationFunction *resumeFunction, - // ExecutorRef newExecutor); - // - // The async context given as the first argument is not passed using the - // calling convention's first register, it's passed in the platform's async - // context register. This means the `resumeFunction` parameter uses the - // first ABI register (ex: x86-64: rdi, arm64: x0). +static ThreadPlanSP +CreateRunThroughTaskSwitchThreadPlan(Thread &thread, + unsigned resume_fn_generic_regnum) { RegisterContextSP reg_ctx = thread.GetStackFrameAtIndex(0)->GetRegisterContext(); - constexpr unsigned resume_fn_regnum = LLDB_REGNUM_GENERIC_ARG1; unsigned resume_fn_reg = reg_ctx->ConvertRegisterKindToRegisterNumber( - RegisterKind::eRegisterKindGeneric, resume_fn_regnum); + RegisterKind::eRegisterKindGeneric, resume_fn_generic_regnum); uint64_t resume_fn_ptr = reg_ctx->ReadRegisterAsUnsigned(resume_fn_reg, 0); if (!resume_fn_ptr) return {}; @@ -397,6 +387,29 @@ static ThreadPlanSP CreateRunThroughTaskSwitchThreadPlan(Thread &thread) { thread, resume_fn_ptr, async_ctx); } +/// Creates a thread plan to step over swift runtime functions that can trigger +/// a task switch, like `async_task_switch` or `swift_asyncLet_get`. +static ThreadPlanSP +CreateRunThroughTaskSwitchingTrampolines(Thread &thread, + StringRef trampoline_name) { + // The signature for `swift_task_switch` is as follows: + // SWIFT_CC(swiftasync) + // void swift_task_switch( + // SWIFT_ASYNC_CONTEXT AsyncContext *resumeContext, + // TaskContinuationFunction *resumeFunction, + // ExecutorRef newExecutor); + // + // The async context given as the first argument is not passed using the + // calling convention's first register, it's passed in the platform's async + // context register. This means the `resumeFunction` parameter uses the + // first ABI register (ex: x86-64: rdi, arm64: x0). + if (trampoline_name == "swift_task_switch") + return CreateRunThroughTaskSwitchThreadPlan(thread, + LLDB_REGNUM_GENERIC_ARG1); + + return nullptr; +} + static lldb::ThreadPlanSP GetStepThroughTrampolinePlan(Thread &thread, bool stop_others) { // Here are the trampolines we have at present. @@ -429,8 +442,9 @@ static lldb::ThreadPlanSP GetStepThroughTrampolinePlan(Thread &thread, Mangled &mangled_symbol_name = symbol->GetMangled(); const char *symbol_name = mangled_symbol_name.GetMangledName().AsCString(); - if (mangled_symbol_name.GetDemangledName() == "swift_task_switch") - return CreateRunThroughTaskSwitchThreadPlan(thread); + if (ThreadPlanSP thread_plan = CreateRunThroughTaskSwitchingTrampolines( + thread, mangled_symbol_name.GetDemangledName())) + return thread_plan; ThunkKind thunk_kind = GetThunkKind(symbol); ThunkAction thunk_action = GetThunkAction(thunk_kind); From 405a7914f1f3c7c9bb56f2afc196cf14b46b297b Mon Sep 17 00:00:00 2001 From: Felipe de Azevedo Piovezan Date: Mon, 18 Nov 2024 16:26:20 -0800 Subject: [PATCH 2/2] [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. --- .../Swift/SwiftLanguageRuntimeNames.cpp | 14 +++++++- .../stepping/step_over_asynclet/Makefile | 3 ++ .../TestSwiftAsyncStepOverAsyncLet.py | 36 +++++++++++++++++++ .../stepping/step_over_asynclet/main.swift | 27 ++++++++++++++ 4 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 lldb/test/API/lang/swift/async/stepping/step_over_asynclet/Makefile create mode 100644 lldb/test/API/lang/swift/async/stepping/step_over_asynclet/TestSwiftAsyncStepOverAsyncLet.py create mode 100644 lldb/test/API/lang/swift/async/stepping/step_over_asynclet/main.swift diff --git a/lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntimeNames.cpp b/lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntimeNames.cpp index 5fc83389b1b0a..5c9d676e890e4 100644 --- a/lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntimeNames.cpp +++ b/lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntimeNames.cpp @@ -406,7 +406,19 @@ CreateRunThroughTaskSwitchingTrampolines(Thread &thread, if (trampoline_name == "swift_task_switch") return CreateRunThroughTaskSwitchThreadPlan(thread, LLDB_REGNUM_GENERIC_ARG1); - + // The signature for `swift_asyncLet_get` and `swift_asyncLet_finish` are the + // same. Like `task_switch`, the async context (first argument) uses the async + // context register, and not the arg1 register; as such, the continuation + // funclet can be found in arg3. + // + // swift_asyncLet_get(SWIFT_ASYNC_CONTEXT AsyncContext *, + // AsyncLet *, + // void *, + // TaskContinuationFunction *, + if (trampoline_name == "swift_asyncLet_get" || + trampoline_name == "swift_asyncLet_finish") + return CreateRunThroughTaskSwitchThreadPlan(thread, + LLDB_REGNUM_GENERIC_ARG3); return nullptr; } diff --git a/lldb/test/API/lang/swift/async/stepping/step_over_asynclet/Makefile b/lldb/test/API/lang/swift/async/stepping/step_over_asynclet/Makefile new file mode 100644 index 0000000000000..cca30b939e652 --- /dev/null +++ b/lldb/test/API/lang/swift/async/stepping/step_over_asynclet/Makefile @@ -0,0 +1,3 @@ +SWIFT_SOURCES := main.swift +SWIFTFLAGS_EXTRAS := -parse-as-library +include Makefile.rules diff --git a/lldb/test/API/lang/swift/async/stepping/step_over_asynclet/TestSwiftAsyncStepOverAsyncLet.py b/lldb/test/API/lang/swift/async/stepping/step_over_asynclet/TestSwiftAsyncStepOverAsyncLet.py new file mode 100644 index 0000000000000..b7c11e200ec87 --- /dev/null +++ b/lldb/test/API/lang/swift/async/stepping/step_over_asynclet/TestSwiftAsyncStepOverAsyncLet.py @@ -0,0 +1,36 @@ +import lldb +from lldbsuite.test.decorators import * +import lldbsuite.test.lldbtest as lldbtest +import lldbsuite.test.lldbutil as lldbutil + + +@skipIfAsan # rdar://138777205 +class TestCase(lldbtest.TestBase): + + def check_is_in_line(self, thread, linenum): + frame = thread.frames[0] + line_entry = frame.GetLineEntry() + self.assertEqual(linenum, line_entry.GetLine()) + + @swiftTest + @skipIf(oslist=["windows", "linux"]) + def test(self): + """Test conditions for async step-over.""" + self.build() + + source_file = lldb.SBFileSpec("main.swift") + target, process, thread, bkpt = lldbutil.run_to_source_breakpoint( + self, "BREAK HERE", source_file + ) + + # Step over should reach every line in the interval [10, 20] + expected_line_nums = [10, 11, 12, 13, 14, 15] + # FIXME: for some reason we loop back to the start of the do block after the last statement. + # rdar://140159600 + expected_line_nums += [8] + expected_line_nums += [17, 18, 19, 20] + for expected_line_num in expected_line_nums: + thread.StepOver() + stop_reason = thread.GetStopReason() + self.assertStopReason(stop_reason, lldb.eStopReasonPlanComplete) + self.check_is_in_line(thread, expected_line_num) diff --git a/lldb/test/API/lang/swift/async/stepping/step_over_asynclet/main.swift b/lldb/test/API/lang/swift/async/stepping/step_over_asynclet/main.swift new file mode 100644 index 0000000000000..6949c40bb2aa5 --- /dev/null +++ b/lldb/test/API/lang/swift/async/stepping/step_over_asynclet/main.swift @@ -0,0 +1,27 @@ +func getTimestamp(x: Int) async -> Int { + return 40 + x +} + +func work() {} + +func foo() async { + do { + work() // BREAK HERE + async let timestamp1 = getTimestamp(x:1) + work() + async let timestamp2 = getTimestamp(x:2) + work() + let timestamps = await [timestamp1, timestamp2] + print(timestamps) + } + async let timestamp3 = getTimestamp(x:3) + work() + let actual_timestamp3 = await timestamp3 + print(actual_timestamp3) +} + +@main enum entry { + static func main() async { + await foo() + } +}