From c8aadde5d611447ebfe1f652958f356eef8e44e3 Mon Sep 17 00:00:00 2001 From: Felipe de Azevedo Piovezan Date: Thu, 17 Oct 2024 06:25:04 -0700 Subject: [PATCH 1/3] [lldb][nfc] Create overload for IsAnySwiftAsyncFunctionSymbol This will enable reuse of this function in a subsequent commit. --- .../Plugins/LanguageRuntime/Swift/SwiftLanguageRuntime.h | 4 ++++ .../LanguageRuntime/Swift/SwiftLanguageRuntimeNames.cpp | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntime.h b/lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntime.h index 1f579833963d1..b234c86f666b0 100644 --- a/lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntime.h +++ b/lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntime.h @@ -139,6 +139,10 @@ class SwiftLanguageRuntime : public LanguageRuntime { /// function, or suspend resume partial function symbol. static bool IsAnySwiftAsyncFunctionSymbol(llvm::StringRef name); + /// Return true if node is a Swift async function, await resume partial + /// function, or suspend resume partial function symbol. + static bool IsAnySwiftAsyncFunctionSymbol(NodePointer node); + /// Return the async context address using the target's specific register. static lldb::addr_t GetAsyncContext(RegisterContext *regctx); diff --git a/lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntimeNames.cpp b/lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntimeNames.cpp index c1018096528f7..267f0a941518c 100644 --- a/lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntimeNames.cpp +++ b/lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntimeNames.cpp @@ -130,6 +130,10 @@ bool SwiftLanguageRuntime::IsAnySwiftAsyncFunctionSymbol(StringRef name) { using namespace swift::Demangle; Context ctx; NodePointer node = SwiftLanguageRuntime::DemangleSymbolAsNode(name, ctx); + return IsAnySwiftAsyncFunctionSymbol(node); +} + +bool SwiftLanguageRuntime::IsAnySwiftAsyncFunctionSymbol(NodePointer node) { if (!node || node->getKind() != Node::Kind::Global || !node->getNumChildren()) return false; auto marker = node->getFirstChild()->getKind(); From 1aec81a6083f756cc6b462c319ccf716dbcf3029 Mon Sep 17 00:00:00 2001 From: Felipe de Azevedo Piovezan Date: Thu, 17 Oct 2024 06:44:52 -0700 Subject: [PATCH 2/3] [lldb] Implement SwiftLanguageRuntime::AreFuncletsOfSameAsyncFunction This is going to be useful for the Swift Language plugin to identify whether two symbol contexts are equivalent, an operation which is done by thread plans such as ThreadPlanStepOverRange. The implementation compares the following attributes of the mangling tree: * If these funclets come from a simple async function (as opposed to a closure), we compare the "Function" child node in the tree. * If these funclets come from an async closure: ** Their "Number" child node must be the same. This number distinguishes different closures in the same scope. ** The closure type must be the same. ** If they have a parent closure, it must be identical. ** If they have a parent function, it must be identical. --- .../Swift/SwiftLanguageRuntime.h | 14 ++++ .../Swift/SwiftLanguageRuntimeNames.cpp | 71 ++++++++++++++++++ lldb/unittests/Symbol/TestSwiftDemangler.cpp | 72 ++++++++++++++++++- 3 files changed, 154 insertions(+), 3 deletions(-) diff --git a/lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntime.h b/lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntime.h index b234c86f666b0..17e3e4d15e3a3 100644 --- a/lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntime.h +++ b/lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntime.h @@ -132,6 +132,20 @@ class SwiftLanguageRuntime : public LanguageRuntime { /// since some day we may want to support more than one swift variant. static bool IsSwiftMangledName(llvm::StringRef name); + enum class FuncletComparisonResult { + NotBothFunclets, + DifferentAsyncFunctions, + SameAsyncFunction + }; + + /// Compares name1 and name2 to decide whether they are both async funclets. + /// If either is not an async funclet, returns NotBothFunclets. + /// If they are both funclets but of different async functions, returns + /// DifferentAsyncFunctions. + /// Otherwise, returns SameAsyncFunction. + static FuncletComparisonResult + AreFuncletsOfSameAsyncFunction(StringRef name1, StringRef name2); + /// Return true if name is a Swift async function symbol. static bool IsSwiftAsyncFunctionSymbol(llvm::StringRef name); diff --git a/lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntimeNames.cpp b/lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntimeNames.cpp index 267f0a941518c..bc561085ad25d 100644 --- a/lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntimeNames.cpp +++ b/lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntimeNames.cpp @@ -105,6 +105,77 @@ static bool IsSwiftAsyncFunctionSymbol(swift::Demangle::NodePointer node) { Node::Kind::AsyncAnnotation}); } +/// Returns true if closure1 and closure2 have the same number, type, and +/// parent closures / function. +static bool AreFuncletsOfSameAsyncClosure(NodePointer closure1, + NodePointer closure2) { + NodePointer closure1_number = childAtPath(closure1, Node::Kind::Number); + NodePointer closure2_number = childAtPath(closure2, Node::Kind::Number); + if (!Node::deepEquals(closure1_number, closure2_number)) + return false; + + NodePointer closure1_type = childAtPath(closure1, Node::Kind::Type); + NodePointer closure2_type = childAtPath(closure2, Node::Kind::Type); + if (!Node::deepEquals(closure1_type, closure2_type)) + return false; + + // Because the tree is inverted, a parent closure (in swift code) is a child + // *node* (in the demangle tree). Check that any such parents are identical. + NodePointer closure1_parent = + childAtPath(closure1, Node::Kind::ExplicitClosure); + NodePointer closure2_parent = + childAtPath(closure2, Node::Kind::ExplicitClosure); + if (!Node::deepEquals(closure1_parent, closure2_parent)) + return false; + + // If there are no ExplicitClosure as parents, there may still be a + // Function. Also check that they are identical. + NodePointer closure1_function = childAtPath(closure1, Node::Kind::Function); + NodePointer closure2_function = childAtPath(closure2, Node::Kind::Function); + return Node::deepEquals(closure1_function, closure2_function); +} + +SwiftLanguageRuntime::FuncletComparisonResult +SwiftLanguageRuntime::AreFuncletsOfSameAsyncFunction(StringRef name1, + StringRef name2) { + using namespace swift::Demangle; + Context ctx; + NodePointer node1 = DemangleSymbolAsNode(name1, ctx); + NodePointer node2 = DemangleSymbolAsNode(name2, ctx); + + if (!IsAnySwiftAsyncFunctionSymbol(node1) || + !IsAnySwiftAsyncFunctionSymbol(node2)) + return FuncletComparisonResult::NotBothFunclets; + + // Peel off Static nodes. + NodePointer static_wrapper1 = childAtPath(node1, Node::Kind::Static); + NodePointer static_wrapper2 = childAtPath(node2, Node::Kind::Static); + if (static_wrapper1 || static_wrapper2) { + if (!static_wrapper1 | !static_wrapper2) + return FuncletComparisonResult::DifferentAsyncFunctions; + node1 = static_wrapper1; + node2 = static_wrapper2; + } + + // If there are closures involved, do the closure-specific comparison. + NodePointer closure1 = childAtPath(node1, Node::Kind::ExplicitClosure); + NodePointer closure2 = childAtPath(node2, Node::Kind::ExplicitClosure); + if (closure1 || closure2) { + if (!closure1 || !closure2) + return FuncletComparisonResult::DifferentAsyncFunctions; + return AreFuncletsOfSameAsyncClosure(closure1, closure2) + ? FuncletComparisonResult::SameAsyncFunction + : FuncletComparisonResult::DifferentAsyncFunctions; + } + + // Otherwise, find the corresponding function and compare the two. + NodePointer function1 = childAtPath(node1, Node::Kind::Function); + NodePointer function2 = childAtPath(node2, Node::Kind::Function); + return Node::deepEquals(function1, function2) + ? FuncletComparisonResult::SameAsyncFunction + : FuncletComparisonResult::DifferentAsyncFunctions; +} + bool SwiftLanguageRuntime::IsSwiftAsyncFunctionSymbol(StringRef name) { if (!IsSwiftMangledName(name)) return false; diff --git a/lldb/unittests/Symbol/TestSwiftDemangler.cpp b/lldb/unittests/Symbol/TestSwiftDemangler.cpp index 9894ba04df353..2fb0c358b2bde 100644 --- a/lldb/unittests/Symbol/TestSwiftDemangler.cpp +++ b/lldb/unittests/Symbol/TestSwiftDemangler.cpp @@ -11,6 +11,39 @@ static constexpr auto IsAnySwiftAsyncFunctionSymbol = [](StringRef name) { return SwiftLanguageRuntime::IsAnySwiftAsyncFunctionSymbol(name); }; +using FuncletComparisonResult = SwiftLanguageRuntime::FuncletComparisonResult; +static constexpr auto AreFuncletsOfSameAsyncFunction = + SwiftLanguageRuntime::AreFuncletsOfSameAsyncFunction; + +/// Checks that all names in \c funclets belong to the same function. +static void CheckGroupOfFuncletsFromSameFunction(ArrayRef funclets) { + for (StringRef funclet1 : funclets) + for (StringRef funclet2 : funclets) { + EXPECT_EQ(FuncletComparisonResult::SameAsyncFunction, + AreFuncletsOfSameAsyncFunction(funclet1, funclet2)) + << funclet1 << " -- " << funclet2; + EXPECT_EQ(FuncletComparisonResult::SameAsyncFunction, + AreFuncletsOfSameAsyncFunction(funclet2, funclet1)) + << funclet1 << " -- " << funclet2; + } +} + +/// Checks that all pairs of combinations of names from \c funclets1 and \c +/// funclets2 belong to different functions. +static void +CheckGroupOfFuncletsFromDifferentFunctions(ArrayRef funclets1, + ArrayRef funclets2) { + for (StringRef funclet1 : funclets1) + for (StringRef funclet2 : funclets2) { + EXPECT_EQ(FuncletComparisonResult::DifferentAsyncFunctions, + AreFuncletsOfSameAsyncFunction(funclet1, funclet2)) + << funclet1 << " -- " << funclet2; + EXPECT_EQ(FuncletComparisonResult::DifferentAsyncFunctions, + AreFuncletsOfSameAsyncFunction(funclet2, funclet1)) + << funclet1 << " -- " << funclet2; + } +} + TEST(TestSwiftDemangleAsyncNames, BasicAsync) { // "sayBasic" == a basic async function // "sayGeneric" == a generic async function @@ -31,6 +64,10 @@ TEST(TestSwiftDemangleAsyncNames, BasicAsync) { EXPECT_TRUE(IsSwiftMangledName(async_name)) << async_name; EXPECT_TRUE(IsAnySwiftAsyncFunctionSymbol(async_name)) << async_name; } + + CheckGroupOfFuncletsFromSameFunction(basic_funclets); + CheckGroupOfFuncletsFromSameFunction(generic_funclets); + CheckGroupOfFuncletsFromDifferentFunctions(basic_funclets, generic_funclets); } TEST(TestSwiftDemangleAsyncNames, ClosureAsync) { @@ -69,19 +106,48 @@ TEST(TestSwiftDemangleAsyncNames, ClosureAsync) { EXPECT_TRUE(IsSwiftMangledName(async_name)) << async_name; EXPECT_TRUE(IsAnySwiftAsyncFunctionSymbol(async_name)) << async_name; } + + CheckGroupOfFuncletsFromSameFunction(nested1_funclets); + CheckGroupOfFuncletsFromSameFunction(nested2_funclets1); + CheckGroupOfFuncletsFromSameFunction(nested2_funclets2); + CheckGroupOfFuncletsFromSameFunction(nested2_funclets_top_not_async); + + CheckGroupOfFuncletsFromDifferentFunctions(nested1_funclets, + nested2_funclets1); + CheckGroupOfFuncletsFromDifferentFunctions(nested1_funclets, + nested2_funclets2); + CheckGroupOfFuncletsFromDifferentFunctions(nested1_funclets, + nested2_funclets_top_not_async); + CheckGroupOfFuncletsFromDifferentFunctions(nested2_funclets1, + nested2_funclets2); + CheckGroupOfFuncletsFromDifferentFunctions(nested2_funclets1, + nested2_funclets_top_not_async); + CheckGroupOfFuncletsFromDifferentFunctions(nested2_funclets2, + nested2_funclets_top_not_async); } TEST(TestSwiftDemangleAsyncNames, StaticAsync) { // static async functions - SmallVector async_names = { - "$s1a6StructV9sayStaticyySSYaFZ" + SmallVector static_async_funclets = { + "$s1a6StructV9sayStaticyySSYaFZ", "$s1a6StructV9sayStaticyySSYaFZTY0_", "$s1a6StructV9sayStaticyySSYaFZTQ1_", "$s1a6StructV9sayStaticyySSYaFZTY2_", }; - for (StringRef async_name : async_names) { + for (StringRef async_name : static_async_funclets) { EXPECT_TRUE(IsSwiftMangledName(async_name)) << async_name; EXPECT_TRUE(IsAnySwiftAsyncFunctionSymbol(async_name)) << async_name; } + + CheckGroupOfFuncletsFromSameFunction(static_async_funclets); + + // Make sure we can compare static funclets to other kinds of funclets + SmallVector other_funclets = { + // Nested funclets: + "$s1a8sayHelloyyYaFyypYacfU_", "$s1a8sayHelloyyYaFyypYacfU_TY0_", + // "Normal" funclets: + "$s1a8sayBasicyySSYaF", "$s1a8sayBasicyySSYaFTY0_"}; + CheckGroupOfFuncletsFromDifferentFunctions(static_async_funclets, + other_funclets); } From 063abb3966ffecabc8afa0a6128104e093386a54 Mon Sep 17 00:00:00 2001 From: Felipe de Azevedo Piovezan Date: Thu, 17 Oct 2024 06:09:19 -0700 Subject: [PATCH 3/3] [lldb] Implement SwiftLanguage::AreEqualForFrameComparison This checks whether the two provided symbol contexts refer to funclets of the same async function. In particular, this fixes an issue in the ThreadPlanStepOverRange algorithm for async functions. When the plan stops, it compares the current symbol context to the symbol context of where it started. An initial comparison is done through StackIDs. If that comparison says the two are equal, the plan then attempts to confirm the two SymbolContexts are the same by checking whether the underlying functions are the same. Because different funclets correspond to different low level functions, this comparison was failing. --- .../Plugins/Language/Swift/SwiftLanguage.cpp | 17 +++++++++++++++++ .../Plugins/Language/Swift/SwiftLanguage.h | 6 ++++++ 2 files changed, 23 insertions(+) diff --git a/lldb/source/Plugins/Language/Swift/SwiftLanguage.cpp b/lldb/source/Plugins/Language/Swift/SwiftLanguage.cpp index 8ca2076cb163f..5c238bb94ca4c 100644 --- a/lldb/source/Plugins/Language/Swift/SwiftLanguage.cpp +++ b/lldb/source/Plugins/Language/Swift/SwiftLanguage.cpp @@ -1834,6 +1834,23 @@ bool SwiftLanguage::IgnoreForLineBreakpoints(const SymbolContext &sc) const { name); } +std::optional +SwiftLanguage::AreEqualForFrameComparison(const SymbolContext &sc1, + const SymbolContext &sc2) const { + auto result = SwiftLanguageRuntime::AreFuncletsOfSameAsyncFunction( + sc1.GetFunctionName(Mangled::ePreferMangled), + sc2.GetFunctionName(Mangled::ePreferMangled)); + switch (result) { + case SwiftLanguageRuntime::FuncletComparisonResult::NotBothFunclets: + return {}; + case SwiftLanguageRuntime::FuncletComparisonResult::SameAsyncFunction: + return true; + case SwiftLanguageRuntime::FuncletComparisonResult::DifferentAsyncFunctions: + return false; + } + llvm_unreachable("unhandled enumeration in AreEquivalentFunctions"); +} + //------------------------------------------------------------------ // Static Functions //------------------------------------------------------------------ diff --git a/lldb/source/Plugins/Language/Swift/SwiftLanguage.h b/lldb/source/Plugins/Language/Swift/SwiftLanguage.h index 46001748bd263..191a03ccf1712 100644 --- a/lldb/source/Plugins/Language/Swift/SwiftLanguage.h +++ b/lldb/source/Plugins/Language/Swift/SwiftLanguage.h @@ -75,6 +75,12 @@ class SwiftLanguage : public Language { ConstString GetDemangledFunctionNameWithoutArguments(Mangled mangled) const override; + /// Returns whether two SymbolContexts correspond to funclets of the same + /// async function. + /// If either SymbolContext is not a funclet, nullopt is returned. + std::optional + AreEqualForFrameComparison(const SymbolContext &sc1, + const SymbolContext &sc2) const override; //------------------------------------------------------------------ // Static Functions //------------------------------------------------------------------