[clang] 9138900 - [LLDB] Add data formatter for std::coroutine_handle

Adrian Vogelsgesang via cfe-commits cfe-commits at lists.llvm.org
Wed Aug 24 14:45:22 PDT 2022


Author: Adrian Vogelsgesang
Date: 2022-08-24T14:40:53-07:00
New Revision: 91389000abe8ef5d06d98cbbefd3fa03ac7e4480

URL: https://github.com/llvm/llvm-project/commit/91389000abe8ef5d06d98cbbefd3fa03ac7e4480
DIFF: https://github.com/llvm/llvm-project/commit/91389000abe8ef5d06d98cbbefd3fa03ac7e4480.diff

LOG: [LLDB] Add data formatter for std::coroutine_handle

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
reveal 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 newly 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

Differential Revision: https://reviews.llvm.org/D132415

Added: 
    lldb/source/Plugins/Language/CPlusPlus/Coroutines.cpp
    lldb/source/Plugins/Language/CPlusPlus/Coroutines.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

Modified: 
    clang/docs/tools/clang-formatted-files.txt
    lldb/packages/Python/lldbsuite/test/lldbtest.py
    lldb/packages/Python/lldbsuite/test/lldbutil.py
    lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt
    lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp

Removed: 
    


################################################################################
diff  --git a/clang/docs/tools/clang-formatted-files.txt b/clang/docs/tools/clang-formatted-files.txt
index f89e19ca7cd3a..c49acfaec58aa 100644
--- a/clang/docs/tools/clang-formatted-files.txt
+++ b/clang/docs/tools/clang-formatted-files.txt
@@ -4180,6 +4180,8 @@ lldb/source/Plugins/Language/ClangCommon/ClangHighlighter.cpp
 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

diff  --git a/lldb/packages/Python/lldbsuite/test/lldbtest.py b/lldb/packages/Python/lldbsuite/test/lldbtest.py
index 69bb5ac5629e4..1c090395e6c4c 100644
--- a/lldb/packages/Python/lldbsuite/test/lldbtest.py
+++ b/lldb/packages/Python/lldbsuite/test/lldbtest.py
@@ -292,8 +292,12 @@ def check_value(self, test_base, val, error_msg=None):
             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)
 

diff  --git a/lldb/packages/Python/lldbsuite/test/lldbutil.py b/lldb/packages/Python/lldbsuite/test/lldbutil.py
index 8bd49c742cb04..7e64afb3639cd 100644
--- a/lldb/packages/Python/lldbsuite/test/lldbutil.py
+++ b/lldb/packages/Python/lldbsuite/test/lldbutil.py
@@ -988,6 +988,21 @@ def continue_to_breakpoint(process, bkpt):
         return get_threads_stopped_at_breakpoint(process, bkpt)
 
 
+def continue_to_source_breakpoint(test, process, bkpt_pattern, source_spec):
+    """
+    Sets a breakpoint set by source regex bkpt_pattern, continues the process, and deletes the breakpoint again.
+    Otherwise the same as `continue_to_breakpoint`
+    """
+    breakpoint = process.target.BreakpointCreateBySourceRegex(
+            bkpt_pattern, source_spec, None)
+    test.assertTrue(breakpoint.GetNumLocations() > 0,
+                    'No locations found for source breakpoint: "%s", file: "%s", dir: "%s"'
+                    %(bkpt_pattern, source_spec.GetFilename(), source_spec.GetDirectory()))
+    stopped_threads = continue_to_breakpoint(process, breakpoint)
+    process.target.BreakpointDelete(breakpoint.GetID())
+    return stopped_threads
+
+
 def get_caller_symbol(thread):
     """
     Returns the symbol name for the call site of the leaf function.

diff  --git a/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt b/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt
index 80135daf4cfd7..3e76ba30ada59 100644
--- a/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt
+++ b/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

diff  --git a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
index 23ce1654fb83f..e2b8bc641fba3 100644
--- a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
+++ b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
@@ -35,6 +35,7 @@
 
 #include "BlockPointer.h"
 #include "CPlusPlusNameParser.h"
+#include "Coroutines.h"
 #include "CxxStringTypes.h"
 #include "Generic.h"
 #include "LibCxx.h"
@@ -796,6 +797,14 @@ static void LoadLibCxxFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {
                 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 @@ static void LoadLibCxxFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {
                 "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,14 @@ static void LoadLibStdcppFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {
       "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 +1171,10 @@ static void LoadLibStdcppFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {
                 "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",

diff  --git a/lldb/source/Plugins/Language/CPlusPlus/Coroutines.cpp b/lldb/source/Plugins/Language/CPlusPlus/Coroutines.cpp
new file mode 100644
index 0000000000000..5427a7a2b7850
--- /dev/null
+++ b/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);
+}

diff  --git a/lldb/source/Plugins/Language/CPlusPlus/Coroutines.h b/lldb/source/Plugins/Language/CPlusPlus/Coroutines.h
new file mode 100644
index 0000000000000..f5846c6b8cfbe
--- /dev/null
+++ b/lldb/source/Plugins/Language/CPlusPlus/Coroutines.h
@@ -0,0 +1,57 @@
+//===-- 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 {
+
+/// Summary provider for `std::coroutine_handle<T>` from  libc++, libstdc++ and
+/// MSVC STL.
+bool StdlibCoroutineHandleSummaryProvider(ValueObject &valobj, Stream &stream,
+                                          const TypeSummaryOptions &options);
+
+/// Synthetic children frontend for `std::coroutine_handle<promise_type>` from
+/// libc++, libstdc++ and MSVC STL. Shows the compiler-generated `resume` and
+/// `destroy` function pointers as well as the `promise`, if the promise type
+/// is `promise_type != void`.
+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

diff  --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/coroutine_handle/Makefile b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/coroutine_handle/Makefile
new file mode 100644
index 0000000000000..6914024392cfd
--- /dev/null
+++ b/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

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
new file mode 100644
index 0000000000000..33d580d98df61
--- /dev/null
+++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/coroutine_handle/TestCoroutineHandle.py
@@ -0,0 +1,79 @@
+"""
+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]*\)$")
+
+        # Run until the initial suspension point
+        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),
+            ])
+
+        # Run until after the `co_yield`
+        process = self.process()
+        lldbutil.continue_to_source_breakpoint(self, process,
+                '// 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"),
+                ])
+            ])
+        
+        # Run until the `final_suspend`
+        lldbutil.continue_to_source_breakpoint(self, process,
+                '// 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)

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
new file mode 100644
index 0000000000000..6a54912459c80
--- /dev/null
+++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/coroutine_handle/main.cpp
@@ -0,0 +1,40 @@
+#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; }
+
+// This is an empty function which we call just so the debugger has
+// a place to reliably set a breakpoint on.
+void empty_function_so_we_can_set_a_breakpoint() {}
+
+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
+  empty_function_so_we_can_set_a_breakpoint(); // Break at final_suspend
+}


        


More information about the cfe-commits mailing list