Skip to content

Commit c763551

Browse files
Merge pull request #9546 from felipepiovezan/felipe/remove_thread_plan_enhance_async_plan_try2
[lldb][swift] Fix thread plan to step through swift_task_switch
2 parents 834e05a + 1d2f744 commit c763551

File tree

6 files changed

+147
-203
lines changed

6 files changed

+147
-203
lines changed

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

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2522,20 +2522,7 @@ void SwiftLanguageRuntime::DidFinishExecutingUserExpression(
25222522

25232523
bool SwiftLanguageRuntime::IsABIStable() { FORWARD(IsABIStable); }
25242524

2525-
namespace {
2526-
/// The target specific register numbers used for async unwinding.
2527-
///
2528-
/// For UnwindPlans, these use eh_frame / dwarf register numbering.
2529-
struct AsyncUnwindRegisterNumbers {
2530-
uint32_t async_ctx_regnum;
2531-
uint32_t fp_regnum;
2532-
uint32_t pc_regnum;
2533-
2534-
RegisterKind GetRegisterKind() const { return lldb::eRegisterKindDWARF; }
2535-
};
2536-
} // namespace
2537-
2538-
static std::optional<AsyncUnwindRegisterNumbers>
2525+
std::optional<AsyncUnwindRegisterNumbers>
25392526
GetAsyncUnwindRegisterNumbers(llvm::Triple::ArchType triple) {
25402527
switch (triple) {
25412528
case llvm::Triple::x86_64: {

lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntime.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,21 @@ class SwiftLanguageRuntime : public LanguageRuntime {
517517
unsigned num_indirections = 0);
518518
};
519519

520+
/// The target specific register numbers used for async unwinding.
521+
///
522+
/// For UnwindPlans, these use eh_frame / dwarf register numbering.
523+
struct AsyncUnwindRegisterNumbers {
524+
uint32_t async_ctx_regnum;
525+
uint32_t fp_regnum;
526+
uint32_t pc_regnum;
527+
528+
/// All register numbers in this struct are given in the eRegisterKindDWARF
529+
/// domain.
530+
lldb::RegisterKind GetRegisterKind() const { return lldb::eRegisterKindDWARF; }
531+
};
532+
533+
std::optional<AsyncUnwindRegisterNumbers>
534+
GetAsyncUnwindRegisterNumbers(llvm::Triple::ArchType triple);
520535
} // namespace lldb_private
521536

522537
#endif // liblldb_SwiftLanguageRuntime_h_

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

Lines changed: 77 additions & 189 deletions
Original file line numberDiff line numberDiff line change
@@ -46,15 +46,13 @@ enum class ThunkKind {
4646
ObjCAttribute,
4747
Reabstraction,
4848
ProtocolConformance,
49-
AsyncFunction,
5049
};
5150

5251
enum class ThunkAction {
5352
Unknown = 0,
5453
GetThunkTarget,
5554
StepIntoConformance,
5655
StepThrough,
57-
AsyncStepIn,
5856
};
5957

6058
} // namespace
@@ -232,12 +230,8 @@ static ThunkKind GetThunkKind(Symbol *symbol) {
232230
if (nodes->getNumChildren() == 0)
233231
return ThunkKind::Unknown;
234232

235-
if (!demangle_ctx.isThunkSymbol(symbol_name)) {
236-
if (IsSwiftAsyncFunctionSymbol(nodes)) {
237-
return ThunkKind::AsyncFunction;
238-
}
233+
if (!demangle_ctx.isThunkSymbol(symbol_name))
239234
return ThunkKind::Unknown;
240-
}
241235

242236
NodePointer main_node = nodes->getFirstChild();
243237
switch (main_node->getKind()) {
@@ -276,8 +270,6 @@ static const char *GetThunkKindName(ThunkKind kind) {
276270
return "GetThunkTarget";
277271
case ThunkKind::ProtocolConformance:
278272
return "StepIntoConformance";
279-
case ThunkKind::AsyncFunction:
280-
return "AsyncStepIn";
281273
}
282274
}
283275

@@ -295,219 +287,115 @@ static ThunkAction GetThunkAction(ThunkKind kind) {
295287
return ThunkAction::StepThrough;
296288
case ThunkKind::ProtocolConformance:
297289
return ThunkAction::StepIntoConformance;
298-
case ThunkKind::AsyncFunction:
299-
return ThunkAction::AsyncStepIn;
300290
}
301291
}
302292

303-
class ThreadPlanStepInAsync : public ThreadPlan {
293+
/// A thread plan to run to a specific address on a specific async context.
294+
class ThreadPlanRunToAddressOnAsyncCtx : public ThreadPlan {
304295
public:
305-
static bool NeedsStep(SymbolContext &sc) {
306-
if (sc.line_entry.IsValid() && sc.line_entry.line == 0)
307-
// Compiler generated function, need to step in.
308-
return true;
309-
310-
// TEMPORARY HACK WORKAROUND
311-
if (!sc.symbol || !sc.comp_unit)
312-
return false;
313-
auto fn_start = sc.symbol->GetFileAddress();
314-
auto fn_end = sc.symbol->GetFileAddress() + sc.symbol->GetByteSize();
315-
llvm::SmallSet<uint32_t, 2> unique_debug_lines;
316-
if (auto *line_table = sc.comp_unit->GetLineTable()) {
317-
for (uint32_t i = 0; i < line_table->GetSize(); ++i) {
318-
LineEntry line_entry;
319-
if (line_table->GetLineEntryAtIndex(i, line_entry)) {
320-
if (!line_entry.IsValid() || line_entry.line == 0)
321-
continue;
322-
323-
auto line_start = line_entry.range.GetBaseAddress().GetFileAddress();
324-
if (fn_start <= line_start && line_start < fn_end) {
325-
unique_debug_lines.insert(line_entry.line);
326-
// This logic is to distinguish between async functions that only
327-
// call `swift_task_switch` (which, from the perspective of the
328-
// user, has no meaningful function body), vs async functions that
329-
// do have a function body. In the first case, lldb should step
330-
// further to find the function body, in the second case lldb has
331-
// found a body and should stop.
332-
//
333-
// Currently, async functions that go through `swift_task_switch`
334-
// are generated with a reference to a single line. If this function
335-
// has more than one unique debug line, then it is a function that
336-
// has a body, and execution can stop here.
337-
if (unique_debug_lines.size() >= 2)
338-
// No step into `swift_task_switch` required.
339-
return false;
340-
}
341-
}
342-
}
343-
}
344-
345-
return true;
296+
/// Creates a thread plan to run to destination_addr of an async function
297+
/// whose context is async_ctx.
298+
ThreadPlanRunToAddressOnAsyncCtx(Thread &thread, addr_t destination_addr,
299+
addr_t async_ctx)
300+
: ThreadPlan(eKindGeneric, "run-to-funclet", thread, eVoteNoOpinion,
301+
eVoteNoOpinion),
302+
m_destination_addr(destination_addr), m_expected_async_ctx(async_ctx) {
303+
auto &target = thread.GetProcess()->GetTarget();
304+
m_funclet_bp = target.CreateBreakpoint(destination_addr, true, false);
305+
m_funclet_bp->SetBreakpointKind("async-run-to-funclet");
346306
}
347307

348-
ThreadPlanStepInAsync(Thread &thread, SymbolContext &sc)
349-
: ThreadPlan(eKindGeneric, "step-in-async", thread, eVoteNoOpinion,
350-
eVoteNoOpinion) {
351-
assert(sc.function);
352-
if (!sc.function)
353-
return;
354-
355-
m_step_in_plan_sp = std::make_shared<ThreadPlanStepInRange>(
356-
thread, sc.function->GetAddressRange(), sc, "swift_task_switch",
357-
RunMode::eAllThreads, eLazyBoolNo, eLazyBoolNo);
358-
}
308+
bool ValidatePlan(Stream *error) override {
309+
if (m_funclet_bp->HasResolvedLocations())
310+
return true;
359311

360-
void DidPush() override {
361-
if (m_step_in_plan_sp)
362-
PushPlan(m_step_in_plan_sp);
312+
// If we failed to resolve any locations, this plan is invalid.
313+
m_funclet_bp->GetTarget().RemoveBreakpointByID(m_funclet_bp->GetID());
314+
return false;
363315
}
364316

365-
bool ValidatePlan(Stream *error) override { return (bool)m_step_in_plan_sp; }
366-
367317
void GetDescription(Stream *s, lldb::DescriptionLevel level) override {
368-
// TODO: Implement completely.
369-
s->PutCString("ThreadPlanStepInAsync");
318+
s->PutCString("ThreadPlanRunToAddressOnAsyncCtx to address = ");
319+
s->PutHex64(m_destination_addr);
320+
s->PutCString(" with async ctx = ");
321+
s->PutHex64(m_expected_async_ctx);
370322
}
371323

324+
/// This plan explains the stop if the current async context is the async
325+
/// context this plan was created with.
372326
bool DoPlanExplainsStop(Event *event) override {
373327
if (!HasTID())
374328
return false;
375-
376-
if (!m_async_breakpoint_sp)
377-
return false;
378-
379-
return GetBreakpointAsyncContext() == m_initial_async_ctx;
329+
return GetCurrentAsyncContext() == m_expected_async_ctx;
380330
}
381331

332+
/// If this plan explained the stop, it always stops: its sole purpose is to
333+
/// run to the breakpoint it set on the right async function invocation.
382334
bool ShouldStop(Event *event) override {
383-
if (!m_async_breakpoint_sp)
384-
return false;
385-
386-
if (GetBreakpointAsyncContext() != m_initial_async_ctx)
387-
return false;
388-
389335
SetPlanComplete();
390336
return true;
391337
}
392338

339+
/// If this plan said ShouldStop, then its job is complete.
393340
bool MischiefManaged() override {
394-
if (IsPlanComplete())
395-
return true;
396-
397-
if (!m_step_in_plan_sp->IsPlanComplete())
398-
return false;
399-
400-
if (!m_step_in_plan_sp->PlanSucceeded()) {
401-
// If the step in fails, then this plan fails.
402-
SetPlanComplete(false);
403-
return true;
404-
}
405-
406-
if (!m_async_breakpoint_sp) {
407-
auto &thread = GetThread();
408-
m_async_breakpoint_sp = CreateAsyncBreakpoint(thread);
409-
m_initial_async_ctx = GetAsyncContext(thread.GetStackFrameAtIndex(1));
410-
ClearTID();
411-
}
412-
413-
return false;
341+
return IsPlanComplete();
414342
}
415343

416344
bool WillStop() override { return false; }
417-
418345
lldb::StateType GetPlanRunState() override { return eStateRunning; }
419-
420346
bool StopOthers() override { return false; }
421-
422347
void DidPop() override {
423-
if (m_async_breakpoint_sp)
424-
m_async_breakpoint_sp->GetTarget().RemoveBreakpointByID(
425-
m_async_breakpoint_sp->GetID());
348+
m_funclet_bp->GetTarget().RemoveBreakpointByID(m_funclet_bp->GetID());
426349
}
427350

428351
private:
429-
bool IsAtAsyncBreakpoint() {
430-
auto stop_info_sp = GetPrivateStopInfo();
431-
if (!stop_info_sp)
432-
return false;
433-
434-
if (stop_info_sp->GetStopReason() != eStopReasonBreakpoint)
435-
return false;
436-
437-
auto &site_list = m_process.GetBreakpointSiteList();
438-
auto site_sp = site_list.FindByID(stop_info_sp->GetValue());
439-
if (!site_sp)
440-
return false;
441-
442-
return site_sp->IsBreakpointAtThisSite(m_async_breakpoint_sp->GetID());
443-
}
444-
445-
std::optional<lldb::addr_t> GetBreakpointAsyncContext() {
446-
if (m_breakpoint_async_ctx)
447-
return m_breakpoint_async_ctx;
448-
449-
if (!IsAtAsyncBreakpoint())
450-
return {};
451-
352+
addr_t GetCurrentAsyncContext() {
452353
auto frame_sp = GetThread().GetStackFrameAtIndex(0);
453-
auto async_ctx = GetAsyncContext(frame_sp);
454-
455-
if (!IsIndirectContext(frame_sp)) {
456-
m_breakpoint_async_ctx = async_ctx;
457-
return m_breakpoint_async_ctx;
458-
}
459-
460-
// Dereference the indirect async context.
461-
auto process_sp = GetThread().GetProcess();
462-
Status error;
463-
m_breakpoint_async_ctx =
464-
process_sp->ReadPointerFromMemory(async_ctx, error);
465-
return m_breakpoint_async_ctx;
466-
}
467-
468-
bool IsIndirectContext(lldb::StackFrameSP frame_sp) {
469-
auto sc = frame_sp->GetSymbolContext(eSymbolContextSymbol);
470-
auto mangled_name = sc.symbol->GetMangled().GetMangledName().GetStringRef();
471-
return SwiftLanguageRuntime::IsSwiftAsyncAwaitResumePartialFunctionSymbol(
472-
mangled_name);
354+
return frame_sp->GetStackID().GetCallFrameAddress();
473355
}
474356

475-
BreakpointSP CreateAsyncBreakpoint(Thread &thread) {
476-
// The signature for `swift_task_switch` is as follows:
477-
// SWIFT_CC(swiftasync)
478-
// void swift_task_switch(
479-
// SWIFT_ASYNC_CONTEXT AsyncContext *resumeContext,
480-
// TaskContinuationFunction *resumeFunction,
481-
// ExecutorRef newExecutor);
482-
//
483-
// The async context given as the first argument is not passed using the
484-
// calling convention's first register, it's passed in the platform's async
485-
// context register. This means the `resumeFunction` parameter uses the
486-
// first ABI register (ex: x86-64: rdi, arm64: x0).
487-
auto reg_ctx = thread.GetStackFrameAtIndex(0)->GetRegisterContext();
488-
constexpr auto resume_fn_regnum = LLDB_REGNUM_GENERIC_ARG1;
489-
auto resume_fn_reg = reg_ctx->ConvertRegisterKindToRegisterNumber(
490-
RegisterKind::eRegisterKindGeneric, resume_fn_regnum);
491-
auto resume_fn_ptr = reg_ctx->ReadRegisterAsUnsigned(resume_fn_reg, 0);
492-
if (!resume_fn_ptr)
493-
return {};
357+
addr_t m_destination_addr;
358+
addr_t m_expected_async_ctx;
359+
BreakpointSP m_funclet_bp;
360+
};
494361

495-
auto &target = thread.GetProcess()->GetTarget();
496-
auto breakpoint_sp = target.CreateBreakpoint(resume_fn_ptr, true, false);
497-
breakpoint_sp->SetBreakpointKind("async-step");
498-
return breakpoint_sp;
499-
}
362+
/// Given a thread that is stopped at the start of swift_task_switch, create a
363+
/// 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).
376+
RegisterContextSP reg_ctx =
377+
thread.GetStackFrameAtIndex(0)->GetRegisterContext();
378+
constexpr unsigned resume_fn_regnum = LLDB_REGNUM_GENERIC_ARG1;
379+
unsigned resume_fn_reg = reg_ctx->ConvertRegisterKindToRegisterNumber(
380+
RegisterKind::eRegisterKindGeneric, resume_fn_regnum);
381+
uint64_t resume_fn_ptr = reg_ctx->ReadRegisterAsUnsigned(resume_fn_reg, 0);
382+
if (!resume_fn_ptr)
383+
return {};
500384

501-
static lldb::addr_t GetAsyncContext(lldb::StackFrameSP frame_sp) {
502-
auto reg_ctx_sp = frame_sp->GetRegisterContext();
503-
return SwiftLanguageRuntime::GetAsyncContext(reg_ctx_sp.get());
504-
}
385+
auto arch = reg_ctx->CalculateTarget()->GetArchitecture();
386+
std::optional<AsyncUnwindRegisterNumbers> async_regs =
387+
GetAsyncUnwindRegisterNumbers(arch.GetMachine());
388+
if (!async_regs)
389+
return {};
390+
unsigned async_reg_number = reg_ctx->ConvertRegisterKindToRegisterNumber(
391+
async_regs->GetRegisterKind(), async_regs->async_ctx_regnum);
392+
uint64_t async_ctx = reg_ctx->ReadRegisterAsUnsigned(async_reg_number, 0);
393+
if (!async_ctx)
394+
return {};
505395

506-
ThreadPlanSP m_step_in_plan_sp;
507-
BreakpointSP m_async_breakpoint_sp;
508-
std::optional<lldb::addr_t> m_initial_async_ctx;
509-
std::optional<lldb::addr_t> m_breakpoint_async_ctx;
510-
};
396+
return std::make_shared<ThreadPlanRunToAddressOnAsyncCtx>(
397+
thread, resume_fn_ptr, async_ctx);
398+
}
511399

512400
static lldb::ThreadPlanSP GetStepThroughTrampolinePlan(Thread &thread,
513401
bool stop_others) {
@@ -538,18 +426,18 @@ static lldb::ThreadPlanSP GetStepThroughTrampolinePlan(Thread &thread,
538426
if (symbol_addr != cur_addr)
539427
return nullptr;
540428

541-
const char *symbol_name = symbol->GetMangled().GetMangledName().AsCString();
429+
Mangled &mangled_symbol_name = symbol->GetMangled();
430+
const char *symbol_name = mangled_symbol_name.GetMangledName().AsCString();
431+
432+
if (mangled_symbol_name.GetDemangledName() == "swift_task_switch")
433+
return CreateRunThroughTaskSwitchThreadPlan(thread);
542434

543435
ThunkKind thunk_kind = GetThunkKind(symbol);
544436
ThunkAction thunk_action = GetThunkAction(thunk_kind);
545437

546438
switch (thunk_action) {
547439
case ThunkAction::Unknown:
548440
return nullptr;
549-
case ThunkAction::AsyncStepIn:
550-
if (ThreadPlanStepInAsync::NeedsStep(sc))
551-
return std::make_shared<ThreadPlanStepInAsync>(thread, sc);
552-
return nullptr;
553441
case ThunkAction::GetThunkTarget: {
554442
swift::Demangle::Context demangle_ctx;
555443
std::string thunk_target = demangle_ctx.getThunkTarget(symbol_name);

0 commit comments

Comments
 (0)