From 457397ab41925381848e1aadd4a088f46a57699e Mon Sep 17 00:00:00 2001 From: Felipe de Azevedo Piovezan Date: Thu, 23 Jan 2025 13:30:39 -0800 Subject: [PATCH 1/3] [lldb][swift] Add helper function to find Swift concurrency's module version --- .../Swift/SwiftLanguageRuntime.cpp | 69 +++++++++++++++++++ .../Swift/SwiftLanguageRuntime.h | 12 ++++ 2 files changed, 81 insertions(+) diff --git a/lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntime.cpp b/lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntime.cpp index acca5f8c4bf5195..c9ef965e03586ab 100644 --- a/lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntime.cpp +++ b/lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntime.cpp @@ -91,6 +91,10 @@ const char *SwiftLanguageRuntime::GetStandardLibraryBaseName() { return "swiftCore"; } +const char *SwiftLanguageRuntime::GetConcurrencyLibraryBaseName() { + return "swift_Concurrency"; +} + static ConstString GetStandardLibraryName(Process &process) { // This result needs to be stored in the constructor. PlatformSP platform_sp(process.GetTarget().GetPlatform()); @@ -100,6 +104,14 @@ static ConstString GetStandardLibraryName(Process &process) { return {}; } +static ConstString GetConcurrencyLibraryName(Process &process) { + PlatformSP platform_sp = process.GetTarget().GetPlatform(); + if (platform_sp) + return platform_sp->GetFullNameForDylib( + ConstString(SwiftLanguageRuntime::GetConcurrencyLibraryBaseName())); + return {}; +} + ConstString SwiftLanguageRuntime::GetStandardLibraryName() { return ::GetStandardLibraryName(*m_process); } @@ -109,6 +121,12 @@ static bool IsModuleSwiftRuntime(lldb_private::Process &process, return module.GetFileSpec().GetFilename() == GetStandardLibraryName(process); } +static bool IsModuleSwiftConcurrency(lldb_private::Process &process, + lldb_private::Module &module) { + return module.GetFileSpec().GetFilename() == + GetConcurrencyLibraryName(process); +} + AppleObjCRuntimeV2 * SwiftLanguageRuntime::GetObjCRuntime(lldb_private::Process &process) { if (auto objc_runtime = ObjCLanguageRuntime::Get(process)) { @@ -131,6 +149,11 @@ static bool IsStaticSwiftRuntime(Module &image) { return image.FindFirstSymbolWithNameAndType(swift_reflection_version_sym); } +static bool IsStaticSwiftConcurrency(Module &image) { + static const ConstString task_switch_symbol("_swift_task_switch"); + return image.FindFirstSymbolWithNameAndType(task_switch_symbol); +} + /// \return the Swift or Objective-C runtime found in the loaded images. static ModuleSP findRuntime(Process &process, RuntimeKind runtime_kind) { AppleObjCRuntimeV2 *objc_runtime = nullptr; @@ -168,6 +191,52 @@ static ModuleSP findRuntime(Process &process, RuntimeKind runtime_kind) { return runtime_image; } +ModuleSP SwiftLanguageRuntime::FindConcurrencyModule(Process &process) { + ModuleSP concurrency_module; + process.GetTarget().GetImages().ForEach([&](const ModuleSP &candidate) { + if (candidate && IsModuleSwiftConcurrency(process, *candidate)) { + concurrency_module = candidate; + return false; + } + return true; + }); + if (concurrency_module) + return concurrency_module; + + // Do a more expensive search for a statically linked Swift runtime. + process.GetTarget().GetImages().ForEach([&](const ModuleSP &candidate) { + if (candidate && IsStaticSwiftConcurrency(*candidate)) { + concurrency_module = candidate; + return false; + } + return true; + }); + return concurrency_module; +} + +std::optional +SwiftLanguageRuntime::FindConcurrencyDebugVersion(Process &process) { + ModuleSP concurrency_module = FindConcurrencyModule(process); + if (!concurrency_module) + return {}; + + const Symbol *version_symbol = + concurrency_module->FindFirstSymbolWithNameAndType( + ConstString("_swift_concurrency_debug_internal_layout_version")); + if (!version_symbol) + return 0; + + addr_t symbol_addr = version_symbol->GetLoadAddress(&process.GetTarget()); + if (symbol_addr == LLDB_INVALID_ADDRESS) + return {}; + Status error; + uint64_t version = process.ReadUnsignedIntegerFromMemory( + symbol_addr, /*width*/ 4, /*fail_value=*/0, error); + if (error.Fail()) + return {}; + return version; +} + static std::optional FindSymbolForSwiftObject(Process &process, RuntimeKind runtime_kind, StringRef object, const SymbolType sym_type) { diff --git a/lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntime.h b/lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntime.h index 3cde81a97f1c656..a1b3e9faf1138f5 100644 --- a/lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntime.h +++ b/lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntime.h @@ -98,6 +98,16 @@ class SwiftLanguageRuntime : public LanguageRuntime { static SwiftLanguageRuntime *Get(lldb::ProcessSP process_sp) { return SwiftLanguageRuntime::Get(process_sp.get()); } + + /// Returns the Module containing the Swift Concurrency runtime, if it exists. + static lldb::ModuleSP FindConcurrencyModule(Process &process); + + /// Returns the version of the swift concurrency runtime debug layout. + /// If no Concurrency module is found, or if errors occur, nullopt is + /// returned. + /// Returns 0 for versions of the module prior to the introduction + /// of versioning. + static std::optional FindConcurrencyDebugVersion(Process &process); /// \} /// PluginInterface protocol. @@ -481,6 +491,8 @@ class SwiftLanguageRuntime : public LanguageRuntime { static const char *GetErrorBackstopName(); ConstString GetStandardLibraryName(); static const char *GetStandardLibraryBaseName(); + static const char *GetConcurrencyLibraryBaseName(); + static bool IsSwiftClassName(const char *name); /// Determines wether \c variable is the "self" object. static bool IsSelf(Variable &variable); From ce95a9ae30c2a2aad7dda44643ed2e35fe05b05d Mon Sep 17 00:00:00 2001 From: Felipe de Azevedo Piovezan Date: Mon, 3 Feb 2025 11:32:39 -0800 Subject: [PATCH 2/3] [lldb] Add experimental target option controlling swift OS plugin This is here mostly out of an abundance of caution, the default is to use the plugins. --- lldb/include/lldb/Target/Target.h | 2 ++ lldb/source/Target/Target.cpp | 11 +++++++++++ lldb/source/Target/TargetProperties.td | 3 +++ 3 files changed, 16 insertions(+) diff --git a/lldb/include/lldb/Target/Target.h b/lldb/include/lldb/Target/Target.h index be61833d7a5d38f..03ce1bf9cd8fba4 100644 --- a/lldb/include/lldb/Target/Target.h +++ b/lldb/include/lldb/Target/Target.h @@ -199,6 +199,8 @@ class TargetProperties : public Properties { AutoBool GetSwiftPCMValidation() const; + bool GetSwiftUseTasksPlugin() const; + Args GetSwiftPluginServerForPath() const; bool GetSwiftAutoImportFrameworks() const; diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp index ff551291e4fdb2e..a596e1176ce7f44 100644 --- a/lldb/source/Target/Target.cpp +++ b/lldb/source/Target/Target.cpp @@ -4496,6 +4496,17 @@ AutoBool TargetProperties::GetSwiftPCMValidation() const { return AutoBool::Auto; } +bool TargetProperties::GetSwiftUseTasksPlugin() const { + const Property *exp_property = + m_collection_sp->GetPropertyAtIndex(ePropertyExperimental); + OptionValueProperties *exp_values = + exp_property->GetValue()->GetAsProperties(); + if (exp_values) + return exp_values->GetPropertyAtIndexAs(ePropertySwiftUseTasksPlugin) + .value_or(true); + return true; +} + Args TargetProperties::GetSwiftPluginServerForPath() const { const uint32_t idx = ePropertySwiftPluginServerForPath; diff --git a/lldb/source/Target/TargetProperties.td b/lldb/source/Target/TargetProperties.td index f42a1a0e133ac0d..d4ad0996745f380 100644 --- a/lldb/source/Target/TargetProperties.td +++ b/lldb/source/Target/TargetProperties.td @@ -31,6 +31,9 @@ let Definition = "target_experimental" in { DefaultEnumValue<"llvm::to_underlying(AutoBool::Auto)">, EnumValues<"OptionEnumValues(g_swift_pcm_validation_values)">, Desc<"Enable validation when loading Clang PCM files (-fvalidate-pch, -fmodules-check-relocated).">; + def SwiftUseTasksPlugin: Property<"swift-tasks-plugin-enabled", "Boolean">, + DefaultTrue, + Desc<"Enables the swift plugin converting tasks into threads">; } let Definition = "target" in { From 33da3aaef33a363463fb80ba06c4f4dcfb5263b4 Mon Sep 17 00:00:00 2001 From: Felipe de Azevedo Piovezan Date: Mon, 13 Jan 2025 10:01:43 -0800 Subject: [PATCH 3/3] [lldb][swift] Swift OS Plugin This commit introduces an OS plugin converting Process threads into OperatingSystem threads that represent a Task. This is done by inspecting ThreadLocalStorage for a specific location where the swift runtime writes information about the current Task. With this plugin, ThreadPlans now work properly in async code: a step action on a Thread is now effectively an action on a Task, and LLDB will correctly track that. ThreadPlans are effectively "Task Plans". --- .../Swift/SwiftLanguageRuntime.cpp | 6 +- .../Swift/SwiftLanguageRuntimeNames.cpp | 73 +------- .../Plugins/OperatingSystem/CMakeLists.txt | 1 + .../OperatingSystem/SwiftTasks/CMakeLists.txt | 12 ++ .../SwiftTasks/OperatingSystemSwiftTasks.cpp | 166 ++++++++++++++++++ .../SwiftTasks/OperatingSystemSwiftTasks.h | 64 +++++++ .../stepping/step_over_lots_of_tasks/Makefile | 3 + .../TestSwiftAsyncSteppingManyTasks.py | 64 +++++++ .../step_over_lots_of_tasks/main.swift | 25 +++ 9 files changed, 342 insertions(+), 72 deletions(-) create mode 100644 lldb/source/Plugins/OperatingSystem/SwiftTasks/CMakeLists.txt create mode 100644 lldb/source/Plugins/OperatingSystem/SwiftTasks/OperatingSystemSwiftTasks.cpp create mode 100644 lldb/source/Plugins/OperatingSystem/SwiftTasks/OperatingSystemSwiftTasks.h create mode 100644 lldb/test/API/lang/swift/async/stepping/step_over_lots_of_tasks/Makefile create mode 100644 lldb/test/API/lang/swift/async/stepping/step_over_lots_of_tasks/TestSwiftAsyncSteppingManyTasks.py create mode 100644 lldb/test/API/lang/swift/async/stepping/step_over_lots_of_tasks/main.swift diff --git a/lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntime.cpp b/lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntime.cpp index c9ef965e03586ab..7976d7e4acb740b 100644 --- a/lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntime.cpp +++ b/lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntime.cpp @@ -2795,7 +2795,11 @@ llvm::Expected GetTaskAddrFromThreadLocalStorage(Thread &thread) { #else // Compute the thread local storage address for this thread. addr_t tsd_addr = LLDB_INVALID_ADDRESS; - if (auto info_sp = thread.GetExtendedInfo()) + + // Look through backing threads when inspecting TLS. + Thread &real_thread = + thread.GetBackingThread() ? *thread.GetBackingThread() : thread; + if (auto info_sp = real_thread.GetExtendedInfo()) if (auto *info_dict = info_sp->GetAsDictionary()) info_dict->GetValueForKeyAsInteger("tsd_address", tsd_addr); diff --git a/lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntimeNames.cpp b/lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntimeNames.cpp index 442500c55a55bbe..be01fba03cfcfbf 100644 --- a/lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntimeNames.cpp +++ b/lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntimeNames.cpp @@ -290,75 +290,6 @@ static ThunkAction GetThunkAction(ThunkKind kind) { } } -/// A thread plan to run to a specific address on a specific async context. -class ThreadPlanRunToAddressOnAsyncCtx : public ThreadPlan { -public: - /// Creates a thread plan to run to destination_addr of an async function - /// whose context is async_ctx. - ThreadPlanRunToAddressOnAsyncCtx(Thread &thread, addr_t destination_addr, - addr_t async_ctx) - : ThreadPlan(eKindGeneric, "run-to-funclet", thread, eVoteNoOpinion, - eVoteNoOpinion), - m_destination_addr(destination_addr), m_expected_async_ctx(async_ctx) { - auto &target = thread.GetProcess()->GetTarget(); - m_funclet_bp = target.CreateBreakpoint(destination_addr, true, false); - m_funclet_bp->SetBreakpointKind("async-run-to-funclet"); - } - - bool ValidatePlan(Stream *error) override { - if (m_funclet_bp->HasResolvedLocations()) - return true; - - // If we failed to resolve any locations, this plan is invalid. - m_funclet_bp->GetTarget().RemoveBreakpointByID(m_funclet_bp->GetID()); - return false; - } - - void GetDescription(Stream *s, lldb::DescriptionLevel level) override { - s->PutCString("ThreadPlanRunToAddressOnAsyncCtx to address = "); - s->PutHex64(m_destination_addr); - s->PutCString(" with async ctx = "); - s->PutHex64(m_expected_async_ctx); - } - - /// This plan explains the stop if the current async context is the async - /// context this plan was created with. - bool DoPlanExplainsStop(Event *event) override { - if (!HasTID()) - return false; - return GetCurrentAsyncContext() == m_expected_async_ctx; - } - - /// If this plan explained the stop, it always stops: its sole purpose is to - /// run to the breakpoint it set on the right async function invocation. - bool ShouldStop(Event *event) override { - SetPlanComplete(); - return true; - } - - /// If this plan said ShouldStop, then its job is complete. - bool MischiefManaged() override { - return IsPlanComplete(); - } - - bool WillStop() override { return false; } - lldb::StateType GetPlanRunState() override { return eStateRunning; } - bool StopOthers() override { return false; } - void DidPop() override { - m_funclet_bp->GetTarget().RemoveBreakpointByID(m_funclet_bp->GetID()); - } - -private: - addr_t GetCurrentAsyncContext() { - auto frame_sp = GetThread().GetStackFrameAtIndex(0); - return frame_sp->GetStackID().GetCallFrameAddress(); - } - - addr_t m_destination_addr; - addr_t m_expected_async_ctx; - BreakpointSP m_funclet_bp; -}; - /// 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 @@ -383,8 +314,8 @@ CreateRunThroughTaskSwitchThreadPlan(Thread &thread, if (!async_ctx) return {}; - return std::make_shared( - thread, resume_fn_ptr, async_ctx); + return std::make_shared(thread, resume_fn_ptr, + /*stop_others*/ false); } /// Creates a thread plan to step over swift runtime functions that can trigger diff --git a/lldb/source/Plugins/OperatingSystem/CMakeLists.txt b/lldb/source/Plugins/OperatingSystem/CMakeLists.txt index 06d909b862a0480..faa8d1435746479 100644 --- a/lldb/source/Plugins/OperatingSystem/CMakeLists.txt +++ b/lldb/source/Plugins/OperatingSystem/CMakeLists.txt @@ -1,3 +1,4 @@ if (LLDB_ENABLE_PYTHON) add_subdirectory(Python) + add_subdirectory(SwiftTasks) endif() diff --git a/lldb/source/Plugins/OperatingSystem/SwiftTasks/CMakeLists.txt b/lldb/source/Plugins/OperatingSystem/SwiftTasks/CMakeLists.txt new file mode 100644 index 000000000000000..31c81c5f4643216 --- /dev/null +++ b/lldb/source/Plugins/OperatingSystem/SwiftTasks/CMakeLists.txt @@ -0,0 +1,12 @@ +add_lldb_library(lldbPluginOperatingSystemSwiftTasks PLUGIN + OperatingSystemSwiftTasks.cpp + + LINK_LIBS + lldbCore + lldbInterpreter + lldbSymbol + lldbTarget + lldbValueObject + lldbPluginProcessUtility + lldbPluginSwiftLanguageRuntime + ) diff --git a/lldb/source/Plugins/OperatingSystem/SwiftTasks/OperatingSystemSwiftTasks.cpp b/lldb/source/Plugins/OperatingSystem/SwiftTasks/OperatingSystemSwiftTasks.cpp new file mode 100644 index 000000000000000..95435e75d633ab0 --- /dev/null +++ b/lldb/source/Plugins/OperatingSystem/SwiftTasks/OperatingSystemSwiftTasks.cpp @@ -0,0 +1,166 @@ +//===-- OperatingSystemSwiftTasks.cpp -------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#if LLDB_ENABLE_SWIFT + +#include "OperatingSystemSwiftTasks.h" +#include "Plugins/LanguageRuntime/Swift/SwiftLanguageRuntime.h" +#include "Plugins/Process/Utility/ThreadMemory.h" +#include "lldb/Core/Debugger.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/PluginManager.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/Thread.h" +#include "lldb/Target/ThreadList.h" +#include "lldb/Utility/LLDBLog.h" + +#include + +using namespace lldb; +using namespace lldb_private; + +LLDB_PLUGIN_DEFINE(OperatingSystemSwiftTasks) + +void OperatingSystemSwiftTasks::Initialize() { + PluginManager::RegisterPlugin(GetPluginNameStatic(), + GetPluginDescriptionStatic(), CreateInstance, + nullptr); +} + +void OperatingSystemSwiftTasks::Terminate() { + PluginManager::UnregisterPlugin(CreateInstance); +} + +OperatingSystem *OperatingSystemSwiftTasks::CreateInstance(Process *process, + bool force) { + if (!process || !process->GetTarget().GetSwiftUseTasksPlugin()) + return nullptr; + + Log *log = GetLog(LLDBLog::OS); + std::optional concurrency_version = + SwiftLanguageRuntime::FindConcurrencyDebugVersion(*process); + if (!concurrency_version) { + LLDB_LOG(log, + "OperatingSystemSwiftTasks: did not find concurrency module."); + return nullptr; + } + + LLDB_LOGF(log, + "OperatingSystemSwiftTasks: got a concurrency version symbol of %u", + *concurrency_version); + if (*concurrency_version > 1) { + auto warning = + llvm::formatv("Unexpected Swift concurrency version {0}. Stepping on " + "concurrent code may behave incorrectly.", + *concurrency_version); + lldb::user_id_t debugger_id = process->GetTarget().GetDebugger().GetID(); + static std::once_flag concurrency_warning_flag; + Debugger::ReportWarning(warning, debugger_id, &concurrency_warning_flag); + return nullptr; + } + return new OperatingSystemSwiftTasks(*process); +} + +llvm::StringRef OperatingSystemSwiftTasks::GetPluginDescriptionStatic() { + return "Operating system plug-in converting Swift Tasks into Threads."; +} + +OperatingSystemSwiftTasks::~OperatingSystemSwiftTasks() = default; + +OperatingSystemSwiftTasks::OperatingSystemSwiftTasks( + lldb_private::Process &process) + : OperatingSystem(&process) { + size_t ptr_size = process.GetAddressByteSize(); + // Offset of a Task ID inside a Task data structure, guaranteed by the ABI. + // See Job in swift/RemoteInspection/RuntimeInternals.h. + m_job_id_offset = 4 * ptr_size + 4; +} + +ThreadSP +OperatingSystemSwiftTasks::FindOrCreateSwiftThread(ThreadList &old_thread_list, + uint64_t task_id) { + // Mask higher bits to avoid conflicts with core thread IDs. + uint64_t masked_task_id = 0x0000000f00000000 | task_id; + + // If we already had a thread for this Task in the last stop, re-use it. + if (ThreadSP old_thread = old_thread_list.FindThreadByID(masked_task_id); + IsOperatingSystemPluginThread(old_thread)) + return old_thread; + + std::string name = llvm::formatv("Swift Task {0:x}", task_id); + llvm::StringRef queue_name = ""; + return std::make_shared(*m_process, masked_task_id, name, + queue_name, + /*register_data_addr*/ 0); +} + +bool OperatingSystemSwiftTasks::UpdateThreadList(ThreadList &old_thread_list, + ThreadList &core_thread_list, + ThreadList &new_thread_list) { + Log *log = GetLog(LLDBLog::OS); + LLDB_LOG(log, "OperatingSystemSwiftTasks: Updating thread list"); + + for (const ThreadSP &real_thread : core_thread_list.Threads()) { + std::optional task_id = FindTaskId(*real_thread); + + // If this is not a thread running a Task, add it to the list as is. + if (!task_id) { + new_thread_list.AddThread(real_thread); + LLDB_LOGF(log, + "OperatingSystemSwiftTasks: thread %" PRIx64 + " is not executing a Task", + real_thread->GetID()); + continue; + } + + ThreadSP swift_thread = FindOrCreateSwiftThread(old_thread_list, *task_id); + swift_thread->SetBackingThread(real_thread); + new_thread_list.AddThread(swift_thread); + LLDB_LOGF(log, + "OperatingSystemSwiftTasks: mapping thread IDs: %" PRIx64 + " -> %" PRIx64, + real_thread->GetID(), swift_thread->GetID()); + } + return true; +} + +void OperatingSystemSwiftTasks::ThreadWasSelected(Thread *thread) {} + +RegisterContextSP OperatingSystemSwiftTasks::CreateRegisterContextForThread( + Thread *thread, addr_t reg_data_addr) { + if (!thread || !IsOperatingSystemPluginThread(thread->shared_from_this())) + return nullptr; + return thread->GetRegisterContext(); +} + +StopInfoSP OperatingSystemSwiftTasks::CreateThreadStopReason( + lldb_private::Thread *thread) { + return thread->GetStopInfo(); +} + +std::optional OperatingSystemSwiftTasks::FindTaskId(Thread &thread) { + llvm::Expected task_addr = GetTaskAddrFromThreadLocalStorage(thread); + if (!task_addr) { + LLDB_LOG_ERROR(GetLog(LLDBLog::OS), task_addr.takeError(), + "OperatingSystemSwiftTasks: failed to find task address in " + "thread local storage: {0}"); + return {}; + } + + Status error; + // The Task ID is at offset m_job_id_offset from the Task pointer. + constexpr uint32_t num_bytes_task_id = 4; + auto task_id = m_process->ReadUnsignedIntegerFromMemory( + *task_addr + m_job_id_offset, num_bytes_task_id, LLDB_INVALID_ADDRESS, + error); + if (error.Fail()) + return {}; + return task_id; +} + +#endif // #if LLDB_ENABLE_SWIFT diff --git a/lldb/source/Plugins/OperatingSystem/SwiftTasks/OperatingSystemSwiftTasks.h b/lldb/source/Plugins/OperatingSystem/SwiftTasks/OperatingSystemSwiftTasks.h new file mode 100644 index 000000000000000..fe144fc338da220 --- /dev/null +++ b/lldb/source/Plugins/OperatingSystem/SwiftTasks/OperatingSystemSwiftTasks.h @@ -0,0 +1,64 @@ +//===-- OperatingSystemSwiftTasks.h -----------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef liblldb_OperatingSystemSwiftTasks_h_ +#define liblldb_OperatingSystemSwiftTasks_h_ + +#if LLDB_ENABLE_SWIFT + +#include "lldb/Target/OperatingSystem.h" + +namespace lldb_private { +class OperatingSystemSwiftTasks : public OperatingSystem { +public: + OperatingSystemSwiftTasks(Process &process); + ~OperatingSystemSwiftTasks() override; + + static OperatingSystem *CreateInstance(Process *process, bool force); + static void Initialize(); + static void Terminate(); + static llvm::StringRef GetPluginNameStatic() { return "swift"; } + static llvm::StringRef GetPluginDescriptionStatic(); + + /// PluginInterface Methods + + llvm::StringRef GetPluginName() override { return GetPluginNameStatic(); } + + /// OperatingSystem Methods + + bool UpdateThreadList(ThreadList &old_thread_list, + ThreadList &real_thread_list, + ThreadList &new_thread_list) override; + + void ThreadWasSelected(Thread *thread) override; + + lldb::RegisterContextSP + CreateRegisterContextForThread(Thread *thread, + lldb::addr_t reg_data_addr) override; + + lldb::StopInfoSP CreateThreadStopReason(Thread *thread) override; + + bool DoesPluginReportAllThreads() override { return false; } + +private: + /// If a thread for task_id had been created in the last stop, return it. + /// Otherwise, create a new MemoryThread for it. + lldb::ThreadSP FindOrCreateSwiftThread(ThreadList &old_thread_list, + uint64_t task_id); + + /// Find the Task ID of the task being executed by `thread`, if any. + std::optional FindTaskId(Thread &thread); + + /// The offset of the Job ID inside a Task data structure. + size_t m_job_id_offset; +}; +} // namespace lldb_private + +#endif // LLDB_ENABLE_SWIFT + +#endif // liblldb_OperatingSystemSwiftTasks_h_ diff --git a/lldb/test/API/lang/swift/async/stepping/step_over_lots_of_tasks/Makefile b/lldb/test/API/lang/swift/async/stepping/step_over_lots_of_tasks/Makefile new file mode 100644 index 000000000000000..cca30b939e652f7 --- /dev/null +++ b/lldb/test/API/lang/swift/async/stepping/step_over_lots_of_tasks/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_lots_of_tasks/TestSwiftAsyncSteppingManyTasks.py b/lldb/test/API/lang/swift/async/stepping/step_over_lots_of_tasks/TestSwiftAsyncSteppingManyTasks.py new file mode 100644 index 000000000000000..92ed5634277cc37 --- /dev/null +++ b/lldb/test/API/lang/swift/async/stepping/step_over_lots_of_tasks/TestSwiftAsyncSteppingManyTasks.py @@ -0,0 +1,64 @@ +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, expected_linenum, expected_tid): + """Checks that thread has tid == expected_tid and is stopped at expected_linenum""" + self.assertEqual(expected_tid, thread.GetThreadID()) + + frame = thread.frames[0] + line_entry = frame.GetLineEntry() + self.assertEqual(expected_linenum, line_entry.GetLine()) + + @swiftTest + @skipIf(oslist=["windows", "linux"]) + def test_step_over_main(self): + self.build() + + source_file = lldb.SBFileSpec("main.swift") + target, process, thread, bkpt = lldbutil.run_to_source_breakpoint( + self, "Breakpoint main", source_file + ) + + main_task_id = thread.GetThreadID() + main_first_line = thread.frames[0].GetLineEntry().GetLine() + num_lines_main = 7 + + for line_offset in range(1, num_lines_main): + thread.StepOver() + self.check_is_in_line(thread, main_first_line + line_offset, main_task_id) + + @swiftTest + @skipIf(oslist=["windows", "linux"]) + def test_step_over_top_level_fibonacci(self): + self.build() + + source_file = lldb.SBFileSpec("main.swift") + target, process, thread, bkpt = lldbutil.run_to_source_breakpoint( + self, "Breakpoint main", source_file + ) + + main_task_id = thread.GetThreadID() + fib_bp = target.BreakpointCreateBySourceRegex("Breakpoint fib", source_file) + lldbutil.continue_to_breakpoint(process, fib_bp) + + # Get any of the threads that might have reached this breakpoint. + # Any thread should work: their initial value for `n` is > 2, + # so all threads do the full function body the first time around. + thread_in_fib = lldbutil.get_threads_stopped_at_breakpoint(process, fib_bp)[0] + thread_id_in_fib = thread_in_fib.GetThreadID() + fib_bp.SetEnabled(False) + + fib_first_line = thread_in_fib.frames[0].GetLineEntry().GetLine() + num_lines_fib = 5 + for line_offset in range(1, num_lines_fib): + thread_in_fib.StepOver() + self.assertEqual(process.GetSelectedThread(), thread_in_fib) + self.check_is_in_line( + thread_in_fib, fib_first_line + line_offset, thread_id_in_fib + ) diff --git a/lldb/test/API/lang/swift/async/stepping/step_over_lots_of_tasks/main.swift b/lldb/test/API/lang/swift/async/stepping/step_over_lots_of_tasks/main.swift new file mode 100644 index 000000000000000..87fe4707041aef5 --- /dev/null +++ b/lldb/test/API/lang/swift/async/stepping/step_over_lots_of_tasks/main.swift @@ -0,0 +1,25 @@ +@main enum entry { + static func main() async { + print("Breakpoint main") + async let fib5_task = fib(n: 5) + async let fib6_task = fib(n: 6) + let fib4 = await fib(n: 4) + let fib5 = await fib5_task + let fib6 = await fib6_task + print(fib4, fib5, fib6) + } +} + +func fib(n: Int) async -> Int { + if (n == 0) { + return 1 + } + if (n == 1) { + return 1 + } + async let n1_task = fib(n: n - 1) // Breakpoint fib + async let n2_task = fib(n: n - 2) + let n1 = await n1_task + let n2 = await n2_task + return n1 + n2 +}