Skip to content

Commit 52c98fb

Browse files
Merge pull request #10259 from felipepiovezan/felipe/new_filtering_mechanism
[lldb][swift] Filter unnecessary funclets when setting line breakpoints
2 parents dd526d8 + de7263a commit 52c98fb

File tree

3 files changed

+112
-10
lines changed

3 files changed

+112
-10
lines changed

lldb/source/Plugins/Language/Swift/SwiftLanguage.cpp

Lines changed: 100 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1837,19 +1837,109 @@ SwiftLanguage::GetDemangledFunctionNameWithoutArguments(Mangled mangled) const {
18371837
return mangled_name;
18381838
}
18391839

1840-
void SwiftLanguage::FilterForLineBreakpoints(
1841-
llvm::SmallVectorImpl<SymbolContext> &sc_list) const {
1842-
llvm::erase_if(sc_list, [](const SymbolContext &sc) {
1843-
// If we don't have a function, conservatively keep this sc.
1844-
if (!sc.function)
1845-
return false;
1840+
namespace {
1841+
using namespace swift::Demangle;
1842+
struct AsyncInfo {
1843+
const Function *function;
1844+
NodePointer demangle_node;
1845+
std::optional<uint64_t> funclet_number;
1846+
};
18461847

1847-
// In async functions, ignore await resume ("Q") funclets, these only
1848-
// deallocate the async context and task_switch back to user code.
1848+
std::string to_string(const AsyncInfo &async_info) {
1849+
StreamString stream_str;
1850+
llvm::raw_ostream &str = stream_str.AsRawOstream();
1851+
str << "function = ";
1852+
if (async_info.function)
1853+
str << async_info.function->GetMangled().GetMangledName();
1854+
else
1855+
str << "nullptr";
1856+
str << ", demangle_node: " << async_info.demangle_node;
1857+
str << ", funclet_number = ";
1858+
if (async_info.funclet_number)
1859+
str << *async_info.funclet_number;
1860+
else
1861+
str << "nullopt";
1862+
return stream_str.GetString().str();
1863+
}
1864+
1865+
/// Map each unique Function in sc_list to a Demangle::NodePointer, or null if
1866+
/// demangling is not possible.
1867+
llvm::SmallVector<AsyncInfo> GetAsyncInfo(llvm::ArrayRef<SymbolContext> sc_list,
1868+
swift::Demangle::Context &ctx) {
1869+
Log *log(GetLog(LLDBLog::Demangle));
1870+
llvm::SmallSet<Function *, 8> seen_functions;
1871+
llvm::SmallVector<AsyncInfo> async_infos;
1872+
for (const SymbolContext &sc : sc_list) {
1873+
if (!sc.function || seen_functions.contains(sc.function))
1874+
continue;
1875+
seen_functions.insert(sc.function);
18491876
llvm::StringRef name =
18501877
sc.function->GetMangled().GetMangledName().GetStringRef();
1851-
return SwiftLanguageRuntime::IsSwiftAsyncAwaitResumePartialFunctionSymbol(
1852-
name);
1878+
NodePointer node = SwiftLanguageRuntime::DemangleSymbolAsNode(name, ctx);
1879+
async_infos.push_back(
1880+
{sc.function, node, SwiftLanguageRuntime::GetFuncletNumber(node)});
1881+
1882+
if (log) {
1883+
std::string as_str = to_string(async_infos.back());
1884+
LLDB_LOGF(log, "%s: %s", __FUNCTION__, as_str.c_str());
1885+
}
1886+
}
1887+
return async_infos;
1888+
}
1889+
} // namespace
1890+
1891+
void SwiftLanguage::FilterForLineBreakpoints(
1892+
llvm::SmallVectorImpl<SymbolContext> &sc_list) const {
1893+
using namespace swift::Demangle;
1894+
Context ctx;
1895+
1896+
llvm::SmallVector<AsyncInfo> async_infos = GetAsyncInfo(sc_list, ctx);
1897+
1898+
// Vector containing one representative funclet of each unique async function
1899+
// in sc_list. The representative is always the one with the smallest funclet
1900+
// number seen so far.
1901+
llvm::SmallVector<AsyncInfo> unique_async_funcs;
1902+
1903+
// Note the subtlety: this deletes based on functions, not SymbolContexts, as
1904+
// there might be multiple SCs with the same Function at this point.
1905+
llvm::SmallPtrSet<const Function *, 4> to_delete;
1906+
1907+
for (const auto &async_info : async_infos) {
1908+
// If we can't find a funclet number, don't delete this.
1909+
if (!async_info.funclet_number)
1910+
continue;
1911+
1912+
// Have we found other funclets of the same async function?
1913+
auto *representative =
1914+
llvm::find_if(unique_async_funcs, [&](AsyncInfo &other_info) {
1915+
// This looks quadratic, but in practice it is not. We should have at
1916+
// most 2 different async functions in the same line, unless a user
1917+
// writes many closures on the same line.
1918+
return SwiftLanguageRuntime::AreFuncletsOfSameAsyncFunction(
1919+
async_info.demangle_node, other_info.demangle_node) ==
1920+
SwiftLanguageRuntime::FuncletComparisonResult::
1921+
SameAsyncFunction;
1922+
});
1923+
1924+
// We found a new async function.
1925+
if (representative == unique_async_funcs.end()) {
1926+
unique_async_funcs.push_back(async_info);
1927+
continue;
1928+
}
1929+
1930+
// This is another funclet of the same async function. Keep the one with the
1931+
// smallest number, erase the other. If they have the same number, don't
1932+
// erase it.
1933+
if (async_info.funclet_number > representative->funclet_number)
1934+
to_delete.insert(async_info.function);
1935+
else if (async_info.funclet_number < representative->funclet_number) {
1936+
to_delete.insert(representative->function);
1937+
*representative = async_info;
1938+
}
1939+
}
1940+
1941+
llvm::erase_if(sc_list, [&](const SymbolContext &sc) {
1942+
return to_delete.contains(sc.function);
18531943
});
18541944
}
18551945

lldb/test/API/lang/swift/async_breakpoints/TestSwiftAsyncBreakpoints.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,15 @@ def test(self):
1919
)
2020
breakpoint2 = target.BreakpointCreateBySourceRegex("Breakpoint2", filespec)
2121
breakpoint3 = target.BreakpointCreateBySourceRegex("Breakpoint3", filespec)
22+
breakpoint4 = target.BreakpointCreateBySourceRegex("Breakpoint4", filespec)
23+
breakpoint5 = target.BreakpointCreateBySourceRegex("Breakpoint5", filespec)
2224
self.assertEquals(breakpoint1.GetNumLocations(), 1)
2325
self.assertEquals(breakpoint2.GetNumLocations(), 1)
2426
self.assertEquals(breakpoint3.GetNumLocations(), 1)
27+
# FIXME: there should be two breakpoints here, but the "entry" funclet of the
28+
# implicit closure is mangled slightly differently. rdar://147035260
29+
self.assertEquals(breakpoint4.GetNumLocations(), 3)
30+
self.assertEquals(breakpoint5.GetNumLocations(), 1)
2531

2632
location11 = breakpoint1.GetLocationAtIndex(0)
2733
self.assertEquals(location11.GetHitCount(), 1)

lldb/test/API/lang/swift/async_breakpoints/main.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ func foo() async {
1010
work() // Breakpoint2
1111
let timestamp2 = await getTimestamp(i:43) // Breakpoint3
1212
work()
13+
// There should be two breakpoints below in an async let:
14+
// One for the code in the "callee", i.e., foo.
15+
// One for the implicit closure in the RHS.
16+
async let timestamp3 = getTimestamp(i: 44) // Breakpoint4
17+
// There should be one breakpoint in an await of an async let variable
18+
await timestamp3 // Breakpoint5
1319
}
1420

1521
await foo()

0 commit comments

Comments
 (0)