avogelsgesang created this revision. avogelsgesang added reviewers: ChuanqiXu, aprantl, labath, mib, JDevlieghere. Herald added a subscriber: mgorny. Herald added a project: All. avogelsgesang requested review of this revision. Herald added projects: clang, LLDB. Herald added subscribers: lldb-commits, cfe-commits.
This patch adds a formatter for `std::coroutine_handle`, both for libc++ and libstdc++. For the type-erased `coroutine_handle<>`, it shows the `resume` and `destroy` function pointers. For a non-type-erased `coroutine_handle<promise_type>` it also shows the `promise` value. With this change, executing the `v t` command on the example from https://clang.llvm.org/docs/DebuggingCoroutines.html now outputs (task) t = { handle = coro frame = 0x55555555b2a0 { resume = 0x0000555555555a10 (a.out`coro_task(int, int) at llvm-example.cpp:36) destroy = 0x0000555555556090 (a.out`coro_task(int, int) at llvm-example.cpp:36) } } instead of just (task) t = { handle = { __handle_ = 0x55555555b2a0 } } Note, how the symbols for the `resume` and `destroy` function pointer reveals which coroutine is stored inside the `std::coroutine_handle`. A follow-up commit will use this fact to infer the coroutine's promise type and the representation of its internal coroutine state based on the `resume` and`destroy` pointers. The same formatter is used for both libc++ and libstdc++. It would also work for MSVC's standard library, however it is not registered for MSVC, given that lldb does not provide pretty printers for other MSVC types, either. The formatter is in a new added `Coroutines.{h,cpp}` file because there does not seem to be an already existing place where we could share formatters across libc++ and libstdc++. Also, I expect this code to grow as we improve debugging experience for coroutines further. **Testing** - Added API test Repository: rG LLVM Github Monorepo https://reviews.llvm.org/D132415 Files: clang/docs/tools/clang-formatted-files.txt lldb/packages/Python/lldbsuite/test/lldbtest.py lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp lldb/source/Plugins/Language/CPlusPlus/Coroutines.cpp lldb/source/Plugins/Language/CPlusPlus/Coroutines.h lldb/source/Plugins/Language/CPlusPlus/LibCxx.h lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/coroutine_handle/Makefile lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/coroutine_handle/TestCoroutineHandle.py lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/coroutine_handle/main.cpp
Index: lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/coroutine_handle/main.cpp =================================================================== --- /dev/null +++ lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/coroutine_handle/main.cpp @@ -0,0 +1,36 @@ +#include <coroutine> + +// `int_generator` is a stripped down, minimal coroutine generator +// type. +struct int_generator { + struct promise_type { + int current_value = -1; + + auto get_return_object() { + return std::coroutine_handle<promise_type>::from_promise(*this); + } + auto initial_suspend() { return std::suspend_always(); } + auto final_suspend() noexcept { return std::suspend_always(); } + auto return_void() { return std::suspend_always(); } + void unhandled_exception() { __builtin_unreachable(); } + auto yield_value(int v) { + current_value = v; + return std::suspend_always(); + } + }; + + std::coroutine_handle<promise_type> hdl; + + int_generator(std::coroutine_handle<promise_type> h) : hdl(h) {} + ~int_generator() { hdl.destroy(); } +}; + +int_generator my_generator_func() { co_yield 42; } + +int main() { + int_generator gen = my_generator_func(); + std::coroutine_handle<> type_erased_hdl = gen.hdl; + gen.hdl.resume(); // Break at initial_suspend + gen.hdl.resume(); // Break after co_yield + // Break at final_suspend +} Index: lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/coroutine_handle/TestCoroutineHandle.py =================================================================== --- /dev/null +++ lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/coroutine_handle/TestCoroutineHandle.py @@ -0,0 +1,75 @@ +""" +Test lldb data formatter subsystem. +""" + + + +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil + +USE_LIBSTDCPP = "USE_LIBSTDCPP" +USE_LIBCPP = "USE_LIBCPP" + +class TestCoroutineHandle(TestBase): + def do_test(self, stdlib_type): + """Test std::coroutine_handle is displayed correctly.""" + self.build(dictionary={stdlib_type: "1"}) + + test_generator_func_ptr_re = re.compile( + r"^\(a.out`my_generator_func\(\) at main.cpp:[0-9]*\)$") + + lldbutil.run_to_source_breakpoint(self, '// Break at initial_suspend', + lldb.SBFileSpec("main.cpp", False)) + # Check that we show the correct function pointers and the `promise`. + self.expect_expr("gen.hdl", + result_summary=re.compile("^coro frame = 0x[0-9a-f]*$"), + result_children=[ + ValueCheck(name="resume", summary = test_generator_func_ptr_re), + ValueCheck(name="destroy", summary = test_generator_func_ptr_re), + ValueCheck(name="promise", children=[ + ValueCheck(name="current_value", value = "-1"), + ]) + ]) + # For type-erased `coroutine_handle<>` we are missing the `promise` + # but still show `resume` and `destroy`. + self.expect_expr("type_erased_hdl", + result_summary=re.compile("^coro frame = 0x[0-9a-f]*$"), + result_children=[ + ValueCheck(name="resume", summary = test_generator_func_ptr_re), + ValueCheck(name="destroy", summary = test_generator_func_ptr_re), + ]) + + lldbutil.run_to_source_breakpoint(self, '// Break after co_yield', + lldb.SBFileSpec("main.cpp", False)) + # We correctly show the updated value inside `prommise.current_value`. + self.expect_expr("gen.hdl", + result_children=[ + ValueCheck(name="resume", summary = test_generator_func_ptr_re), + ValueCheck(name="destroy", summary = test_generator_func_ptr_re), + ValueCheck(name="promise", children=[ + ValueCheck(name="current_value", value = "42"), + ]) + ]) + + lldbutil.run_to_source_breakpoint(self, '// Break at final_suspend', + lldb.SBFileSpec("main.cpp", False)) + # At the final suspension point, `resume` is set to a nullptr. + # Check that we still show the remaining data correctly. + self.expect_expr("gen.hdl", + result_children=[ + ValueCheck(name="resume", value = "0x0000000000000000"), + ValueCheck(name="destroy", summary = test_generator_func_ptr_re), + ValueCheck(name="promise", children=[ + ValueCheck(name="current_value", value = "42"), + ]) + ]) + + @add_test_categories(["libstdcxx"]) + def test_libstdcpp(self): + self.do_test(USE_LIBSTDCPP) + + @add_test_categories(["libc++"]) + def test_libcpp(self): + self.do_test(USE_LIBCPP) Index: lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/coroutine_handle/Makefile =================================================================== --- /dev/null +++ lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/coroutine_handle/Makefile @@ -0,0 +1,4 @@ +CXX_SOURCES := main.cpp +CFLAGS_EXTRAS := -std=c++20 + +include Makefile.rules Index: lldb/source/Plugins/Language/CPlusPlus/LibCxx.h =================================================================== --- lldb/source/Plugins/Language/CPlusPlus/LibCxx.h +++ lldb/source/Plugins/Language/CPlusPlus/LibCxx.h @@ -59,9 +59,9 @@ const TypeSummaryOptions &options); // libc++ std::shared_ptr<> and std::weak_ptr<> -// libc++ std::unique_ptr<> -bool LibcxxUniquePointerSummaryProvider(ValueObject &valobj, Stream &stream, - const TypeSummaryOptions &options); +bool LibcxxUniquePointerSummaryProvider( + ValueObject &valobj, Stream &stream, + const TypeSummaryOptions &options); // libc++ std::unique_ptr<> bool LibcxxFunctionSummaryProvider( ValueObject &valobj, Stream &stream, Index: lldb/source/Plugins/Language/CPlusPlus/Coroutines.h =================================================================== --- /dev/null +++ lldb/source/Plugins/Language/CPlusPlus/Coroutines.h @@ -0,0 +1,52 @@ +//===-- Coroutines.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 LLDB_SOURCE_PLUGINS_LANGUAGE_CPLUSPLUS_COROUTINES_H +#define LLDB_SOURCE_PLUGINS_LANGUAGE_CPLUSPLUS_COROUTINES_H + +#include "lldb/Core/ValueObject.h" +#include "lldb/DataFormatters/TypeSummary.h" +#include "lldb/DataFormatters/TypeSynthetic.h" +#include "lldb/Utility/Stream.h" + +namespace lldb_private { +namespace formatters { + +// libc++, libstdc++ and MSVC STL: std::coroutine_handle<T> +bool StdlibCoroutineHandleSummaryProvider(ValueObject &valobj, Stream &stream, + const TypeSummaryOptions &options); + +class StdlibCoroutineHandleSyntheticFrontEnd + : public SyntheticChildrenFrontEnd { +public: + StdlibCoroutineHandleSyntheticFrontEnd(lldb::ValueObjectSP valobj_sp); + + ~StdlibCoroutineHandleSyntheticFrontEnd() override; + + size_t CalculateNumChildren() override; + + lldb::ValueObjectSP GetChildAtIndex(size_t idx) override; + + bool Update() override; + + bool MightHaveChildren() override; + + size_t GetIndexOfChildWithName(ConstString name) override; + +private: + lldb::ValueObjectSP m_frame_ptr_sp; +}; + +SyntheticChildrenFrontEnd * +StdlibCoroutineHandleSyntheticFrontEndCreator(CXXSyntheticChildren *, + lldb::ValueObjectSP); + +} // namespace formatters +} // namespace lldb_private + +#endif // LLDB_SOURCE_PLUGINS_LANGUAGE_CPLUSPLUS_COROUTINES_H Index: lldb/source/Plugins/Language/CPlusPlus/Coroutines.cpp =================================================================== --- /dev/null +++ lldb/source/Plugins/Language/CPlusPlus/Coroutines.cpp @@ -0,0 +1,137 @@ +//===-- Coroutines.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 +// +//===----------------------------------------------------------------------===// + +#include "Coroutines.h" + +#include "Plugins/TypeSystem/Clang/TypeSystemClang.h" + +using namespace lldb; +using namespace lldb_private; +using namespace lldb_private::formatters; + +static ValueObjectSP GetCoroFramePtrFromHandle(ValueObject &valobj) { + ValueObjectSP valobj_sp(valobj.GetNonSyntheticValue()); + if (!valobj_sp) + return nullptr; + + // We expect a single pointer in the `coroutine_handle` class. + // We don't care about its name. + if (valobj_sp->GetNumChildren() != 1) + return nullptr; + ValueObjectSP ptr_sp(valobj_sp->GetChildAtIndex(0, true)); + if (!ptr_sp) + return nullptr; + if (!ptr_sp->GetCompilerType().IsPointerType()) + return nullptr; + + return ptr_sp; +} + +static CompilerType GetCoroutineFrameType(TypeSystemClang &ast_ctx, + CompilerType promise_type) { + CompilerType void_type = ast_ctx.GetBasicType(lldb::eBasicTypeVoid); + CompilerType coro_func_type = ast_ctx.CreateFunctionType( + /*result_type*/ void_type, /*args*/ &void_type, /*num_args*/ 1, + /*is_variadic*/ false, /*qualifiers*/ 0); + CompilerType coro_abi_type; + if (promise_type.IsVoidType()) { + coro_abi_type = ast_ctx.CreateStructForIdentifier( + ConstString(), {{"resume", coro_func_type.GetPointerType()}, + {"destroy", coro_func_type.GetPointerType()}}); + } else { + coro_abi_type = ast_ctx.CreateStructForIdentifier( + ConstString(), {{"resume", coro_func_type.GetPointerType()}, + {"destroy", coro_func_type.GetPointerType()}, + {"promise", promise_type}}); + } + return coro_abi_type; +} + +bool lldb_private::formatters::StdlibCoroutineHandleSummaryProvider( + ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) { + ValueObjectSP ptr_sp(GetCoroFramePtrFromHandle(valobj)); + if (!ptr_sp) + return false; + + stream.Printf("coro frame = 0x%" PRIx64, ptr_sp->GetValueAsUnsigned(0)); + return true; +} + +lldb_private::formatters::StdlibCoroutineHandleSyntheticFrontEnd:: + StdlibCoroutineHandleSyntheticFrontEnd(lldb::ValueObjectSP valobj_sp) + : SyntheticChildrenFrontEnd(*valobj_sp) { + if (valobj_sp) + Update(); +} + +lldb_private::formatters::StdlibCoroutineHandleSyntheticFrontEnd:: + ~StdlibCoroutineHandleSyntheticFrontEnd() = default; + +size_t lldb_private::formatters::StdlibCoroutineHandleSyntheticFrontEnd:: + CalculateNumChildren() { + if (!m_frame_ptr_sp) + return 0; + + return m_frame_ptr_sp->GetNumChildren(); +} + +lldb::ValueObjectSP lldb_private::formatters:: + StdlibCoroutineHandleSyntheticFrontEnd::GetChildAtIndex(size_t idx) { + if (!m_frame_ptr_sp) + return lldb::ValueObjectSP(); + + return m_frame_ptr_sp->GetChildAtIndex(idx, true); +} + +bool lldb_private::formatters::StdlibCoroutineHandleSyntheticFrontEnd:: + Update() { + m_frame_ptr_sp.reset(); + + ValueObjectSP valobj_sp = m_backend.GetSP(); + if (!valobj_sp) + return false; + + ValueObjectSP ptr_sp(GetCoroFramePtrFromHandle(m_backend)); + if (!ptr_sp) + return false; + + TypeSystemClang *ast_ctx = llvm::dyn_cast_or_null<TypeSystemClang>( + valobj_sp->GetCompilerType().GetTypeSystem()); + if (!ast_ctx) + return false; + + CompilerType promise_type( + valobj_sp->GetCompilerType().GetTypeTemplateArgument(0)); + if (!promise_type) + return false; + CompilerType coro_frame_type = GetCoroutineFrameType(*ast_ctx, promise_type); + + m_frame_ptr_sp = ptr_sp->Cast(coro_frame_type.GetPointerType()); + + return false; +} + +bool lldb_private::formatters::StdlibCoroutineHandleSyntheticFrontEnd:: + MightHaveChildren() { + return true; +} + +size_t StdlibCoroutineHandleSyntheticFrontEnd::GetIndexOfChildWithName( + ConstString name) { + if (!m_frame_ptr_sp) + return UINT32_MAX; + + return m_frame_ptr_sp->GetIndexOfChildWithName(name); +} + +SyntheticChildrenFrontEnd * +lldb_private::formatters::StdlibCoroutineHandleSyntheticFrontEndCreator( + CXXSyntheticChildren *, lldb::ValueObjectSP valobj_sp) { + return (valobj_sp ? new StdlibCoroutineHandleSyntheticFrontEnd(valobj_sp) + : nullptr); +} \ No newline at end of file Index: lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp =================================================================== --- lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp +++ lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp @@ -34,6 +34,7 @@ #include "lldb/Utility/RegularExpression.h" #include "BlockPointer.h" +#include "Coroutines.h" #include "CPlusPlusNameParser.h" #include "CxxStringTypes.h" #include "Generic.h" @@ -796,6 +797,14 @@ ConstString("^std::__[[:alnum:]]+::function<.+>$"), stl_summary_flags, true); + ConstString libcxx_std_coroutine_handle_regex( + "^std::__[[:alnum:]]+::coroutine_handle<.+>(( )?&)?$"); + AddCXXSynthetic( + cpp_category_sp, + lldb_private::formatters::StdlibCoroutineHandleSyntheticFrontEndCreator, + "coroutine_handle synthetic children", libcxx_std_coroutine_handle_regex, + stl_deref_flags, true); + stl_summary_flags.SetDontShowChildren(false); stl_summary_flags.SetSkipPointers(false); AddCXXSummary(cpp_category_sp, @@ -898,6 +907,11 @@ "libc++ std::unique_ptr summary provider", libcxx_std_unique_ptr_regex, stl_summary_flags, true); + AddCXXSummary(cpp_category_sp, + lldb_private::formatters::StdlibCoroutineHandleSummaryProvider, + "libc++ std::coroutine_handle summary provider", + libcxx_std_coroutine_handle_regex, stl_summary_flags, true); + AddCXXSynthetic( cpp_category_sp, lldb_private::formatters::LibCxxVectorIteratorSyntheticFrontEndCreator, @@ -1122,6 +1136,20 @@ "std::tuple synthetic children", ConstString("^std::tuple<.+>(( )?&)?$"), stl_synth_flags, true); + AddCXXSynthetic( // XXX + cpp_category_sp, + lldb_private::formatters::LibStdcppTupleSyntheticFrontEndCreator, + "std::tuple synthetic children", ConstString("^std::tuple<.+>(( )?&)?$"), + stl_synth_flags, true); + + ConstString libstdcpp_std_coroutine_handle_regex( + "^std::coroutine_handle<.+>(( )?&)?$"); + AddCXXSynthetic( + cpp_category_sp, + lldb_private::formatters::StdlibCoroutineHandleSyntheticFrontEndCreator, + "std::coroutine_handle synthetic children", libstdcpp_std_coroutine_handle_regex, + stl_deref_flags, true); + AddCXXSynthetic( cpp_category_sp, lldb_private::formatters::LibStdcppBitsetSyntheticFrontEndCreator, @@ -1149,6 +1177,11 @@ "libstdc++ std::weak_ptr summary provider", ConstString("^std::weak_ptr<.+>(( )?&)?$"), stl_summary_flags, true); + AddCXXSummary(cpp_category_sp, + lldb_private::formatters::StdlibCoroutineHandleSummaryProvider, + "libstdc++ std::coroutine_handle summary provider", + libstdcpp_std_coroutine_handle_regex, stl_summary_flags, + true); AddCXXSummary( cpp_category_sp, lldb_private::formatters::GenericOptionalSummaryProvider, "libstd++ std::optional summary provider", Index: lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt =================================================================== --- lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt +++ lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt @@ -1,5 +1,6 @@ add_lldb_library(lldbPluginCPlusPlusLanguage PLUGIN BlockPointer.cpp + Coroutines.cpp CPlusPlusLanguage.cpp CPlusPlusNameParser.cpp CxxStringTypes.cpp Index: lldb/packages/Python/lldbsuite/test/lldbtest.py =================================================================== --- lldb/packages/Python/lldbsuite/test/lldbtest.py +++ lldb/packages/Python/lldbsuite/test/lldbtest.py @@ -292,8 +292,12 @@ test_base.assertEqual(self.expect_type, val.GetDisplayTypeName(), this_error_msg) if self.expect_summary: - test_base.assertEqual(self.expect_summary, val.GetSummary(), - this_error_msg) + if isinstance(self.expect_summary, re.Pattern): + test_base.assertRegex(val.GetSummary(), self.expect_summary, + this_error_msg) + else: + test_base.assertEqual(self.expect_summary, val.GetSummary(), + this_error_msg) if self.children is not None: self.check_value_children(test_base, val, error_msg) Index: clang/docs/tools/clang-formatted-files.txt =================================================================== --- clang/docs/tools/clang-formatted-files.txt +++ clang/docs/tools/clang-formatted-files.txt @@ -4180,6 +4180,8 @@ lldb/source/Plugins/Language/ClangCommon/ClangHighlighter.h lldb/source/Plugins/Language/CPlusPlus/BlockPointer.cpp lldb/source/Plugins/Language/CPlusPlus/BlockPointer.h +lldb/source/Plugins/Language/CPlusPlus/Coroutines.cpp +lldb/source/Plugins/Language/CPlusPlus/Coroutines.h lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.h lldb/source/Plugins/Language/CPlusPlus/CPlusPlusNameParser.h lldb/source/Plugins/Language/CPlusPlus/CxxStringTypes.h
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits