diff --git a/lldb/source/Plugins/Language/CPlusPlus/Coroutines.cpp b/lldb/source/Plugins/Language/CPlusPlus/Coroutines.cpp --- a/lldb/source/Plugins/Language/CPlusPlus/Coroutines.cpp +++ b/lldb/source/Plugins/Language/CPlusPlus/Coroutines.cpp @@ -35,7 +35,7 @@ return ptr_sp; } -static Function *ExtractDestroyFunction(ValueObjectSP &frame_ptr_sp) { +static Function *ExtractFunction(ValueObjectSP &frame_ptr_sp, int offset) { lldb::TargetSP target_sp = frame_ptr_sp->GetTargetSP(); lldb::ProcessSP process_sp = frame_ptr_sp->GetProcessSP(); auto ptr_size = process_sp->GetAddressByteSize(); @@ -47,24 +47,64 @@ lldbassert(addr_type == AddressType::eAddressTypeLoad); Status error; - // The destroy pointer is the 2nd pointer inside the compiler-generated - // `pair`. - auto destroy_func_ptr_addr = frame_ptr_addr + ptr_size; - lldb::addr_t destroy_func_addr = - process_sp->ReadPointerFromMemory(destroy_func_ptr_addr, error); + auto func_ptr_addr = frame_ptr_addr + offset * ptr_size; + lldb::addr_t func_addr = + process_sp->ReadPointerFromMemory(func_ptr_addr, error); if (error.Fail()) return nullptr; - Address destroy_func_address; - if (!target_sp->ResolveLoadAddress(destroy_func_addr, destroy_func_address)) + Address func_address; + if (!target_sp->ResolveLoadAddress(func_addr, func_address)) return nullptr; - Function *destroy_func = - destroy_func_address.CalculateSymbolContextFunction(); - if (!destroy_func) - return nullptr; + return func_address.CalculateSymbolContextFunction(); +} + +static Function *ExtractResumeFunction(ValueObjectSP &frame_ptr_sp) { + return ExtractFunction(frame_ptr_sp, 0); +} + +static Function *ExtractDestroyFunction(ValueObjectSP &frame_ptr_sp) { + return ExtractFunction(frame_ptr_sp, 1); +} + +static bool IsNoopCoroFunction(Function *f) { + if (!f) + return false; - return destroy_func; + // clang's `__builtin_coro_noop` gets lowered to + // `_NoopCoro_ResumeDestroy`. This is used by libc++ + // on clang. + auto mangledName = f->GetMangled().GetMangledName(); + if (mangledName == "__NoopCoro_ResumeDestroy") + return true; + + // libc++ uses the following name as a fallback on + // compilers without `__builtin_coro_noop`. + auto name = f->GetNameNoArguments(); + static RegularExpression libcxxRegex( + "^std::coroutine_handle::" + "__noop_coroutine_frame_ty_::__dummy_resume_destroy_func$"); + lldbassert(libcxxRegex.IsValid()); + if (libcxxRegex.Execute(name.GetStringRef())) + return true; + static RegularExpression libcxxRegexAbiNS( + "^std::__[[:alnum:]]+::coroutine_handle::__noop_coroutine_frame_ty_::" + "__dummy_resume_destroy_func$"); + lldbassert(libcxxRegexAbiNS.IsValid()); + if (libcxxRegexAbiNS.Execute(name.GetStringRef())) + return true; + + // libstdc++ uses the following name on both gcc and clang. + static RegularExpression libstdcppRegex( + "^std::__[[:alnum:]]+::coroutine_handle::__frame::__dummy_resume_destroy$"); + lldbassert(libstdcppRegex.IsValid()); + if (libstdcppRegex.Execute(name.GetStringRef())) + return true; + + return false; } static CompilerType InferPromiseType(Function &destroy_func) { @@ -113,9 +153,15 @@ if (!ptr_sp->GetValueAsUnsigned(0)) { stream << "nullptr"; - } else { - stream.Printf("coro frame = 0x%" PRIx64, ptr_sp->GetValueAsUnsigned(0)); + return true; } + if (IsNoopCoroFunction(ExtractResumeFunction(ptr_sp)) && + IsNoopCoroFunction(ExtractDestroyFunction(ptr_sp))) { + stream << "noop_coroutine"; + return true; + } + + stream.Printf("coro frame = 0x%" PRIx64, ptr_sp->GetValueAsUnsigned(0)); return true; } @@ -158,6 +204,14 @@ if (!ptr_sp) return false; + Function *resume_func = ExtractResumeFunction(ptr_sp); + Function *destroy_func = ExtractDestroyFunction(ptr_sp); + + if (IsNoopCoroFunction(resume_func) && IsNoopCoroFunction(destroy_func)) { + // For `std::noop_coroutine()`, we don't want to display any child nodes. + return false; + } + // Get the `promise_type` from the template argument CompilerType promise_type( valobj_sp->GetCompilerType().GetTypeTemplateArgument(0)); @@ -169,12 +223,10 @@ auto ast_ctx = ts.dyn_cast_or_null(); if (!ast_ctx) return false; - if (promise_type.IsVoidType()) { - if (Function *destroy_func = ExtractDestroyFunction(ptr_sp)) { - if (CompilerType inferred_type = InferPromiseType(*destroy_func)) { - // Copy the type over to the correct `TypeSystemClang` instance - promise_type = m_ast_importer->CopyType(*ast_ctx, inferred_type); - } + if (promise_type.IsVoidType() && destroy_func) { + if (CompilerType inferred_type = InferPromiseType(*destroy_func)) { + // Copy the type over to the correct `TypeSystemClang` instance + promise_type = m_ast_importer->CopyType(*ast_ctx, inferred_type); } } diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/coroutine_handle/TestCoroutineHandle.py b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/coroutine_handle/TestCoroutineHandle.py --- a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/coroutine_handle/TestCoroutineHandle.py +++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/coroutine_handle/TestCoroutineHandle.py @@ -38,6 +38,13 @@ ValueCheck(name="current_value", value = "-1"), ]) ]) + # We recognize and pretty-print `std::noop_coroutine`. We don't display + # any children as those are irrelevant for the noop coroutine. + # clang version < 16 did not yet write debug info for the noop coroutines. + if not (is_clang and self.expectedCompilerVersion(["<", "16"])): + self.expect_expr("noop_hdl", + result_summary="noop_coroutine", + result_children=[]) if is_clang: # For a type-erased `coroutine_handle<>`, we can still devirtualize # the promise call and display the correctly typed promise. diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/coroutine_handle/main.cpp b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/coroutine_handle/main.cpp --- a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/coroutine_handle/main.cpp +++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/coroutine_handle/main.cpp @@ -45,6 +45,7 @@ std::coroutine_handle<> type_erased_hdl = gen.hdl; std::coroutine_handle incorrectly_typed_hdl = std::coroutine_handle::from_address(gen.hdl.address()); + std::coroutine_handle<> noop_hdl = std::noop_coroutine(); gen.hdl.resume(); // Break at initial_suspend gen.hdl.resume(); // Break after co_yield empty_function_so_we_can_set_a_breakpoint(); // Break at final_suspend