From 71331338d3d946c995ef478c25b28195420f6130 Mon Sep 17 00:00:00 2001 From: Joshua Rowe Date: Fri, 3 Oct 2025 09:23:51 -0700 Subject: [PATCH 01/46] Add make_shared_task make_task and make_shared_task are now niebloids --- Phantom.Coroutines.Modules/make_task.ixx | 1 + Phantom.Coroutines.Test/make_Task_test.cpp | 6 +-- .../include/cppcoro/shared_task.hpp | 2 +- .../include/cppcoro/task.hpp | 2 +- .../include/Phantom.Coroutines/make_task.h | 44 +++++++++++-------- 5 files changed, 31 insertions(+), 24 deletions(-) diff --git a/Phantom.Coroutines.Modules/make_task.ixx b/Phantom.Coroutines.Modules/make_task.ixx index a4ee032..89bfb0c 100644 --- a/Phantom.Coroutines.Modules/make_task.ixx +++ b/Phantom.Coroutines.Modules/make_task.ixx @@ -3,6 +3,7 @@ module; #include #include "Phantom.Coroutines/detail/config_macros.h" export module Phantom.Coroutines.make_task; +import Phantom.Coroutines.shared_task; import Phantom.Coroutines.task; import Phantom.Coroutines.type_traits; #include "Phantom.Coroutines/make_task.h" diff --git a/Phantom.Coroutines.Test/make_Task_test.cpp b/Phantom.Coroutines.Test/make_Task_test.cpp index 8f117d5..245f1b2 100644 --- a/Phantom.Coroutines.Test/make_Task_test.cpp +++ b/Phantom.Coroutines.Test/make_Task_test.cpp @@ -30,7 +30,7 @@ ASYNC_TEST(make_task, Can_make_task_returning_void) ASYNC_TEST(make_task, Can_make_shared_task_returning_void) { async_manual_reset_event<> event; - auto result = make_task(event); + auto result = make_shared_task(event); static_assert(std::same_as, decltype(result)>); @@ -45,7 +45,7 @@ ASYNC_TEST(make_task, Can_make_shared_task_from_task) { co_return; }; - auto result = make_task(lambda()); + auto result = make_shared_task(lambda()); static_assert(std::same_as, decltype(result)>); @@ -59,7 +59,7 @@ ASYNC_TEST(make_task, Can_make_shared_task_from_task_return_value) { co_return "hello world"; }; - auto result = make_task(lambda()); + auto result = make_shared_task(lambda()); static_assert(std::same_as, decltype(result)>); diff --git a/Phantom.Coroutines.cppcoro/include/cppcoro/shared_task.hpp b/Phantom.Coroutines.cppcoro/include/cppcoro/shared_task.hpp index 7bae5b0..0bab046 100644 --- a/Phantom.Coroutines.cppcoro/include/cppcoro/shared_task.hpp +++ b/Phantom.Coroutines.cppcoro/include/cppcoro/shared_task.hpp @@ -18,7 +18,7 @@ template< Awaitable&& awaitable ) { - return ::Phantom::Coroutines::make_task( + return ::Phantom::Coroutines::make_task_wrapper( std::forward(awaitable)); } diff --git a/Phantom.Coroutines.cppcoro/include/cppcoro/task.hpp b/Phantom.Coroutines.cppcoro/include/cppcoro/task.hpp index fbcba80..e836ad0 100644 --- a/Phantom.Coroutines.cppcoro/include/cppcoro/task.hpp +++ b/Phantom.Coroutines.cppcoro/include/cppcoro/task.hpp @@ -16,7 +16,7 @@ constexpr auto make_task = []( auto&& awaitable ) { - return ::Phantom::Coroutines::make_task( + return ::Phantom::Coroutines::make_task_wrapper( std::forward(awaitable)); }; diff --git a/Phantom.Coroutines/include/Phantom.Coroutines/make_task.h b/Phantom.Coroutines/include/Phantom.Coroutines/make_task.h index 98bff83..03c5fd8 100644 --- a/Phantom.Coroutines/include/Phantom.Coroutines/make_task.h +++ b/Phantom.Coroutines/include/Phantom.Coroutines/make_task.h @@ -3,6 +3,7 @@ #ifndef PHANTOM_COROUTINES_COMPILING_MODULES #include #include +#include "shared_task.h" #include "task.h" #include "type_traits.h" #endif @@ -13,31 +14,36 @@ PHANTOM_COROUTINES_ASSERT_IS_MODULE; namespace Phantom::Coroutines { -PHANTOM_COROUTINES_MODULE_EXPORT template< - template typename Task = task, + typename Task, typename Awaitable -> auto make_task( - Awaitable&& awaitable +> Task make_task_wrapper_implementation( + Awaitable awaitable ) +{ + co_return co_await std::forward(awaitable); +} + +PHANTOM_COROUTINES_MODULE_EXPORT +template< + template typename Task +> constexpr auto make_task_wrapper = []( + Awaitable&& awaitable + ) { using result_task_type = Task>>; + return make_task_wrapper_implementation< + result_task_type, + Awaitable + >( + std::forward(awaitable)); +}; - if constexpr (std::is_lvalue_reference_v) - { - return [](Awaitable awaitable) -> result_task_type - { - co_return co_await awaitable; - }(awaitable); - } - else - { - return [](Awaitable awaitable) -> result_task_type - { - co_return co_await std::move(awaitable); - }(std::move(awaitable)); - } -} +PHANTOM_COROUTINES_MODULE_EXPORT +constexpr auto make_task = make_task_wrapper; + +PHANTOM_COROUTINES_MODULE_EXPORT +constexpr auto make_shared_task = make_task_wrapper; } #endif From 0e5a30bfc134f5970e182baf7c30000ddf4677af Mon Sep 17 00:00:00 2001 From: Joshua Rowe Date: Mon, 27 Oct 2025 11:33:37 -0700 Subject: [PATCH 02/46] Add text tracing skeleton --- Phantom.Coroutines.Modules/CMakeLists.txt | 3 ++- Phantom.Coroutines.Modules/text_tracing.ixx | 4 ++++ Phantom.Coroutines.Test/CMakeLists.txt | 3 ++- Phantom.Coroutines.Test/text_tracing_test.cpp | 16 ++++++++++++++ .../include/Phantom.Coroutines/text_tracing.h | 21 +++++++++++++++++++ 5 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 Phantom.Coroutines.Modules/text_tracing.ixx create mode 100644 Phantom.Coroutines.Test/text_tracing_test.cpp create mode 100644 Phantom.Coroutines/include/Phantom.Coroutines/text_tracing.h diff --git a/Phantom.Coroutines.Modules/CMakeLists.txt b/Phantom.Coroutines.Modules/CMakeLists.txt index 0b3c9b2..240f6dc 100644 --- a/Phantom.Coroutines.Modules/CMakeLists.txt +++ b/Phantom.Coroutines.Modules/CMakeLists.txt @@ -76,6 +76,7 @@ target_sources( "sync_wait.ixx" "tagged_pointer.ixx" "task.ixx" + "text_tracing.ixx" "thread_local_context.ixx" "thread_local_contextual_promise.ixx" "thread_local_storage.ixx" @@ -84,7 +85,7 @@ target_sources( "type_traits.ixx" "value_awaiter.ixx" "variant_result_storage.ixx" - ) +) target_link_libraries( Phantom.Coroutines.Modules diff --git a/Phantom.Coroutines.Modules/text_tracing.ixx b/Phantom.Coroutines.Modules/text_tracing.ixx new file mode 100644 index 0000000..1eaacc7 --- /dev/null +++ b/Phantom.Coroutines.Modules/text_tracing.ixx @@ -0,0 +1,4 @@ +module; +#include "Phantom.Coroutines/detail/config_macros.h" +export module Phantom.Coroutines.text_tracing; +#include "Phantom.Coroutines/text_tracing.h" diff --git a/Phantom.Coroutines.Test/CMakeLists.txt b/Phantom.Coroutines.Test/CMakeLists.txt index bcac685..07600a6 100644 --- a/Phantom.Coroutines.Test/CMakeLists.txt +++ b/Phantom.Coroutines.Test/CMakeLists.txt @@ -48,13 +48,14 @@ set( "sync_wait_test.cpp" "tagged_pointer_test.cpp" "task_test.cpp" + "text_tracing_test.cpp" "thread_local_contextual_promise_test.cpp" "thread_local_storage_test.cpp" "thread_pool_scheduler_test.cpp" "tracing_test.cpp" "type_traits_test.cpp" "value_awaiter_test.cpp" - ) +) add_executable( Phantom.Coroutines.Test diff --git a/Phantom.Coroutines.Test/text_tracing_test.cpp b/Phantom.Coroutines.Test/text_tracing_test.cpp new file mode 100644 index 0000000..1c9d619 --- /dev/null +++ b/Phantom.Coroutines.Test/text_tracing_test.cpp @@ -0,0 +1,16 @@ +#include "async_test.h" +#include "Phantom.Coroutines/detail/config_macros.h" +#if defined(PHANTOM_COROUTINES_TESTING_SINGLE_MODULE) +import Phantom.Coroutines; +#elif defined(PHANTOM_COROUTINES_TESTING_MODULES) +import Phantom.Coroutines.text_tracing; +#elif defined(PHANTOM_COROUTINES_TESTING_HEADERS) +#include "Phantom.Coroutines/text_tracing.h" +#endif + +namespace Phantom::Coroutines::tracing +{ + + + +} diff --git a/Phantom.Coroutines/include/Phantom.Coroutines/text_tracing.h b/Phantom.Coroutines/include/Phantom.Coroutines/text_tracing.h new file mode 100644 index 0000000..9036be7 --- /dev/null +++ b/Phantom.Coroutines/include/Phantom.Coroutines/text_tracing.h @@ -0,0 +1,21 @@ +#ifndef PHANTOM_COROUTINES_INCLUDE_TEXT_TRACING_H +#define PHANTOM_COROUTINES_INCLUDE_TEXT_TRACING_H +#ifndef PHANTOM_COROUTINES_COMPILING_MODULES +#endif + +static_assert(PHANTOM_COROUTINES_IS_CONFIGURED); +PHANTOM_COROUTINES_ASSERT_IS_MODULE; + +namespace Phantom::Coroutines +{ + +namespace tracing +{ + + +} + +// namespace Phantom::Coroutines +} + +#endif From 9880f27ac3e98c3da78964fa9436f3aa16032ac7 Mon Sep 17 00:00:00 2001 From: Joshua Rowe Date: Sat, 15 Nov 2025 19:39:52 -0800 Subject: [PATCH 03/46] Rewrite uses of conflicted_name for latest Msvc --- .../include/Phantom.Coroutines/type_traits.h | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Phantom.Coroutines/include/Phantom.Coroutines/type_traits.h b/Phantom.Coroutines/include/Phantom.Coroutines/type_traits.h index afdc7d1..717bd01 100644 --- a/Phantom.Coroutines/include/Phantom.Coroutines/type_traits.h +++ b/Phantom.Coroutines/include/Phantom.Coroutines/type_traits.h @@ -314,15 +314,15 @@ struct conflict_detector PHANTOM_COROUTINES_MODULE_EXPORT struct has_await_suspend_conflicted_name { - using await_suspend = int; + static int await_suspend; }; PHANTOM_COROUTINES_MODULE_EXPORT template< typename Awaiter -> concept has_await_suspend = !requires +> concept has_await_suspend = !requires (conflict_detector conflictDetector, int result) { - typename conflict_detector::await_suspend; + { result = conflictDetector.await_suspend }; }; PHANTOM_COROUTINES_MODULE_EXPORT @@ -397,7 +397,7 @@ template< TAwaitable awaitable ) { - { operator co_await(std::forward(awaitable)) } -> is_awaiter; + { operator co_await(awaitable) } -> is_awaiter; }; PHANTOM_COROUTINES_MODULE_EXPORT @@ -485,15 +485,15 @@ template< struct has_await_transform_conflicted_name { - using await_transform = int; + static int await_transform; }; PHANTOM_COROUTINES_MODULE_EXPORT template< typename Promise -> concept has_await_transform = !requires +> concept has_await_transform = !requires (conflict_detector conflictDetector, int result) { - typename conflict_detector::await_transform; + { result = conflictDetector.await_transform }; }; PHANTOM_COROUTINES_MODULE_EXPORT @@ -535,9 +535,9 @@ struct has_yield_value_conflicted_name PHANTOM_COROUTINES_MODULE_EXPORT template< typename Promise -> concept has_yield_value = !requires +> concept has_yield_value = !requires (conflict_detector conflictDetector, int result) { - typename conflict_detector::yield_value; + { result = conflictDetector.yield_value }; }; PHANTOM_COROUTINES_MODULE_EXPORT From 51a2ba070cc1e06a7a43c329e738ee798b135674 Mon Sep 17 00:00:00 2001 From: Joshua Rowe Date: Sat, 15 Nov 2025 19:40:06 -0800 Subject: [PATCH 04/46] Fix missing "template" keywords --- .../include/Phantom.Coroutines/detail/core_task.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Phantom.Coroutines/include/Phantom.Coroutines/detail/core_task.h b/Phantom.Coroutines/include/Phantom.Coroutines/detail/core_task.h index 0d3aba7..5ecaf7d 100644 --- a/Phantom.Coroutines/include/Phantom.Coroutines/detail/core_task.h +++ b/Phantom.Coroutines/include/Phantom.Coroutines/detail/core_task.h @@ -55,7 +55,7 @@ class core_task_variant_return_result Result value ) requires (!std::same_as) { - self.m_result.emplace(std::move(value)); + self.m_result.template emplace(std::move(value)); } void return_variant_result( @@ -63,7 +63,7 @@ class core_task_variant_return_result const Result& value ) { - self.m_result.emplace(value); + self.m_result.template emplace(value); } }; From 413d6da19c229086b1e3e05fb207b09b70642a12 Mon Sep 17 00:00:00 2001 From: Joshua Rowe Date: Sat, 15 Nov 2025 19:40:54 -0800 Subject: [PATCH 05/46] Add missing #include --- Phantom.Coroutines.cppcoro.Test/cppcoro_shared_task_test.cpp | 2 ++ Phantom.Coroutines.cppcoro.Test/cppcoro_task_test.cpp | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Phantom.Coroutines.cppcoro.Test/cppcoro_shared_task_test.cpp b/Phantom.Coroutines.cppcoro.Test/cppcoro_shared_task_test.cpp index 756103f..de661e9 100644 --- a/Phantom.Coroutines.cppcoro.Test/cppcoro_shared_task_test.cpp +++ b/Phantom.Coroutines.cppcoro.Test/cppcoro_shared_task_test.cpp @@ -1,5 +1,7 @@ #include "async_test.h" #include "cppcoro/shared_task.hpp" +#include +#include static_assert(std::same_as, Phantom::Coroutines::shared_task<>>); static_assert(std::same_as<::Phantom::Coroutines::shared_task<>, decltype(::cppcoro::make_shared_task(std::suspend_always{}))>); diff --git a/Phantom.Coroutines.cppcoro.Test/cppcoro_task_test.cpp b/Phantom.Coroutines.cppcoro.Test/cppcoro_task_test.cpp index 1cd0b7a..c4b0f9d 100644 --- a/Phantom.Coroutines.cppcoro.Test/cppcoro_task_test.cpp +++ b/Phantom.Coroutines.cppcoro.Test/cppcoro_task_test.cpp @@ -1,8 +1,9 @@ #include "async_test.h" +#include #include +#include #include "cppcoro/task.hpp" - namespace Phantom::cppcoro_test { using namespace ::cppcoro; From 65edb99d33012abd7164264c5b8e61f137797fef Mon Sep 17 00:00:00 2001 From: Joshua Rowe Date: Sat, 15 Nov 2025 19:42:20 -0800 Subject: [PATCH 06/46] Add missing template keywords --- .../include/Phantom.Coroutines/contextual_promise.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Phantom.Coroutines/include/Phantom.Coroutines/contextual_promise.h b/Phantom.Coroutines/include/Phantom.Coroutines/contextual_promise.h index 9a02f68..efab0cb 100644 --- a/Phantom.Coroutines/include/Phantom.Coroutines/contextual_promise.h +++ b/Phantom.Coroutines/include/Phantom.Coroutines/contextual_promise.h @@ -78,7 +78,7 @@ public derived_promise self.m_bSuspended = true; if (std::same_as) { - self.extensible_promise_handle::promise().leave(); + self.template extensible_promise_handle::promise().leave(); } return self.awaiter().await_suspend(std::forward(args)...); } @@ -96,7 +96,7 @@ public derived_promise { if (self.m_bSuspended) { - self.extensible_promise_handle::promise().enter(); + self.template extensible_promise_handle::promise().enter(); } } return self.awaiter().await_resume(std::forward(args)...); @@ -135,7 +135,7 @@ public derived_promise self, [&]() -> decltype(auto) { - return self.derived_promise::initial_suspend(); + return self.template derived_promise::initial_suspend(); } }; } @@ -153,7 +153,7 @@ public derived_promise self, [&]() noexcept -> decltype(auto) { - return self.derived_promise::final_suspend(); + return self.template derived_promise::final_suspend(); } }; } @@ -170,11 +170,11 @@ public derived_promise DoLeaveOnSuspend{}, self, [&]() noexcept(noexcept( - self.derived_promise::await_transform( + self.template derived_promise::await_transform( std::forward(awaitable)) )) -> decltype(auto) { - return self.derived_promise::await_transform( + return self.template derived_promise::await_transform( std::forward(awaitable)); } }; From f2d07ea202b85419d34d8b531a658fe68bae00ec Mon Sep 17 00:00:00 2001 From: Joshua Rowe Date: Sun, 16 Nov 2025 10:01:05 -0800 Subject: [PATCH 07/46] Make things work on Msvc 14.50.35717 --- .../Phantom.Coroutines.ixx | 2 +- Phantom.Coroutines.Test/async_mutex_test.cpp | 2 +- Phantom.Coroutines.Test/async_test.h | 1 - Phantom.Coroutines.Test/detail/awaiters.h | 4 +- .../detail/non_copyable_test.cpp | 4 +- .../reusable_task_test.cpp | 2 +- Phantom.Coroutines.Test/type_traits_test.cpp | 4 +- .../Phantom.Coroutines/awaiter_wrapper.h | 11 +++--- .../Phantom.Coroutines/contextual_promise.h | 38 +++++++++++-------- .../include/Phantom.Coroutines/type_traits.h | 2 +- 10 files changed, 37 insertions(+), 33 deletions(-) diff --git a/Phantom.Coroutines.Modules/Phantom.Coroutines.ixx b/Phantom.Coroutines.Modules/Phantom.Coroutines.ixx index 0400313..78f290b 100644 --- a/Phantom.Coroutines.Modules/Phantom.Coroutines.ixx +++ b/Phantom.Coroutines.Modules/Phantom.Coroutines.ixx @@ -59,7 +59,7 @@ export import Phantom.Coroutines.thread_local_context; export import Phantom.Coroutines.thread_local_contextual_promise; export import Phantom.Coroutines.thread_local_storage; export import Phantom.Coroutines.thread_pool_scheduler; -export import Phantom.Coroutines.tracing; +//export import Phantom.Coroutines.tracing; export import Phantom.Coroutines.type_traits; export import Phantom.Coroutines.value_awaiter; export import Phantom.Coroutines.variant_result_storage; diff --git a/Phantom.Coroutines.Test/async_mutex_test.cpp b/Phantom.Coroutines.Test/async_mutex_test.cpp index cecaa95..1c4dbd2 100644 --- a/Phantom.Coroutines.Test/async_mutex_test.cpp +++ b/Phantom.Coroutines.Test/async_mutex_test.cpp @@ -1,6 +1,7 @@ #include "Phantom.Coroutines/detail/config_macros.h" #include #include +#include "async_test.h" #if defined(PHANTOM_COROUTINES_TESTING_SINGLE_MODULE) import Phantom.Coroutines; #elif defined(PHANTOM_COROUTINES_TESTING_MODULES) @@ -18,7 +19,6 @@ import Phantom.Coroutines.task; #include "Phantom.Coroutines/policies.h" #include "Phantom.Coroutines/task.h" #endif -#include "async_test.h" namespace Phantom::Coroutines { diff --git a/Phantom.Coroutines.Test/async_test.h b/Phantom.Coroutines.Test/async_test.h index 3b44b2e..6098d36 100644 --- a/Phantom.Coroutines.Test/async_test.h +++ b/Phantom.Coroutines.Test/async_test.h @@ -1,7 +1,6 @@ #ifndef PHANTOM_COROUTINES_INCLUDE_ASYNC_TEST_H_H #define PHANTOM_COROUTINES_INCLUDE_ASYNC_TEST_H_H -#include #include #if defined(PHANTOM_COROUTINES_TESTING_SINGLE_MODULE) import Phantom.Coroutines; diff --git a/Phantom.Coroutines.Test/detail/awaiters.h b/Phantom.Coroutines.Test/detail/awaiters.h index 785c0f3..2984b5e 100644 --- a/Phantom.Coroutines.Test/detail/awaiters.h +++ b/Phantom.Coroutines.Test/detail/awaiters.h @@ -1,10 +1,10 @@ -#include -#include #if defined(PHANTOM_COROUTINES_TESTING_SINGLE_MODULE) import Phantom.Coroutines; #elif defined(PHANTOM_COROUTINES_TESTING_MODULES) import Phantom.Coroutines.coroutine; #elif defined(PHANTOM_COROUTINES_TESTING_HEADERS) +#include +#include #include "Phantom.Coroutines/detail/coroutine.h" #endif diff --git a/Phantom.Coroutines.Test/detail/non_copyable_test.cpp b/Phantom.Coroutines.Test/detail/non_copyable_test.cpp index f473f94..f147ff3 100644 --- a/Phantom.Coroutines.Test/detail/non_copyable_test.cpp +++ b/Phantom.Coroutines.Test/detail/non_copyable_test.cpp @@ -1,3 +1,5 @@ +#include +#include #if defined(PHANTOM_COROUTINES_TESTING_SINGLE_MODULE) import Phantom.Coroutines; #elif defined(PHANTOM_COROUTINES_TESTING_MODULES) @@ -5,8 +7,6 @@ import Phantom.Coroutines.non_copyable; #elif defined(PHANTOM_COROUTINES_TESTING_HEADERS) #include "Phantom.Coroutines/detail/non_copyable.h" #endif -#include -#include namespace { diff --git a/Phantom.Coroutines.Test/reusable_task_test.cpp b/Phantom.Coroutines.Test/reusable_task_test.cpp index fbb4490..81dd144 100644 --- a/Phantom.Coroutines.Test/reusable_task_test.cpp +++ b/Phantom.Coroutines.Test/reusable_task_test.cpp @@ -1,6 +1,7 @@ #include #include #include +#include "async_test.h" #if defined(PHANTOM_COROUTINES_TESTING_SINGLE_MODULE) import Phantom.Coroutines; import Phantom.Coroutines.Test.lifetime_tracker; @@ -28,7 +29,6 @@ import Phantom.Coroutines.Test.pmr_task; #include "lifetime_tracker.h" #include "pmr_task.h" #endif -#include "async_test.h" using namespace Phantom::Coroutines; using namespace Phantom::Coroutines::detail; diff --git a/Phantom.Coroutines.Test/type_traits_test.cpp b/Phantom.Coroutines.Test/type_traits_test.cpp index e1ad511..478a989 100644 --- a/Phantom.Coroutines.Test/type_traits_test.cpp +++ b/Phantom.Coroutines.Test/type_traits_test.cpp @@ -1,4 +1,6 @@ #include +#include +#include #include "Phantom.Coroutines/detail/config_macros.h" #if defined(PHANTOM_COROUTINES_TESTING_SINGLE_MODULE) import Phantom.Coroutines; @@ -9,8 +11,6 @@ import Phantom.Coroutines.value_awaiter; #include "Phantom.Coroutines/type_traits.h" #include "Phantom.Coroutines/value_awaiter.h" #endif -#include -#include #include "detail/awaiters.h" PHANTOM_COROUTINES_PUSH_DISABLE_INTERNAL_LINKAGE_WARNING() diff --git a/Phantom.Coroutines/include/Phantom.Coroutines/awaiter_wrapper.h b/Phantom.Coroutines/include/Phantom.Coroutines/awaiter_wrapper.h index 75d0156..0650b9a 100644 --- a/Phantom.Coroutines/include/Phantom.Coroutines/awaiter_wrapper.h +++ b/Phantom.Coroutines/include/Phantom.Coroutines/awaiter_wrapper.h @@ -201,15 +201,14 @@ template< using awaiter_wrapper::awaiter_wrapper_storage::awaiter_wrapper_storage; public: - decltype(auto) awaiter( - this auto& self) + decltype(auto) awaiter() { - if (!self.awaiter_wrapper::m_awaiter) + if (!m_awaiter) { - self.awaiter_wrapper::m_awaiter.emplace( - self.awaiter_wrapper::get_awaiter_lambda()); + m_awaiter.emplace( + get_awaiter_lambda()); } - return self.awaiter_wrapper::m_awaiter->awaitable(); + return m_awaiter->awaitable(); } decltype(auto) handle( diff --git a/Phantom.Coroutines/include/Phantom.Coroutines/contextual_promise.h b/Phantom.Coroutines/include/Phantom.Coroutines/contextual_promise.h index efab0cb..ffc3005 100644 --- a/Phantom.Coroutines/include/Phantom.Coroutines/contextual_promise.h +++ b/Phantom.Coroutines/include/Phantom.Coroutines/contextual_promise.h @@ -31,6 +31,7 @@ template< : public derived_promise { + using derived_promise = derived_promise; struct DoNotEnterOnResume {}; struct DoNotLeaveOnSuspend {}; struct DoEnterOnResume {}; @@ -45,8 +46,9 @@ public derived_promise public awaiter_wrapper, public extensible_promise_handle { + using awaiter_wrapper = awaiter_wrapper; + using extensible_promise_handle = extensible_promise_handle; bool m_bSuspended = false; - public: contextual_promise_awaiter( Enter, @@ -54,33 +56,37 @@ public derived_promise Promise& promise, std::invocable<> auto&& awaitableFunc ) : - awaiter_wrapper{ std::forward(awaitableFunc) }, - extensible_promise_handle{ promise } + awaiter_wrapper{ std::forward(awaitableFunc) }, + extensible_promise_handle{ promise } {} bool await_ready( this auto& self, auto&&... args ) noexcept( - noexcept(self.awaiter().await_ready(std::forward(args)...)) + noexcept(self.awaiter_wrapper::awaiter().await_ready( + std::forward(args)...)) ) { - return self.awaiter().await_ready(std::forward(args)...); + return self.awaiter_wrapper::awaiter().await_ready( + std::forward(args)...); } auto await_suspend( this auto& self, auto&&... args ) noexcept( - noexcept(self.awaiter().await_suspend(std::forward(args)...)) + noexcept(self.awaiter_wrapper::awaiter().await_suspend( + std::forward(args)...)) ) { self.m_bSuspended = true; if (std::same_as) { - self.template extensible_promise_handle::promise().leave(); + self.extensible_promise_handle::promise().leave(); } - return self.awaiter().await_suspend(std::forward(args)...); + return self.awaiter_wrapper::awaiter().await_suspend( + std::forward(args)...); } auto await_resume( @@ -96,10 +102,10 @@ public derived_promise { if (self.m_bSuspended) { - self.template extensible_promise_handle::promise().enter(); + self.extensible_promise_handle::promise().enter(); } } - return self.awaiter().await_resume(std::forward(args)...); + return self.awaiter_wrapper::awaiter().await_resume(std::forward(args)...); } }; @@ -118,12 +124,12 @@ public derived_promise > contextual_promise_awaiter(Enter, Leave, Promise&, AwaitableFunc&&) -> contextual_promise_awaiter>; public: - using contextual_promise::derived_promise::derived_promise; + using derived_promise::derived_promise; auto initial_suspend( this auto& self ) noexcept(noexcept( - self.derived_promise::initial_suspend() + self.derived_promise::initial_suspend() )) { static_assert(is_contextual_promise); @@ -135,7 +141,7 @@ public derived_promise self, [&]() -> decltype(auto) { - return self.template derived_promise::initial_suspend(); + return self.derived_promise::initial_suspend(); } }; } @@ -153,7 +159,7 @@ public derived_promise self, [&]() noexcept -> decltype(auto) { - return self.template derived_promise::final_suspend(); + return self.derived_promise::final_suspend(); } }; } @@ -170,11 +176,11 @@ public derived_promise DoLeaveOnSuspend{}, self, [&]() noexcept(noexcept( - self.template derived_promise::await_transform( + self.derived_promise::await_transform( std::forward(awaitable)) )) -> decltype(auto) { - return self.template derived_promise::await_transform( + return self.derived_promise::await_transform( std::forward(awaitable)); } }; diff --git a/Phantom.Coroutines/include/Phantom.Coroutines/type_traits.h b/Phantom.Coroutines/include/Phantom.Coroutines/type_traits.h index 717bd01..0ad30a3 100644 --- a/Phantom.Coroutines/include/Phantom.Coroutines/type_traits.h +++ b/Phantom.Coroutines/include/Phantom.Coroutines/type_traits.h @@ -529,7 +529,7 @@ template< struct has_yield_value_conflicted_name { - using yield_value = int; + static int yield_value; }; PHANTOM_COROUTINES_MODULE_EXPORT From 6b21bdbc541e2a5110083a9dcc4c44ed7f70e3e8 Mon Sep 17 00:00:00 2001 From: Joshua Rowe Date: Thu, 18 Dec 2025 20:44:15 -0800 Subject: [PATCH 08/46] Working test for traces_create_and_destroy_promise --- Phantom.Coroutines.Modules/CMakeLists.txt | 2 +- .../Phantom.Coroutines.ixx | 2 +- Phantom.Coroutines.Modules/text_tracing.ixx | 2 + Phantom.Coroutines.Modules/tracing.ixx | 2 + Phantom.Coroutines.Test/CMakeLists.txt | 2 +- Phantom.Coroutines.Test/tracing_test.cpp | 159 +- .../include/Phantom.Coroutines/text_tracing.h | 58 + .../include/Phantom.Coroutines/tracing.h | 1643 ++++++++--------- 8 files changed, 971 insertions(+), 899 deletions(-) diff --git a/Phantom.Coroutines.Modules/CMakeLists.txt b/Phantom.Coroutines.Modules/CMakeLists.txt index 240f6dc..7842f24 100644 --- a/Phantom.Coroutines.Modules/CMakeLists.txt +++ b/Phantom.Coroutines.Modules/CMakeLists.txt @@ -76,7 +76,7 @@ target_sources( "sync_wait.ixx" "tagged_pointer.ixx" "task.ixx" - "text_tracing.ixx" +# "text_tracing.ixx" "thread_local_context.ixx" "thread_local_contextual_promise.ixx" "thread_local_storage.ixx" diff --git a/Phantom.Coroutines.Modules/Phantom.Coroutines.ixx b/Phantom.Coroutines.Modules/Phantom.Coroutines.ixx index 78f290b..0400313 100644 --- a/Phantom.Coroutines.Modules/Phantom.Coroutines.ixx +++ b/Phantom.Coroutines.Modules/Phantom.Coroutines.ixx @@ -59,7 +59,7 @@ export import Phantom.Coroutines.thread_local_context; export import Phantom.Coroutines.thread_local_contextual_promise; export import Phantom.Coroutines.thread_local_storage; export import Phantom.Coroutines.thread_pool_scheduler; -//export import Phantom.Coroutines.tracing; +export import Phantom.Coroutines.tracing; export import Phantom.Coroutines.type_traits; export import Phantom.Coroutines.value_awaiter; export import Phantom.Coroutines.variant_result_storage; diff --git a/Phantom.Coroutines.Modules/text_tracing.ixx b/Phantom.Coroutines.Modules/text_tracing.ixx index 1eaacc7..fc7f762 100644 --- a/Phantom.Coroutines.Modules/text_tracing.ixx +++ b/Phantom.Coroutines.Modules/text_tracing.ixx @@ -1,4 +1,6 @@ module; +#include #include "Phantom.Coroutines/detail/config_macros.h" export module Phantom.Coroutines.text_tracing; +import Phantom.Coroutines.tracing; #include "Phantom.Coroutines/text_tracing.h" diff --git a/Phantom.Coroutines.Modules/tracing.ixx b/Phantom.Coroutines.Modules/tracing.ixx index 18961db..c173423 100644 --- a/Phantom.Coroutines.Modules/tracing.ixx +++ b/Phantom.Coroutines.Modules/tracing.ixx @@ -4,6 +4,8 @@ module; #include #include #include +#include +#include #include #include "Phantom.Coroutines/detail/config_macros.h" export module Phantom.Coroutines.tracing; diff --git a/Phantom.Coroutines.Test/CMakeLists.txt b/Phantom.Coroutines.Test/CMakeLists.txt index 07600a6..fbcec5e 100644 --- a/Phantom.Coroutines.Test/CMakeLists.txt +++ b/Phantom.Coroutines.Test/CMakeLists.txt @@ -48,7 +48,7 @@ set( "sync_wait_test.cpp" "tagged_pointer_test.cpp" "task_test.cpp" - "text_tracing_test.cpp" +# "text_tracing_test.cpp" "thread_local_contextual_promise_test.cpp" "thread_local_storage_test.cpp" "thread_pool_scheduler_test.cpp" diff --git a/Phantom.Coroutines.Test/tracing_test.cpp b/Phantom.Coroutines.Test/tracing_test.cpp index aa16a74..708948b 100644 --- a/Phantom.Coroutines.Test/tracing_test.cpp +++ b/Phantom.Coroutines.Test/tracing_test.cpp @@ -1,4 +1,5 @@ #include "async_test.h" +#include #include #include "Phantom.Coroutines/detail/config_macros.h" #if defined(PHANTOM_COROUTINES_TESTING_SINGLE_MODULE) @@ -53,27 +54,27 @@ struct tracing_tests : testing::Test > struct trace_sink { - traced_events_checker& checker; + const traced_events_checker& checker; trace_sink( - trace_sink& other - ) : checker{ other.checker } + auto&... args + ) : + checker(get(std::tie(args...))) { } - + trace_sink( const trace_sink& other ) : checker{ other.checker } - { } + { + } trace_sink( - auto&... args - ) : - checker(get(std::tie(args...))) + trace_sink& other + ) : checker{ other.checker } { - } - void operator()(const auto& traceEvent) + void operator()(const auto& traceEvent) const { if (Filter{}(traceEvent)) { @@ -99,15 +100,26 @@ struct tracing_tests : testing::Test template< typename EventType > - auto CastEventType( + const EventType& CastEventType( const std::any& anyEvent) { // Get human-readable string for debugging purposes. - std::string expectedTypeName = typeid(const EventType*).name(); + using expected_event_type = const EventType*; + std::string expectedTypeName = typeid(expected_event_type).name(); std::string actualTypeName = anyEvent.type().name(); EXPECT_EQ(expectedTypeName, actualTypeName); - return std::any_cast(anyEvent); + return *std::any_cast(anyEvent); + } + + template< + typename ExpectedEventType + > + const ExpectedEventType& CastEventType( + const std::any& anyEvent, + [[maybe_unused]] const ExpectedEventType& expectedEvent) + { + return CastEventType(anyEvent); } void ExpectIsInitialSuspend( @@ -332,6 +344,32 @@ struct tracing_tests : testing::Test catch(Exception) { } } + + template< + typename expected_event_type + > + bool compare_events( + const expected_event_type& expectedEvent, + const std::any& actualAny + ) + { + auto& actualEvent = CastEventType(actualAny, expectedEvent); + + if (expectedEvent.EventType != actualEvent.EventType) + { + return false; + } + + return true; + } + + void expect_events_equal( + const auto& expectedEvent, + const std::any& actualAny + ) + { + EXPECT_TRUE(compare_events(expectedEvent, actualAny)); + } }; } // namespace Phantom::Coroutines::tracing @@ -372,9 +410,94 @@ struct coroutine_traits< namespace Phantom::Coroutines::tracing { -ASYNC_TEST_F(tracing_tests, traces_basic_events_of_task) +ASYNC_TEST_F(tracing_tests, traces_create_and_destroy_promise) +{ + auto filter = filters::event_type_filter< + events::create_promise, + events::destroy_promise + >; + + auto taskLambda = []( + this auto& self, + traced_events_checker& eventsChecker, + auto filter + ) -> test_traced_task<> + { + co_return; + }; + using taskLambda_type = decltype(taskLambda); + + using expected_promise_type = std::coroutine_traits< + test_traced_task<>, + traced_events_checker&, + decltype(filter) + >::promise_type; + + expected_promise_type* expectedPromise = nullptr; + + auto eventIndex = 0; + traced_events_checker tracedEventsChecker; + tracedEventsChecker = { [&](const std::any& actualEventAny) + { + ++eventIndex; + auto checkingIndex = 0; + if (eventIndex == ++checkingIndex) + { + auto expectedEventData = events::event_data + { + .Promise = static_cast(nullptr), + .PromiseCreationArguments = events::make_arguments( + taskLambda, + tracedEventsChecker, + filter), + }; + + auto expectedEvent = basic_event( + std::source_location(), + events::create_promise{}, + expectedEventData + ); + + auto& actualEvent = CastEventType(actualEventAny, expectedEvent); + + expectedPromise = actualEvent.EventData.Promise; + expectedEvent.EventData.Promise = expectedPromise; + + expect_events_equal( + expectedEvent, + actualEventAny + ); + } + else if(eventIndex == ++checkingIndex) + { + auto expectedEventData = events::event_data + { + .Promise = expectedPromise, + }; + + auto expectedEvent = basic_event( + std::source_location(), + events::destroy_promise{}, + expectedEventData + ); + + expect_events_equal( + expectedEvent, + actualEventAny + ); + } + else + { + EXPECT_FALSE(true); + } + } }; + + co_await taskLambda(tracedEventsChecker, filter); +} + +#if 0 +ASYNC_TEST_F(tracing_tests, traces_initial_suspend) { - PHANTOM_COROUTINES_MSVC_PUSH_DISABLE_WARNING(4702) auto taskLambda = []( traced_events_checker& eventsChecker, std::string inputArgument @@ -382,7 +505,6 @@ ASYNC_TEST_F(tracing_tests, traces_basic_events_of_task) { co_return; }; - PHANTOM_COROUTINES_MSVC_POP_WARNINGS() using expected_promise_type = coroutine_function_traits::promise_type; expected_promise_type* expectedPromise = nullptr; @@ -390,7 +512,7 @@ ASYNC_TEST_F(tracing_tests, traces_basic_events_of_task) using initialSuspendAwaiter = traced_awaiter< trace_sink, std::suspend_always, - initial_suspend_awaiter + detail::initial_suspend_awaiter_type >; initialSuspendAwaiter* expectedInitialSuspendAwaiter = nullptr; @@ -398,7 +520,7 @@ ASYNC_TEST_F(tracing_tests, traces_basic_events_of_task) using finalSuspendAwaiter = traced_awaiter< trace_sink, final_suspend_transfer, - final_suspend_awaiter + detail::final_suspend_awaiter_type >; finalSuspendAwaiter* expectedFinalSuspendAwaiter = nullptr; @@ -921,5 +1043,6 @@ ASYNC_TEST_F(tracing_tests, traces_co_await_events_await_suspend_void_result) EXPECT_EQ(6, eventIndex); co_return; } +#endif } diff --git a/Phantom.Coroutines/include/Phantom.Coroutines/text_tracing.h b/Phantom.Coroutines/include/Phantom.Coroutines/text_tracing.h index 9036be7..88cd903 100644 --- a/Phantom.Coroutines/include/Phantom.Coroutines/text_tracing.h +++ b/Phantom.Coroutines/include/Phantom.Coroutines/text_tracing.h @@ -1,6 +1,8 @@ #ifndef PHANTOM_COROUTINES_INCLUDE_TEXT_TRACING_H #define PHANTOM_COROUTINES_INCLUDE_TEXT_TRACING_H #ifndef PHANTOM_COROUTINES_COMPILING_MODULES +#include +#include "tracing.h" #endif static_assert(PHANTOM_COROUTINES_IS_CONFIGURED); @@ -12,7 +14,63 @@ namespace Phantom::Coroutines namespace tracing { +template< + typename Value, + typename Destination +> +struct formatted_text_trace_value +{ + const Value& value; +}; + +template< + typename Value, + typename Destination, + template typename Formatter +> +struct formatted_text_trace_value_formatter + : + Formatter +{ + using formatter_type = Formatter; + + auto format( + const formatted_text_trace_value& formattedValue, + auto& context + ) const + { + return formatter_type::format(formattedValue.value, context); + } + + using formatter_type::format; + using formatter_type::parse; +}; + +template< + typename TextTraceSink, + typename Destination = TextTraceSink +> +struct text_trace_sink +{ + TextTraceSink m_textTraceSink; + + template< + typename Event + > + auto operator()( + const Event& event + ) + { + m_textTraceSink( + formatted_text_trace_value + { + .value = event + } + ); + } +}; +// namespace [Phantom::Coroutines::]tracing } // namespace Phantom::Coroutines diff --git a/Phantom.Coroutines/include/Phantom.Coroutines/tracing.h b/Phantom.Coroutines/include/Phantom.Coroutines/tracing.h index 084589a..2345dd3 100644 --- a/Phantom.Coroutines/include/Phantom.Coroutines/tracing.h +++ b/Phantom.Coroutines/include/Phantom.Coroutines/tracing.h @@ -4,8 +4,10 @@ #include #include #include -#include #include +#include +#include +#include #include #include "detail/config_macros.h" #include "detail/coroutine.h" @@ -22,6 +24,11 @@ namespace Phantom::Coroutines namespace tracing { + +template< + typename TraceSink +> concept is_trace_sink = true; + namespace events { PHANTOM_COROUTINES_MODULE_EXPORT @@ -44,590 +51,541 @@ template< typename Awaiter > concept is_traced_promise_co_await_awaiter = std::remove_cvref_t::is_traced_promise_co_await_awaiter; -// Base for all tracing events. -PHANTOM_COROUTINES_MODULE_EXPORT -struct event -{ - std::source_location SourceLocation; - - // Two event objects compare equal even if the source - // location is not equal, because SourceLocation - // is neither comparable nor reliable. - friend auto operator<=>(const event&, const event&) - { - return 0 <=> 0; - } - - friend bool operator==(const event&, const event&) - { - return true; - } -}; - -// Base for all tracing events that include arguments PHANTOM_COROUTINES_MODULE_EXPORT template< - typename ... Args -> struct arguments -{ - using arguments_type = std::tuple; - arguments_type Arguments; - - friend auto operator<=>(const arguments&, const arguments&) = default; -}; - -// Base for all tracing events that refer to a promise. -PHANTOM_COROUTINES_MODULE_EXPORT -template< - typename TPromise + typename TValue > -struct promise_event - : - event +struct value { - TPromise* Promise; + using value_type = TValue; + const value_type& Value; - friend auto operator<=>(const promise_event&, const promise_event&) = default; + friend auto operator<=>(const value&, const value&) = default; }; -// Trace a promise creation PHANTOM_COROUTINES_MODULE_EXPORT template< - typename TPromise, typename ... Args > -struct create_promise - : - promise_event, - arguments -{ - friend auto operator<=>(const create_promise&, const create_promise&) = default; -}; +using arguments = value>; -// Trace a promise destruction PHANTOM_COROUTINES_MODULE_EXPORT -template< - typename TPromise -> -struct destroy_promise - : - promise_event +constexpr auto make_arguments( + const auto&... args +) { - friend auto operator<=>(const destroy_promise&, const destroy_promise&) = default; -}; + return value{ std::tie(std::as_const(args)...) }; +} -// Base for all tracing events that refer to a result value, -// either from an awaiter or from the promise itself. PHANTOM_COROUTINES_MODULE_EXPORT template< - typename TResult + typename Label > -struct result_event +struct event_type { - static constexpr bool is_void_result = false; - using result_type = TResult; - using result_reference_type = const TResult&; + event_type() = default; + event_type( + Label + ) + { + } - result_reference_type Result; + using label_type = Label; + + template< + typename Label1, + typename Label2 + > + friend consteval bool operator==( + const event_type&, + const event_type&); - friend auto operator<=>(const result_event&, const result_event&) = default; + template< + typename Label1, + typename Label2 + > + friend consteval bool operator!=( + const event_type&, + const event_type&); }; -// Base for all tracing events that refer to a void result value, -// either from an awaiter or from the promise itself. PHANTOM_COROUTINES_MODULE_EXPORT template< + typename Label1, + typename Label2 > -struct result_event +consteval bool operator==( + const event_type&, + const event_type&) { - static constexpr bool is_void_result = true; - using result_type = void; - - friend auto operator<=>(const result_event&, const result_event&) = default; -}; - -PHANTOM_COROUTINES_MODULE_EXPORT -template< - typename T -> concept is_void_result_event = T::is_void_result; + return std::same_as; +} PHANTOM_COROUTINES_MODULE_EXPORT template< - typename TPromise, - typename TResult + typename Label1, + typename Label2 > -struct promise_result_event - : - promise_event, - result_event -{ - friend auto operator<=>(const promise_result_event&, const promise_result_event&) = default; -}; - -// Base for all tracing events that refer to an exception, -// either from an awaiter or from the promise itself. -PHANTOM_COROUTINES_MODULE_EXPORT -struct exception_event +consteval bool operator!=( + const event_type&, + const event_type&) { - std::exception_ptr Exception; -}; + return !std::same_as; +} -// Base for all tracing events that refer to an awaiter. PHANTOM_COROUTINES_MODULE_EXPORT template< - typename TAwaiter + typename EventType, + typename EventData, + template typename ... Accessors > -struct awaiter_event - : event +struct event : + Accessors>... { - static constexpr bool is_initial_suspend = is_traced_promise_initial_suspend_awaiter; - static constexpr bool is_final_suspend = is_traced_promise_final_suspend_awaiter; - static constexpr bool is_co_yield = is_traced_promise_co_yield_awaiter; - static constexpr bool is_co_await = is_traced_promise_co_await_awaiter; - - TAwaiter* Awaiter; - - friend auto operator<=>(const awaiter_event&, const awaiter_event&) = default; + using event_type = event_type; + using event_data_type = EventData; + + std::source_location SourceLocation; + event_type EventType; + event_data_type EventData; }; -// Trace entry to an awaiter await_ready method. - -PHANTOM_COROUTINES_MODULE_EXPORT -template< - typename TAwaiter, - typename ... Arguments -> -struct await_ready_event - : - awaiter_event, - arguments +struct empty_value { - friend auto operator<=>(const await_ready_event&, const await_ready_event&) = default; + using value_type = void; + static constexpr void* Value = nullptr; + + friend auto operator<=>(const empty_value&, const empty_value&) = default; }; -PHANTOM_COROUTINES_MODULE_EXPORT -template< - typename TAwaiter, - typename ... Arguments -> -struct await_ready_begin - : - await_ready_event +struct empty_arguments_value { - friend auto operator<=>(const await_ready_begin&, const await_ready_begin&) = default; + using value_type = std::tuple<>; + static constexpr value_type Value = {}; + + friend auto operator<=>(const empty_arguments_value&, const empty_arguments_value&) = default; }; -// Trace result from an awaiter await_ready method. -PHANTOM_COROUTINES_MODULE_EXPORT +constexpr empty_value empty{}; +constexpr empty_arguments_value empty_arguments{ }; + template< - typename TAwaiter, - typename TResult, - typename ... Arguments -> -struct await_ready_result - : - await_ready_event, - result_event -{ - friend auto operator<=>(const await_ready_result&, const await_ready_result&) = default; -}; + typename T +> concept is_empty = +std::same_as +|| +std::same_as +|| +std::same_as; -// Trace exception from an awaiter await_ready method. -PHANTOM_COROUTINES_MODULE_EXPORT template< - typename TAwaiter, - typename ... Arguments -> -struct await_ready_exception - : - await_ready_event, - exception_event -{ - friend auto operator<=>(const await_ready_exception&, const await_ready_exception&) = default; -}; + typename T +> concept is_not_empty = !is_empty; -PHANTOM_COROUTINES_MODULE_EXPORT template< - typename TAwaiter + typename Event > -struct await_ready_events +struct PromiseAccessor { - await_ready_events(const TAwaiter&) {} + static constexpr bool has_promise = requires(Event self) + { + { self.Data.Promise } -> is_not_empty; + }; - template< - typename ... Arguments - > - using begin_event = await_ready_begin; + auto* promise(this auto& self) + { + if constexpr (has_promise) + { + return self.Data.Promise; + } + else + { + return ∅ + } + } - template< - typename Result, - typename ... Arguments - > using result_event = await_ready_result; + static constexpr bool has_promise_creation_arguments = requires(Event self) + { + { self.Data.PromiseCreationArguments } -> is_not_empty; + }; - template< - typename ... Arguments - > - using exception_event = await_ready_exception; + auto* promise_creation_arguments( + this auto& self + ) + { + if constexpr (has_promise_creation_arguments) + { + return self.Data.PromiseCreationArguments; + } + else + { + return &empty_arguments; + } + } }; -PHANTOM_COROUTINES_MODULE_EXPORT template< - typename TAwaiter, - typename ... Arguments + typename Event > -struct await_suspend_event - : - awaiter_event, - arguments +struct AwaiterAccessor { - friend auto operator<=>(const await_suspend_event&, const await_suspend_event&) = default; -}; + static constexpr bool has_awaiter = requires(Event self) + { + { self.Data.Awaiter } -> is_not_empty; + }; -// Trace entry from an awaiter await_suspend method. -PHANTOM_COROUTINES_MODULE_EXPORT -template< - typename TAwaiter, - typename ... Arguments -> -struct await_suspend_begin - : - await_suspend_event -{ - friend auto operator<=>(const await_suspend_begin&, const await_suspend_begin&) = default; + auto* awaiter(this auto& self) + { + if constexpr (has_awaiter) + { + return self.Data.Awaiter; + } + else + { + return ∅ + } + } }; -// Trace result from an await_suspend method. -// Note that it is _highly likely_ that the TAwaiter parameter -// to await_suspend_result is not valid anymore, since -// the coroutine may have been destroyed if the await_suspend -// allowed resuming the coroutine either in a nested fashion -// or on another thread. In these cases, the awaiter object -// is likely a temporary object that has been destroyed, -// and should likely not be inspected. -// For these reasons, the await_suspend_result event -// might be emitted _after_ the await_resume events. -PHANTOM_COROUTINES_MODULE_EXPORT template< - typename TAwaiter, - typename TResult, - typename ... Arguments + typename Event > -struct await_suspend_result - : - await_suspend_event, - result_event +struct MethodArgumentsAccessor { - friend auto operator<=>(const await_suspend_result&, const await_suspend_result&) = default; -}; + static constexpr bool has_method_arguments = requires(Event self) + { + { self.Data.MethodArguments } -> is_not_empty; + }; -// Trace exception from an awaiter await_suspend method. -PHANTOM_COROUTINES_MODULE_EXPORT -template< - typename TAwaiter, - typename ... Arguments -> -struct await_suspend_exception - : - await_suspend_event, - exception_event -{ - friend auto operator<=>(const await_suspend_exception&, const await_suspend_exception&) = default; + auto* method_arguments(this auto& self) + { + if constexpr (has_method_arguments) + { + return self.Data.MethodArguments; + } + else + { + return &empty_arguments; + } + } }; -PHANTOM_COROUTINES_MODULE_EXPORT template< - typename TAwaiter + typename Event > -struct await_suspend_events +struct MethodResultAccessor { - await_suspend_events(const TAwaiter&) {} - - template< - typename ... Arguments - > using begin_event = await_suspend_begin; - - template< - typename Result, - typename ... Arguments - > using result_event = await_suspend_result; + static constexpr bool has_method_result = requires(Event self) + { + { self.Data.MethodResult } -> is_not_empty; + }; - template< - typename ... Arguments - > using exception_event = await_suspend_exception; + auto* method_result(this auto& self) + { + if constexpr (has_method_result) + { + return self.Data.MethodResult; + } + else + { + return ∅ + } + } }; -// Base for all tracing events that refer to an awaiter await_ready method. -PHANTOM_COROUTINES_MODULE_EXPORT template< - typename TAwaiter, - typename ... Arguments + typename Event > -struct await_resume_event - : - awaiter_event, - arguments +struct MethodExceptionAccessor { - friend auto operator<=>(const await_resume_event&, const await_resume_event&) = default; -}; + static constexpr bool has_method_exception = requires(Event self) + { + { self.Data.MethodException } -> is_not_empty; + }; -// Trace entry to an awaiter await_resume method. -PHANTOM_COROUTINES_MODULE_EXPORT -template< - typename TAwaiter, - typename ... Arguments -> -struct await_resume_begin - : - await_resume_event -{ - friend auto operator<=>(const await_resume_begin&, const await_resume_begin&) = default; + auto* method_exception(this auto& self) + { + if constexpr (has_method_exception) + { + return self.Data.MethodException; + } + else + { + return ∅ + } + } }; -// Trace result from an awaiter await_resume method. -PHANTOM_COROUTINES_MODULE_EXPORT template< - typename TAwaiter, - typename TResult, - typename ... Arguments + typename Event > -struct await_resume_result - : - await_resume_event, - result_event +struct UnhandledExceptionAccessor { - friend auto operator<=>(const await_resume_result&, const await_resume_result&) = default; -}; + static constexpr bool has_unhandled_exception = requires(Event self) + { + { self.Data.UnhandledException } -> is_not_empty; + }; -// Trace exception from an awaiter await_resume method. -PHANTOM_COROUTINES_MODULE_EXPORT -template< - typename TAwaiter, - typename ... Arguments -> -struct await_resume_exception - : - await_resume_event, - exception_event -{ - friend auto operator<=>(const await_resume_exception&, const await_resume_exception&) = default; + auto* unhandled_exception(this auto& self) + { + if constexpr (has_unhandled_exception) + { + return self.Data.UnhandledException; + } + else + { + return ∅ + } + } }; -PHANTOM_COROUTINES_MODULE_EXPORT template< - typename TAwaiter + typename Event > -struct await_resume_events +struct YieldValueAccessor { - await_resume_events(TAwaiter&) {} - - template< - typename ... Arguments - > using begin_event = await_resume_begin; - - template< - typename Result, - typename ... Arguments - > using result_event = await_resume_result; + static constexpr bool has_yield_value = requires(Event self) + { + { self.Data.YieldValue } -> is_not_empty; + }; - template< - typename ... Arguments - > using exception_event = await_resume_exception; + auto* yield_value(this auto& self) + { + if constexpr (has_yield_value) + { + return self.Data.YieldValue; + } + else + { + return ∅ + } + } }; -// Base for promise events that return an exception -PHANTOM_COROUTINES_MODULE_EXPORT template< - typename TPromise + typename Event > -struct promise_exception_event - : - promise_event, - exception_event +struct ReturnValueAccessor { - friend auto operator<=>(const promise_exception_event&, const promise_exception_event&) = default; -}; + static constexpr bool has_return_value = requires(Event self) + { + { self.Data.ReturnValue } -> is_not_empty; + }; -// Trace unhandled_exception on a promise. -PHANTOM_COROUTINES_MODULE_EXPORT -template< - typename TPromise -> struct unhandled_exception - : - promise_exception_event -{ - friend auto operator<=>(const unhandled_exception&, const unhandled_exception&) = default; + auto* return_value(this auto& self) + { + if constexpr (has_return_value) + { + return self.Data.ReturnValue; + } + else + { + return ∅ + } + } }; -// Trace entry to an return_void method. PHANTOM_COROUTINES_MODULE_EXPORT -template< - typename TPromise -> struct return_void_begin - : - promise_event -{ - friend auto operator<=>(const return_void_begin&, const return_void_begin&) = default; -}; - -// Trace result from a return_void method. +struct create_promise {}; PHANTOM_COROUTINES_MODULE_EXPORT -template< - typename TPromise, - typename TResult -> struct return_void_result - : - promise_result_event -{ - friend auto operator<=>(const return_void_result&, const return_void_result&) = default; -}; - -// Trace exception from a return_void method. +struct destroy_promise {}; PHANTOM_COROUTINES_MODULE_EXPORT -template< - typename TPromise -> struct return_void_exception - : - promise_exception_event -{ - friend auto operator<=>(const return_void_exception&, const return_void_exception&) = default; -}; - -// Trace entry to a return_value method. +struct await_ready_begin {}; PHANTOM_COROUTINES_MODULE_EXPORT -template< - typename TPromise, - typename Argument -> struct return_value_begin - : - promise_result_event -{ - friend auto operator<=>(const return_value_begin&, const return_value_begin&) = default; -}; - -// Trace result from a return_value method. +struct await_ready_result {}; PHANTOM_COROUTINES_MODULE_EXPORT -template< - typename TPromise, - typename TResult -> struct return_value_result - : - promise_result_event -{ - friend auto operator<=>(const return_value_result&, const return_value_result&) = default; -}; - -// Trace exception from a return_value method. +struct await_ready_exception {}; PHANTOM_COROUTINES_MODULE_EXPORT -template< - typename TPromise -> struct return_value_exception - : - promise_exception_event -{ - friend auto operator<=>(const return_value_exception&, const return_value_exception&) = default; -}; - +struct await_suspend_begin {}; PHANTOM_COROUTINES_MODULE_EXPORT -template< - typename Argument -> -struct return_value_argument_events -{ - template< - typename TPromise - > using return_value_begin = events::return_value_begin; - - template< - typename TPromise, - typename TResult - > using return_value_result = events::return_value_result; - - template< - typename TPromise - > using return_value_exception = events::return_value_exception; -}; - -// Trace entry to a yield_value method. -template< - typename TPromise, - typename Argument -> struct yield_value_event - : - promise_event, - arguments -{ -}; +struct await_suspend_result {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct await_suspend_exception {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct await_resume_begin {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct await_resume_result {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct await_resume_exception {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct await_transform_begin {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct await_transform_result {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct await_transform_exception {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct initial_suspend_begin {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct initial_suspend_result {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct initial_suspend_exception {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct final_suspend_begin {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct final_suspend_result {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct final_suspend_exception {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct unhandled_exception_begin {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct unhandled_exception_result {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct unhandled_exception_exception {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct get_return_object_begin {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct get_return_object_result {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct get_return_object_exception {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct yield_value_begin {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct yield_value_result {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct yield_value_exception {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct return_value_begin {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct return_value_result {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct return_value_exception {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct return_void_begin {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct return_void_result {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct return_void_exception {}; PHANTOM_COROUTINES_MODULE_EXPORT template< - typename TPromise, - typename Argument -> struct yield_value_begin - : - yield_value_event -{ - friend auto operator<=>(const yield_value_begin&, const yield_value_begin&) = default; -}; + typename Event, + typename EventType +> concept is_event_type = std::same_as; -// Trace result from a yield_value method. PHANTOM_COROUTINES_MODULE_EXPORT template< - typename TPromise, - typename Argument, - typename TResult -> struct yield_value_result - : - yield_value_event, - result_event + typename TPromise = empty_value, + typename TPromiseCreationArguments = empty_arguments_value, + typename TAwaiter = empty_value, + typename TMethodArguments = empty_arguments_value, + typename TMethodResult = empty_value, + typename TMethodException = empty_value, + typename TUnhandledException = empty_value, + typename TYieldValue = empty_value, + typename TReturnValue = empty_value +> +struct event_data { - friend auto operator<=>(const yield_value_result&, const yield_value_result&) = default; + [[no_unique_address]] TPromise Promise = empty; + [[no_unique_address]] TPromiseCreationArguments PromiseCreationArguments = empty_arguments; + [[no_unique_address]] TAwaiter Awaiter = empty; + [[no_unique_address]] TMethodArguments MethodArguments = empty_arguments; + [[no_unique_address]] TMethodResult MethodResult = empty; + [[no_unique_address]] TMethodException MethodException = empty; + [[no_unique_address]] TUnhandledException UnhandledException = empty; + [[no_unique_address]] TYieldValue YieldValue = empty; + [[no_unique_address]] TReturnValue ReturnValue = empty; + + friend auto operator<=>(const event_data&, const event_data&) = default; + + static constexpr auto combine( + const auto& lhs, + const auto& rhs + ) + { + if constexpr (is_empty>) + { + return lhs; + } + else + { + return rhs; + } + } + + auto with( + const auto& other + ) const + { + return events::event_data + { + .Promise = combine( + this->Promise, + other.Promise), + .PromiseCreationArguments = combine( + this->PromiseCreationArguments, + other.PromiseCreationArguments), + .Awaiter = combine( + this->Awaiter, + other.Awaiter), + .MethodArguments = combine( + this->MethodArguments, + other.MethodArguments), + .MethodResult = combine( + this->MethodResult, + other.MethodResult), + .MethodException = combine( + this->MethodException, + other.MethodException), + .UnhandledException = combine( + this->UnhandledException, + other.UnhandledException), + .YieldValue = combine( + this->YieldValue, + other.YieldValue), + .ReturnValue = combine( + this->ReturnValue, + other.ReturnValue), + }; + } }; -// Trace exception from a yeild_value method. -PHANTOM_COROUTINES_MODULE_EXPORT template< - typename TPromise, - typename Argument -> struct yield_value_exception - : - yield_value_event -{ - friend auto operator<=>(const yield_value_exception&, const yield_value_exception&) = default; -}; + typename EventType, + typename EventData +> +using basic_event_type = event< + EventType, + EventData, + PromiseAccessor, + AwaiterAccessor, + MethodArgumentsAccessor, + MethodResultAccessor, + MethodExceptionAccessor, + UnhandledExceptionAccessor, + YieldValueAccessor, + ReturnValueAccessor +>; PHANTOM_COROUTINES_MODULE_EXPORT template< - typename TPromise + typename EventType, + typename EventData > -struct yield_value_argument_events +auto basic_event( + const std::source_location& sourceLocation, + const EventType& eventType, + const EventData& eventData +) { - template< - typename ... Arguments - > using yield_value_begin = events::yield_value_begin; - - template< - typename TResult, - typename ... Arguments - > using yield_value_result = events::yield_value_result; - - template< - typename ... Arguments - > using yield_value_exception = events::yield_value_exception; -}; + return basic_event_type< + EventType, + EventData + > + { + .SourceLocation = sourceLocation, + .EventType = eventType, + .EventData = eventData, + }; +} // namespace events } +namespace detail +{ PHANTOM_COROUTINES_MODULE_EXPORT -template< - typename TraceSink -> concept is_trace_sink = std::invocable< - TraceSink, - events::event ->; - -PHANTOM_COROUTINES_MODULE_EXPORT -struct initial_suspend_awaiter +struct initial_suspend_awaiter_type { static constexpr bool is_traced_promise_initial_suspend_awaiter = true; static constexpr bool is_traced_promise_final_suspend_awaiter = false; @@ -636,7 +594,7 @@ struct initial_suspend_awaiter }; PHANTOM_COROUTINES_MODULE_EXPORT -struct final_suspend_awaiter +struct final_suspend_awaiter_type { static constexpr bool is_traced_promise_initial_suspend_awaiter = false; static constexpr bool is_traced_promise_final_suspend_awaiter = true; @@ -645,7 +603,7 @@ struct final_suspend_awaiter }; PHANTOM_COROUTINES_MODULE_EXPORT -struct co_yield_awaiter +struct co_yield_awaiter_type { static constexpr bool is_traced_promise_initial_suspend_awaiter = false; static constexpr bool is_traced_promise_final_suspend_awaiter = false; @@ -654,7 +612,7 @@ struct co_yield_awaiter }; PHANTOM_COROUTINES_MODULE_EXPORT -struct co_await_awaiter +struct co_await_awaiter_type { static constexpr bool is_traced_promise_initial_suspend_awaiter = false; static constexpr bool is_traced_promise_final_suspend_awaiter = false; @@ -662,10 +620,8 @@ struct co_await_awaiter static constexpr bool is_traced_promise_co_await_awaiter = true; }; -namespace detail -{ template< - typename TraceSink + is_trace_sink TraceSink > struct trace_sink_accessor { @@ -673,110 +629,128 @@ struct trace_sink_accessor auto& trace_sink() { - return m_traceSink; + return m_traceSink; + } +}; + +template< + typename BeginEventType, + typename ResultEventType, + typename ExceptionEventType +> +struct method_events_group +{ + method_events_group( + BeginEventType, + ResultEventType, + ExceptionEventType) + { } + + using begin_event_type = BeginEventType; + using result_event_type = ResultEventType; + using exception_event_type = ExceptionEventType; + + BeginEventType BeginEvent; + ResultEventType ResultEvent; + ExceptionEventType ExceptionEvent; +}; + +} // namespace detail + +auto call_traced_method( + const auto& traceSink, + auto events, + std::source_location sourceLocation, + const auto& baseEventData, + std::invocable<> auto call +) +{ + using result_type = std::invoke_result_t; + try + { + traceSink( + events::basic_event( + sourceLocation, + events.BeginEvent, + baseEventData)); + if constexpr (std::same_as) + { + call(); + traceSink( + events::basic_event( + sourceLocation, + events.ResultEvent, + baseEventData)); + } + else + { + decltype(auto) result = call(); + traceSink( + events::basic_event( + sourceLocation, + events.ResultEvent, + baseEventData.with( + events::event_data + { + .MethodResult = &result + }))); + return result; + } + } + catch (...) + { + auto exception = std::current_exception(); + traceSink( + events::basic_event( + sourceLocation, + events.ResultEvent, + baseEventData.with( + events::event_data + { + .MethodException = &exception + }))); + throw; } -}; - -} // namespace detail +} PHANTOM_COROUTINES_MODULE_EXPORT template< - typename TraceSink, + is_trace_sink TraceSink, typename Awaitable, - typename AwaiterType + typename AwaiterType, + typename BaseEventData > struct traced_awaiter : detail::trace_sink_accessor, awaiter_wrapper, AwaiterType { - using awaiter_wrapper = traced_awaiter::awaiter_wrapper; + using awaiter_wrapper = awaiter_wrapper; using trace_sink_accessor = traced_awaiter::trace_sink_accessor; using traced_awaiter::trace_sink_accessor::trace_sink; + using wrapped_awaiter_type = typename awaiter_wrapper::awaiter_type; std::source_location m_sourceLocation; + BaseEventData m_baseEventData; - template< - typename Events, - std::invocable<> Call, - typename... Arguments - > auto call_awaiter( - [[maybe_unused]] this auto& awaiter, - Events, - [[maybe_unused]] Call call, - [[maybe_unused]] std::tuple traceArguments - ) -> std::invoke_result_t + this auto& awaiter, + auto events, + const auto& baseEventData, + std::invocable<> auto call + + ) -> std::invoke_result_t { - using wrapped_awaiter_type = typename traced_awaiter::awaiter_wrapper::awaiter_type; - - using begin_event_type = typename Events::template begin_event< - Arguments... - >; - using result_type = std::invoke_result_t; - using result_event_type = typename Events::template result_event< - result_type, - Arguments... - >; - using exception_event_type = typename Events::template exception_event< - Arguments... - >; - - // We capture these variables here because - // the await_suspend_result event may be emitted - // after the awaiter has been destroyed. - auto traceSink = awaiter.traced_awaiter::trace_sink(); - auto sourceLocation = awaiter.traced_awaiter::m_sourceLocation; - - try - { - traceSink( - begin_event_type - { - sourceLocation, - &awaiter, - traceArguments - }); - - if constexpr (std::same_as) - { - call(); - traceSink( - result_event_type - { - sourceLocation, - &awaiter, - traceArguments, - }); - } - else - { - decltype(auto) result = call(); - - traceSink( - result_event_type - { - sourceLocation, - &awaiter, - traceArguments, - result, - }); - - return result; - } - } - catch (...) - { - traceSink( - exception_event_type + return call_traced_method( + awaiter.traced_awaiter::trace_sink(), + events, + awaiter.m_sourceLocation, + baseEventData.with( + events::event_data { - sourceLocation, - &awaiter, - traceArguments, - std::current_exception(), - }); - throw; - } + .Awaiter = static_cast(&awaiter.awaiter()), + }), + call); } decltype(auto) await_ready( @@ -784,12 +758,18 @@ struct traced_awaiter : ) noexcept(noexcept(self.awaiter_wrapper::await_ready())) { return self.traced_awaiter::call_awaiter( - events::await_ready_events{ self }, + detail::method_events_group + { + events::await_ready_begin{}, + events::await_ready_result{}, + events::await_ready_exception{}, + }, + self.get_event_data( + events::empty_arguments), [&]() -> decltype(auto) { return self.awaiter_wrapper::await_ready(); - }, - std::tie() + } ); } @@ -801,14 +781,23 @@ struct traced_awaiter : Arg&& arg ) noexcept(noexcept(self.awaiter_wrapper::await_suspend(std::forward(arg)))) { + std::tuple argumentsTuple{ arg }; + events::arguments arguments{ argumentsTuple }; + return self.traced_awaiter::call_awaiter( - events::await_suspend_events{ self }, + detail::method_events_group + { + events::await_suspend_begin{}, + events::await_suspend_result{}, + events::await_suspend_exception{}, + }, + self.get_event_data( + arguments), [&]() -> decltype(auto) { return self.awaiter_wrapper::await_suspend( std::forward(arg)); - }, - std::tie(arg) + } ); } @@ -817,50 +806,77 @@ struct traced_awaiter : ) noexcept(noexcept(self.awaiter_wrapper::await_resume())) { return self.traced_awaiter::call_awaiter( - events::await_resume_events{ self }, + detail::method_events_group + { + events::await_suspend_begin{}, + events::await_suspend_result{}, + events::await_suspend_exception{}, + }, + self.get_event_data( + events::empty_arguments), [&]() -> decltype(auto) { return self.awaiter_wrapper::await_resume(); - }, - std::tie() + } ); } traced_awaiter( std::source_location sourceLocation, std::invocable auto awaiterFunction, + AwaiterType, trace_sink_accessor traceSinkAccessor, - AwaiterType awaiterType + BaseEventData baseEventData ) : trace_sink_accessor{ traceSinkAccessor }, m_sourceLocation{ sourceLocation }, awaiter_wrapper{ std::move(awaiterFunction) }, - AwaiterType{ awaiterType } + m_baseEventData{ std::move(baseEventData) } + { + } + + template< + typename ... Args + > + auto get_event_data( + this auto& awaiter, + const auto& methodArguments + ) { + return awaiter.m_baseEventData.with( + events::event_data + { + .Awaiter = &awaiter, + .MethodArguments = methodArguments, + }); } }; template< - std::invocable AwaiterFunction, - typename TraceSink, - typename AwaiterType + is_trace_sink TraceSink, + std::invocable<> AwaiterFunction, + typename AwaiterType, + typename BaseEventData > traced_awaiter( std::source_location, AwaiterFunction, + AwaiterType, detail::trace_sink_accessor, - AwaiterType + BaseEventData ) -> traced_awaiter< TraceSink, std::invoke_result_t, - AwaiterType + AwaiterType, + BaseEventData >; namespace detail { +// This class stored the trace sink and produces non-result-specific tracing events. template< is_trace_sink TraceSink > @@ -882,22 +898,21 @@ class traced_promise_trace_sink_storage > traced_promise_trace_sink_storage( TPromise& self, - auto& ... args + const auto& ... args ) requires std::constructible_from : m_traceSink{ args... } { m_traceSink( - events::create_promise< - TPromise, - decltype(args)&... - > - { - std::source_location::current(), - & self, - std::tie(args...) - }); + events::basic_event( + std::source_location::current(), + events::create_promise{}, + events::event_data + { + .Promise = &self, + .PromiseCreationArguments = make_arguments(args...), + })); } template< @@ -915,81 +930,42 @@ class traced_promise_trace_sink_storage m_traceSink{} { m_traceSink( - events::create_promise< - TPromise, - decltype(args)... - > - { - std::source_location::current(), - & self, - std::make_tuple(args...) - }); + events::event + { + .SourceLocation = std::source_location::current(), + .EventType = events::create_promise{}, + .EventData = events::event_data + { + .Promise = &self, + } + } + ); } - template< - typename TPromise, - template typename BeginEventType, - template typename ResultEventType, - template typename ExceptionEventType - > - decltype(auto) call_promise( - this TPromise& promise, - std::source_location sourceLocation, - std::invocable auto call, - auto&& ... traceArgs + auto get_event_data( + this auto& promise ) { - using promise_type = std::remove_cvref_t; - - try + return events::event_data { - promise.traced_promise_trace_sink_storage::m_traceSink( - BeginEventType - { - sourceLocation, - & promise, - std::forward(traceArgs)... - }); - - using result_type = decltype(call()); - if constexpr (std::same_as) - { - call(); - - promise.traced_promise_trace_sink_storage::m_traceSink( - ResultEventType - { - sourceLocation, - & promise, - }); - } - else - { - decltype(auto) result = call(); - - promise.traced_promise_trace_sink_storage::m_traceSink( - ResultEventType - { - sourceLocation, - & promise, - std::forward(traceArgs)..., - result - }); + .Promise = &promise, + }; + } - return std::forward(result); - } - } - catch (...) - { - promise.traced_promise_trace_sink_storage::m_traceSink( - ExceptionEventType - { - sourceLocation, - & promise, - std::current_exception(), - }); - throw; - } + decltype(auto) call_promise_method( + this auto& promise, + auto events, + std::source_location sourceLocation, + const auto& baseEventData, + std::invocable auto call + ) + { + return call_traced_method( + promise.traced_promise_trace_sink_storage::m_traceSink, + events, + sourceLocation, + baseEventData, + call); } }; @@ -1025,21 +1001,24 @@ class traced_promise_yield_value ) requires has_yield_value { - return std::forward(promise).template call_promise< - TPromise, - events::yield_value_argument_events::yield_value_begin, - events::yield_value_argument_events::yield_value_result, - events::yield_value_argument_events::yield_value_exception - >( + return promise.traced_promise_yield_value::call_promise_method( promise, + detail::method_events_group + { + events::yield_value_begin{}, + events::yield_value_result{}, + events::yield_value_exception{}, + }, sourceLocation, + events::event_data + { + .MethodArguments = make_arguments(value), + }, [&]() { return std::forward(promise).traced_promise_yield_value::yield_value( std::forward(value)); - }, - value, - co_yield_awaiter{} + } ); } }; @@ -1065,19 +1044,21 @@ class traced_promise_return_value_or_void std::source_location sourceLocation = std::source_location::current() ) { - return std::forward(promise).template call_promise< - TPromise, - events::return_value_argument_events::return_value_begin, - events::return_value_argument_events::return_value_result, - events::return_value_argument_events::return_value_exception - >( + return std::forward(promise).template call_promise_method( + std::forward(promise), + detail::method_events_group + { + events::return_value_begin{}, + events::return_value_result{}, + events::return_value_exception{}, + }, sourceLocation, + events::event_data{}, [&]() { - return std::forward(promise).traced_promise_return_value_or_void::traced_promise_yield_value::return_value( + return std::forward(promise).traced_promise_return_value_or_void::traced_promise_yield_value::return_value( std::forward(value)); - }, - value + } ); } }; @@ -1094,7 +1075,7 @@ class traced_promise_return_value_or_void< public traced_promise_yield_value { using traced_promise_return_value_or_void::traced_promise_yield_value::traced_promise_yield_value; - using traced_promise_return_value_or_void::traced_promise_yield_value::call_promise; + using traced_promise_return_value_or_void::traced_promise_yield_value::call_promise_method; public: template< @@ -1105,13 +1086,17 @@ class traced_promise_return_value_or_void< std::source_location sourceLocation = std::source_location::current() ) { - return std::forward(promise).template call_promise< - TPromise, - events::return_void_begin, - events::return_void_result, - events::return_void_exception - >( + return std::forward(promise).call_promise_method( + detail::method_events_group + { + events::return_void_begin{}, + events::return_void_result{}, + events::return_void_exception{}, + }, sourceLocation, + events::event_data + { + }, [&]() { return std::forward(promise).traced_promise_return_value_or_void::traced_promise_yield_value::return_void(); @@ -1145,8 +1130,6 @@ struct suppress_trace // co_await trace{ my_trace_event_information{} }; PHANTOM_COROUTINES_MODULE_EXPORT struct trace - : - events::event { }; @@ -1159,31 +1142,33 @@ class traced_promise : public detail::traced_promise_base { +public: + using traced_promise_base = detail::traced_promise_base; + using traced_promise_base::call_promise_method; + using traced_promise_base::get_event_data; + using traced_promise_base::m_traceSink; + template< typename Declaration > struct traced_promise_trace_sink_accessor; -protected: - using base_promise = detail::traced_promise_base; - using base_promise::m_traceSink; - public: traced_promise( auto&& ... args ) : - base_promise{ *this, std::forward(args)... } + traced_promise_base{ *this, std::forward(args)... } { } ~traced_promise() { m_traceSink( - events::destroy_promise - { - std::source_location::current(), - this - }); + basic_event( + std::source_location::current(), + events::destroy_promise{}, + get_event_data() + )); } template< @@ -1197,12 +1182,13 @@ class traced_promise return traced_awaiter { sourceLocation, - [&]() { return promise.base_promise::initial_suspend(); }, + [&]() { return promise.traced_promise_base::initial_suspend(); }, + detail::initial_suspend_awaiter_type{}, detail::trace_sink_accessor { promise.traced_promise::m_traceSink }, - initial_suspend_awaiter{} + promise.get_event_data() }; } @@ -1217,12 +1203,13 @@ class traced_promise return traced_awaiter { sourceLocation, - [&]() { return promise.base_promise::final_suspend(); }, + [&]() { return promise.traced_promise_base::final_suspend(); }, + detail::final_suspend_awaiter_type{}, detail::trace_sink_accessor { promise.traced_promise::m_traceSink }, - final_suspend_awaiter{} + promise.get_event_data() }; } @@ -1234,15 +1221,20 @@ class traced_promise std::source_location sourceLocation = std::source_location::current() ) { - promise.traced_promise::m_traceSink( - events::unhandled_exception - { + promise.call_promise_method( + detail::method_events_group + { + events::unhandled_exception_begin{}, + events::unhandled_exception_result{}, + events::unhandled_exception_exception{}, + }, sourceLocation, - &promise, - std::current_exception() - }); - - promise.base_promise::unhandled_exception(); + events::event_data{}, + [&]() + { + return promise.traced_promise_base::unhandled_exception(); + } + ); } auto await_transform( @@ -1256,14 +1248,14 @@ class traced_promise sourceLocation, [&]() { - return promise.base_promise::await_transform( + return promise.traced_promise_base::await_transform( std::forward(awaiter)); }, detail::trace_sink_accessor { promise.traced_promise::m_traceSink }, - co_await_awaiter{} + detail::co_await_awaiter_type{} }; } @@ -1274,7 +1266,7 @@ class traced_promise const suppress_trace& noTraceAwaiter ) { - return promise.base_promise::await_transform( + return promise.traced_promise_base::await_transform( noTraceAwaiter.value ); } @@ -1296,7 +1288,7 @@ namespace filters struct filter { - constexpr std::false_type operator()(const events::event&) const noexcept { + constexpr std::false_type operator()(const auto&) const noexcept { return {}; } @@ -1309,9 +1301,27 @@ struct filter Right right ) noexcept { - return [=](const events::event& event) { - return left(event) && right(event); - }; + return [=](const auto& event) + { + using leftType = decltype(left(event)); + using rightType = decltype(right(event)); + if constexpr (std::same_as) + { + return leftType{}; + } + else if constexpr (std::same_as) + { + return rightType{}; + } + else if constexpr (std::same_as && std::same_as) + { + return std::true_type{}; + } + else + { + return left(event) && right(event); + } + }; } template< @@ -1323,9 +1333,27 @@ struct filter const Right& right ) noexcept { - return [=](const events::event& event) { - return left(event) || right(event); - }; + return [=](const auto& event) + { + using leftType = decltype(left(event)); + using rightType = decltype(right(event)); + if constexpr (std::same_as) + { + return leftType{}; + } + else if constexpr (std::same_as) + { + return rightType{}; + } + else if constexpr (std::same_as && std::same_as) + { + return std::false_type{}; + } + else + { + return left(event) || right(event); + } + }; } template< @@ -1335,195 +1363,54 @@ struct filter const Filter& filter ) noexcept { - return [=](const events::event& event) { - return !filter(event); - }; + return [=](const auto& event) + { + using type = decltype(filter(event)); + if constexpr (std::same_as) + { + return std::false_type{}; + } + else if constexpr (std::same_as) + { + return std::true_type{}; + } + else + { + return !filter(event); + } + }; } }; struct any_event_fn : filter { - constexpr std::true_type operator()(const events::event&) const noexcept { + constexpr std::true_type operator()(const auto&) const noexcept { return {}; } }; constexpr any_event_fn any_event{}; -struct has_arguments_fn : filter { - using filter::operator(); - template - constexpr std::true_type operator()(const events::arguments&) const noexcept { - return {}; - } -}; -constexpr has_arguments_fn has_arguments{}; - -struct has_void_result_fn : filter { - using filter::operator(); - constexpr std::true_type operator()(const events::result_event&) const noexcept { - return {}; - } -}; -constexpr has_void_result_fn has_void_result{}; - -struct has_result_fn : filter { - using filter::operator(); - template - constexpr std::true_type operator()(const events::result_event&) const noexcept { - return {}; - } -}; -constexpr has_result_fn has_result{}; - -struct has_exception_fn : filter { - template< - std::derived_from Event - > - constexpr auto operator()( - const Event& - ) const noexcept { - return std::is_base_of< - events::exception_event, - Event - >{}; - } -}; -constexpr has_exception_fn has_exception{}; - -struct has_promise_fn : filter { - using filter::operator(); - template - constexpr std::true_type operator()(const events::promise_event&) const noexcept { - return {}; - } -}; -constexpr has_promise_fn has_promise{}; - -struct has_awaiter_fn : filter { - using filter::operator(); - template - constexpr std::true_type operator()(const events::awaiter_event&) const noexcept { - return {}; - } -}; -constexpr has_awaiter_fn has_awaiter{}; - -struct is_create_promise_fn : filter { - using filter::operator(); - template - constexpr std::true_type operator()(const events::create_promise&) const noexcept { - return {}; - } -}; -constexpr is_create_promise_fn is_create_promise{}; - -struct is_destroy_promise_fn : filter { - using filter::operator(); - template - constexpr std::true_type operator()(const events::destroy_promise&) const noexcept { - return {}; - } -}; -constexpr is_destroy_promise_fn is_destroy_promise{}; - -struct is_await_ready_fn : filter { - using filter::operator(); - template - constexpr std::true_type operator()(const events::await_ready_event&) const noexcept { - return {}; - } -}; -constexpr is_await_ready_fn is_await_ready{}; - -struct is_await_suspend_fn : filter { - using filter::operator(); - template - constexpr std::true_type operator()(const events::await_suspend_event&) const noexcept { - return {}; - } -}; -constexpr is_await_suspend_fn is_await_suspend{}; - -struct is_await_resume_fn : filter { - using filter::operator(); - template - constexpr std::true_type operator()(const events::await_resume_event&) const noexcept { - return {}; - } -}; -constexpr is_await_resume_fn is_await_resume{}; - -struct is_return_void_fn : filter { - using filter::operator(); - template - constexpr std::true_type operator()(const events::return_void_begin&) const noexcept { - return {}; - } -}; -constexpr is_return_void_fn is_return_void{}; - -struct is_return_value_fn : filter { - using filter::operator(); - template - constexpr std::true_type operator()(const events::return_value_begin&) const noexcept { - return {}; - } -}; -constexpr is_return_value_fn is_return_value{}; - -struct is_yield_value_fn : filter { - using filter::operator(); - template - constexpr std::true_type operator()(const events::yield_value_event&) const noexcept { - return {}; - } -}; -constexpr is_yield_value_fn is_yield_value{}; - -struct is_unhandled_exception_fn : filter { +template< + typename ... EventType +> +struct event_type_filter_fn : filter +{ using filter::operator(); - template - constexpr std::true_type operator()(const events::unhandled_exception&) const noexcept { + constexpr std::true_type operator()( + const auto& event + ) const noexcept + requires ( + events::is_event_type, EventType> || ... + ) + { return {}; } }; -constexpr is_unhandled_exception_fn is_unhandled_exception{}; - -struct is_initial_suspend_fn : filter { - using filter::operator(); - template - constexpr auto operator()(const events::awaiter_event& event) const noexcept { - return std::bool_constant{}; - } -}; -constexpr is_initial_suspend_fn is_initial_suspend{}; - -struct is_final_suspend_fn : filter { - using filter::operator(); - template - constexpr auto operator()(const events::awaiter_event& event) const noexcept { - return std::bool_constant{}; - } -}; -constexpr is_final_suspend_fn is_final_suspend{}; - -struct is_co_yield_fn : filter { - using filter::operator(); - template - constexpr auto operator()(const events::awaiter_event& event) const noexcept { - return std::bool_constant{}; - } -}; -constexpr is_co_yield_fn is_co_yield{}; -struct is_co_await_fn : filter { - using filter::operator(); - template - constexpr auto operator()(const events::awaiter_event& event) const noexcept { - return std::bool_constant< event.is_co_await>{}; - } -}; -constexpr is_co_await_fn is_co_await{}; +template< + typename ... EventType +> +event_type_filter_fn event_type_filter; template< typename Event From 481d1115ba90d18923357b47c7b8750f5f907652 Mon Sep 17 00:00:00 2001 From: Joshua Rowe Date: Fri, 19 Dec 2025 14:07:38 -0800 Subject: [PATCH 09/46] Make traces_initial_suspend test work --- Phantom.Coroutines.Test/tracing_test.cpp | 683 +++++------------- .../include/Phantom.Coroutines/tracing.h | 396 +++++++--- 2 files changed, 453 insertions(+), 626 deletions(-) diff --git a/Phantom.Coroutines.Test/tracing_test.cpp b/Phantom.Coroutines.Test/tracing_test.cpp index 708948b..4e235bd 100644 --- a/Phantom.Coroutines.Test/tracing_test.cpp +++ b/Phantom.Coroutines.Test/tracing_test.cpp @@ -83,13 +83,20 @@ struct tracing_tests : testing::Test } }; + template< + typename Result + > + using test_underlying_promise = polymorphic_promise< + task_promise + >; + template< typename Result, typename Filter > using test_traced_promise = traced_promise< trace_sink, - polymorphic_promise> + test_underlying_promise >; template< @@ -123,13 +130,18 @@ struct tracing_tests : testing::Test } void ExpectIsInitialSuspend( - const auto* event + const auto& event ) { - EXPECT_EQ(true, event->is_initial_suspend); - EXPECT_EQ(false, event->is_co_await); - EXPECT_EQ(false, event->is_co_yield); - EXPECT_EQ(false, event->is_final_suspend); + static_assert(std::same_as< + decltype(event.EventData.AwaiterType), + events::initial_suspend_awaiter_type + >); + + EXPECT_EQ(true, event.EventData.AwaiterType.is_traced_promise_initial_suspend_awaiter); + EXPECT_EQ(false, event.EventData.AwaiterType.is_traced_promise_co_await_awaiter); + EXPECT_EQ(false, event.EventData.AwaiterType.is_traced_promise_co_yield_awaiter); + EXPECT_EQ(false, event.EventData.AwaiterType.is_traced_promise_final_suspend_awaiter); } void ExpectIsFinalSuspend( @@ -359,7 +371,7 @@ struct tracing_tests : testing::Test { return false; } - + return true; } @@ -427,13 +439,11 @@ ASYNC_TEST_F(tracing_tests, traces_create_and_destroy_promise) }; using taskLambda_type = decltype(taskLambda); - using expected_promise_type = std::coroutine_traits< - test_traced_task<>, - traced_events_checker&, - decltype(filter) - >::promise_type; + using expected_promise_type = test_underlying_promise; + using expected_traced_promise_type = test_traced_promise; expected_promise_type* expectedPromise = nullptr; + expected_traced_promise_type* expectedTracedPromise = nullptr; auto eventIndex = 0; traced_events_checker tracedEventsChecker; @@ -446,6 +456,7 @@ ASYNC_TEST_F(tracing_tests, traces_create_and_destroy_promise) auto expectedEventData = events::event_data { .Promise = static_cast(nullptr), + .TracedPromise = static_cast(nullptr), .PromiseCreationArguments = events::make_arguments( taskLambda, tracedEventsChecker, @@ -461,7 +472,9 @@ ASYNC_TEST_F(tracing_tests, traces_create_and_destroy_promise) auto& actualEvent = CastEventType(actualEventAny, expectedEvent); expectedPromise = actualEvent.EventData.Promise; + expectedTracedPromise = actualEvent.EventData.TracedPromise; expectedEvent.EventData.Promise = expectedPromise; + expectedEvent.EventData.TracedPromise = expectedTracedPromise; expect_events_equal( expectedEvent, @@ -473,6 +486,7 @@ ASYNC_TEST_F(tracing_tests, traces_create_and_destroy_promise) auto expectedEventData = events::event_data { .Promise = expectedPromise, + .TracedPromise = expectedTracedPromise, }; auto expectedEvent = basic_event( @@ -495,554 +509,183 @@ ASYNC_TEST_F(tracing_tests, traces_create_and_destroy_promise) co_await taskLambda(tracedEventsChecker, filter); } -#if 0 ASYNC_TEST_F(tracing_tests, traces_initial_suspend) { + auto filter = filters::event_type_filter< + events::await_ready_begin, + events::await_ready_result, + events::await_suspend_begin, + events::await_suspend_result, + events::await_resume_begin, + events::await_resume_result + > + && + filters::awaiter_type_filter; + auto taskLambda = []( + this auto& self, traced_events_checker& eventsChecker, - std::string inputArgument + decltype(filter) ) -> test_traced_task<> { co_return; }; - using expected_promise_type = coroutine_function_traits::promise_type; - expected_promise_type* expectedPromise = nullptr; - - using initialSuspendAwaiter = traced_awaiter< - trace_sink, - std::suspend_always, - detail::initial_suspend_awaiter_type - >; + using expected_traced_promise_type = std::coroutine_traits< + test_traced_task<>, + traced_events_checker&, + decltype(filter) + >::promise_type; - initialSuspendAwaiter* expectedInitialSuspendAwaiter = nullptr; - - using finalSuspendAwaiter = traced_awaiter< - trace_sink, - final_suspend_transfer, - detail::final_suspend_awaiter_type - >; + using expected_underlying_promise_type = test_underlying_promise; - finalSuspendAwaiter* expectedFinalSuspendAwaiter = nullptr; + // The unwrapped awaiter type - what the underlying promise's initial_suspend() returns + using unwrapped_awaiter_type = std::suspend_always; - int eventIndex = 0; - traced_events_checker eventChecker + expected_underlying_promise_type* expectedPromise = nullptr; + expected_traced_promise_type* expectedTracedPromise = nullptr; + unwrapped_awaiter_type* expectedAwaiter = nullptr; + + auto eventIndex = 0; + traced_events_checker tracedEventsChecker; + tracedEventsChecker = { [&](const std::any& actualEventAny) { - [&](const std::any& anyEvent) + ++eventIndex; + auto checkingIndex = 0; + + if (eventIndex == ++checkingIndex) { - using namespace events; - - ++eventIndex; - // We do this checkingIndex so that when trace messages are - // added or removed we don't have to change every index, - // we just insert code into the right place. - int checkingIndex = 0; - if (eventIndex == ++checkingIndex) - { - if constexpr (Config::Lambda_Reference_Is_First_Argument_Of_Promise_Constructor) - { - using expectedEventType = create_promise< - expected_promise_type, - decltype(taskLambda) const&, - traced_events_checker&, - std::string& - >; - - auto event = CastEventType(anyEvent); - - EXPECT_NE(nullptr, expectedPromise = event->Promise); - EXPECT_EQ(&taskLambda, &get<0>(event->Arguments)); - EXPECT_EQ(&eventChecker, &get<1>(event->Arguments)); - EXPECT_EQ(std::string("hello"), get<2>(event->Arguments)); - } - else - { - using expectedEventType = create_promise< - expected_promise_type, - traced_events_checker&, - std::string& - >; - - auto event = CastEventType(anyEvent); - - EXPECT_NE(nullptr, expectedPromise = event->Promise); - EXPECT_EQ(&eventChecker, &get<0>(event->Arguments)); - EXPECT_EQ(std::string("hello"), get<1>(event->Arguments)); - } - } - else if (eventIndex == ++checkingIndex) - { - using expectedEventType = await_ready_begin< - initialSuspendAwaiter - >; - - auto event = CastEventType(anyEvent); - - EXPECT_NE(nullptr, expectedInitialSuspendAwaiter = event->Awaiter); - - ExpectIsInitialSuspend(event); - EXPECT_EQ(std::tuple<>{}, event->Arguments); - } - else if (eventIndex == ++checkingIndex) - { - using expectedEventType = await_ready_result< - initialSuspendAwaiter, - bool - >; - - auto event = CastEventType(anyEvent); - - EXPECT_EQ(expectedInitialSuspendAwaiter, event->Awaiter); - - ExpectIsInitialSuspend(event); - EXPECT_EQ(std::tuple<>{}, event->Arguments); - EXPECT_EQ(false, event->Result); - } - else if (eventIndex == ++checkingIndex) - { - using expectedEventType = await_suspend_begin< - initialSuspendAwaiter, - std::coroutine_handle - >; - - auto event = CastEventType(anyEvent); - - EXPECT_EQ(expectedInitialSuspendAwaiter, event->Awaiter); - EXPECT_EQ(expectedPromise, &get<0>(event->Arguments).promise()); - ExpectIsInitialSuspend(event); - } - else if (eventIndex == ++checkingIndex) - { - using expectedEventType = await_suspend_result< - initialSuspendAwaiter, - void, - std::coroutine_handle - >; - - auto event = CastEventType(anyEvent); - - EXPECT_EQ(expectedInitialSuspendAwaiter, event->Awaiter); - EXPECT_EQ(expectedPromise, &get<0>(event->Arguments).promise()); - EXPECT_EQ(true, event->is_void_result); - ExpectIsInitialSuspend(event); - } - else if (eventIndex == ++checkingIndex) - { - using expectedEventType = await_resume_begin< - initialSuspendAwaiter - >; - - auto event = CastEventType(anyEvent); - - EXPECT_EQ(expectedInitialSuspendAwaiter, event->Awaiter); - EXPECT_EQ(std::tuple<>{}, event->Arguments); - ExpectIsInitialSuspend(event); - } - else if (eventIndex == ++checkingIndex) - { - using expectedEventType = await_resume_result< - initialSuspendAwaiter, - void - >; - - auto event = CastEventType(anyEvent); - - EXPECT_EQ(expectedInitialSuspendAwaiter, event->Awaiter); - EXPECT_EQ(std::tuple<>{}, event->Arguments); - EXPECT_EQ(true, event->is_void_result); - ExpectIsInitialSuspend(event); - } - else if (eventIndex == ++checkingIndex) - { - using expectedEventType = return_void_begin< - expected_promise_type - >; - - auto event = CastEventType(anyEvent); - - EXPECT_EQ(expectedPromise, event->Promise); - } - else if (eventIndex == ++checkingIndex) - { - using expectedEventType = return_void_result< - expected_promise_type, - void - >; - - auto event = CastEventType(anyEvent); - - EXPECT_EQ(expectedPromise, event->Promise); - EXPECT_EQ(true, event->is_void_result); - } - else if (eventIndex == ++checkingIndex) - { - using expectedEventType = await_ready_begin< - finalSuspendAwaiter - >; - - auto event = CastEventType(anyEvent); - - EXPECT_NE(nullptr, expectedFinalSuspendAwaiter = event->Awaiter); - - ExpectIsFinalSuspend(event); - EXPECT_EQ(std::tuple<>{}, event->Arguments); - } - else if (eventIndex == ++checkingIndex) - { - using expectedEventType = await_ready_result< - finalSuspendAwaiter, - bool - >; - - auto event = CastEventType(anyEvent); - - EXPECT_EQ(expectedFinalSuspendAwaiter, event->Awaiter); - - ExpectIsFinalSuspend(event); - EXPECT_EQ(std::tuple<>{}, event->Arguments); - EXPECT_EQ(false, event->Result); - } - else if (eventIndex == ++checkingIndex) - { - using expectedEventType = await_suspend_begin< - finalSuspendAwaiter, - std::coroutine_handle - >; - - auto event = CastEventType(anyEvent); - - EXPECT_EQ(expectedFinalSuspendAwaiter, event->Awaiter); - EXPECT_EQ(expectedPromise, &get<0>(event->Arguments).promise()); - ExpectIsFinalSuspend(event); - } - else if (eventIndex == ++checkingIndex) - { - using expectedEventType = await_suspend_result< - finalSuspendAwaiter, - std::coroutine_handle, - std::coroutine_handle - >; - - auto event = CastEventType(anyEvent); - - EXPECT_EQ(expectedFinalSuspendAwaiter, event->Awaiter); - EXPECT_EQ(expectedPromise, &get<0>(event->Arguments).promise()); - EXPECT_EQ(false, event->is_void_result); - EXPECT_NE(nullptr, event->Result.address()); - ExpectIsFinalSuspend(event); - } - else if (eventIndex == ++checkingIndex) + auto expectedEventData = events::event_data { - using expectedEventType = destroy_promise< - expected_promise_type - >; + .Promise = static_cast(nullptr), + .TracedPromise = static_cast(nullptr), + .Awaiter = static_cast(nullptr), + .AwaiterType = events::initial_suspend_awaiter_type{}, + }; - auto event = CastEventType(anyEvent); + auto expectedEvent = basic_event( + std::source_location(), + events::await_ready_begin{}, + expectedEventData + ); - EXPECT_EQ(expectedPromise, event->Promise); - } - else - { - EXPECT_FALSE(true); - } + auto& actualEvent = CastEventType(actualEventAny, expectedEvent); + + expectedPromise = actualEvent.EventData.Promise; + expectedTracedPromise = actualEvent.EventData.TracedPromise; + expectedAwaiter = actualEvent.EventData.Awaiter; + + ExpectIsInitialSuspend(actualEvent); } - }; - - co_await taskLambda(eventChecker, "hello"); - EXPECT_EQ(14, eventIndex); -} - -ASYNC_TEST_F(tracing_tests, traces_co_await_events_await_ready_true_void_result) -{ - using awaiter_type = Coroutines::detail::suspend_never; - - auto taskLambda = []( - traced_events_checker& eventsChecker, - std::string inputArgument, - filters::is_co_await_fn = {} - ) -> test_traced_task<> - { - co_await awaiter_type{}; - }; - - using traced_awaiter = traced_awaiter< - trace_sink, - awaiter_type, - co_await_awaiter - >; - - traced_awaiter* expectedAwaiter = nullptr; - - int eventIndex = 0; - traced_events_checker eventChecker - { - [&](const std::any& anyEvent) + else if(eventIndex == ++checkingIndex) { - using namespace events; - - ++eventIndex; - // We do this checkingIndex so that when trace messages are - // added or removed we don't have to change every index, - // we just insert code into the right place. - int checkingIndex = 0; - if (eventIndex == ++checkingIndex) - { - using expectedEventType = await_ready_begin< - traced_awaiter - >; - - auto event = CastEventType(anyEvent); - - expectedAwaiter = event->Awaiter; - EXPECT_EQ(false, filters::has_result(*event).value); - ExpectIsCoAwait(event); - } - else if (eventIndex == ++checkingIndex) - { - using expectedEventType = await_ready_result< - traced_awaiter, - bool - >; - - auto event = CastEventType(anyEvent); - - EXPECT_EQ(expectedAwaiter, event->Awaiter); - EXPECT_EQ(true, event->Result); - ExpectIsCoAwait(event); - } - else if (eventIndex == ++checkingIndex) - { - using expectedEventType = await_resume_begin< - traced_awaiter - >; - - auto event = CastEventType(anyEvent); - - EXPECT_EQ(expectedAwaiter, event->Awaiter); - ExpectIsCoAwait(event); - } - else if (eventIndex == ++checkingIndex) + auto expectedEventData = events::event_data { - using expectedEventType = await_resume_result< - traced_awaiter, - void - >; + .Promise = expectedPromise, + .TracedPromise = expectedTracedPromise, + .Awaiter = expectedAwaiter, + .AwaiterType = events::initial_suspend_awaiter_type{}, + .MethodResult = events::value { false }, + }; - auto event = CastEventType(anyEvent); + auto expectedEvent = basic_event( + std::source_location(), + events::await_ready_result{}, + expectedEventData + ); - EXPECT_EQ(expectedAwaiter, event->Awaiter); - EXPECT_EQ(true, event->is_void_result); - ExpectIsCoAwait(event); - } - else - { - EXPECT_FALSE(true); - } + auto& actualEvent = CastEventType(actualEventAny, expectedEvent); + ExpectIsInitialSuspend(actualEvent); } - }; - - co_await taskLambda(eventChecker, "hello"); - EXPECT_EQ(4, eventIndex); -} - -ASYNC_TEST_F(tracing_tests, traces_co_await_events_await_ready_exception) -{ - auto awaiter = test_awaiter() - .with_await_ready_exception(std::runtime_error("await_ready_exception")); - - using awaiter_type = decltype(awaiter); - - auto taskLambda = [&]( - traced_events_checker& eventsChecker, - std::string inputArgument, - filters::is_co_await_fn = {} - ) -> test_traced_task<> - { - co_await awaiter; - }; - - using traced_awaiter = traced_awaiter< - trace_sink, - awaiter_type, - co_await_awaiter - >; - - traced_awaiter* expectedAwaiter = nullptr; - - int eventIndex = 0; - traced_events_checker eventChecker - { - [&](const std::any& anyEvent) + else if(eventIndex == ++checkingIndex) { - using namespace events; - - ++eventIndex; - // We do this checkingIndex so that when trace messages are - // added or removed we don't have to change every index, - // we just insert code into the right place. - int checkingIndex = 0; - if (eventIndex == ++checkingIndex) - { - using expectedEventType = await_ready_begin< - traced_awaiter - >; - - auto event = CastEventType(anyEvent); - - expectedAwaiter = event->Awaiter; - EXPECT_EQ(false, filters::has_result(*event).value); - ExpectIsCoAwait(event); - } - else if (eventIndex == ++checkingIndex) + auto expectedEventData = events::event_data { - using expectedEventType = await_ready_exception< - traced_awaiter - >; + .Promise = expectedPromise, + .TracedPromise = expectedTracedPromise, + .Awaiter = expectedAwaiter, + .AwaiterType = events::initial_suspend_awaiter_type{}, + .MethodArguments = events::make_arguments( + std::coroutine_handle::from_promise(*expectedTracedPromise) + ) + }; - auto event = CastEventType(anyEvent); + auto expectedEvent = basic_event( + std::source_location(), + events::await_suspend_begin{}, + expectedEventData + ); - EXPECT_EQ(expectedAwaiter, event->Awaiter); - EXPECT_THROW(std::rethrow_exception(event->Exception), std::runtime_error); - ExpectIsCoAwait(event); - } - else - { - EXPECT_FALSE(true); - } + auto& actualEvent = CastEventType(actualEventAny, expectedEvent); + ExpectIsInitialSuspend(actualEvent); } - }; - - co_await expect_exception(taskLambda(eventChecker, "hello")); - EXPECT_EQ(2, eventIndex); -} - -ASYNC_TEST_F(tracing_tests, traces_co_await_events_await_suspend_void_result) -{ - auto awaiter = test_awaiter() - .with_await_ready_false() - .with_await_suspend_void() - .with_await_resume_void(); - - using awaiter_type = decltype(awaiter); - - auto taskLambda = [&]( - traced_events_checker& eventsChecker, - std::string inputArgument, - filters::is_co_await_fn = {} - ) -> test_traced_task<> - { - co_await awaiter; - }; - - using expected_promise_type = coroutine_function_traits::promise_type; - - using traced_awaiter = traced_awaiter< - trace_sink, - awaiter_type, - co_await_awaiter - >; - - traced_awaiter* expectedAwaiter = nullptr; - - int eventIndex = 0; - traced_events_checker eventChecker - { - [&](const std::any& anyEvent) + else if(eventIndex == ++checkingIndex) { - using namespace events; - - ++eventIndex; - // We do this checkingIndex so that when trace messages are - // added or removed we don't have to change every index, - // we just insert code into the right place. - int checkingIndex = 0; - if (eventIndex == ++checkingIndex) - { - using expectedEventType = await_ready_begin< - traced_awaiter - >; - - auto event = CastEventType(anyEvent); - - expectedAwaiter = event->Awaiter; - EXPECT_EQ(false, filters::has_result(*event).value); - ExpectIsCoAwait(event); - } - else if (eventIndex == ++checkingIndex) - { - using expectedEventType = await_ready_result< - traced_awaiter, - bool - >; - - auto event = CastEventType(anyEvent); - - EXPECT_EQ(expectedAwaiter, event->Awaiter); - EXPECT_EQ(false, event->Result); - ExpectIsCoAwait(event); - } - else if (eventIndex == ++checkingIndex) - { - using expectedEventType = await_suspend_begin< - traced_awaiter, - std::coroutine_handle - >; - - auto event = CastEventType(anyEvent); - - EXPECT_EQ(expectedAwaiter, event->Awaiter); - EXPECT_EQ(true, static_cast(get<0>(event->Arguments))); - ExpectIsCoAwait(event); - } - else if (eventIndex == ++checkingIndex) + auto expectedEventData = events::event_data { - using expectedEventType = await_resume_begin< - traced_awaiter - >; + .Promise = expectedPromise, + .TracedPromise = expectedTracedPromise, + .Awaiter = expectedAwaiter, + .AwaiterType = events::initial_suspend_awaiter_type{}, + .MethodArguments = events::make_arguments( + std::coroutine_handle::from_promise(*expectedTracedPromise) + ) + }; - auto event = CastEventType(anyEvent); + auto expectedEvent = basic_event( + std::source_location(), + events::await_suspend_result{}, + expectedEventData + ); - EXPECT_EQ(expectedAwaiter, event->Awaiter); - ExpectIsCoAwait(event); - } - else if (eventIndex == ++checkingIndex) + auto& actualEvent = CastEventType(actualEventAny, expectedEvent); + ExpectIsInitialSuspend(actualEvent); + } + else if(eventIndex == ++checkingIndex) + { + auto expectedEventData = events::event_data { - using expectedEventType = await_resume_result< - traced_awaiter, - void - >; + .Promise = expectedPromise, + .TracedPromise = expectedTracedPromise, + .Awaiter = expectedAwaiter, + .AwaiterType = events::initial_suspend_awaiter_type{}, + }; - auto event = CastEventType(anyEvent); + auto expectedEvent = basic_event( + std::source_location(), + events::await_resume_begin{}, + expectedEventData + ); - EXPECT_EQ(expectedAwaiter, event->Awaiter); - EXPECT_EQ(true, event->is_void_result); - ExpectIsCoAwait(event); - } - else if (eventIndex == ++checkingIndex) + auto& actualEvent = CastEventType(actualEventAny, expectedEvent); + ExpectIsInitialSuspend(actualEvent); + } + else if(eventIndex == ++checkingIndex) + { + auto expectedEventData = events::event_data { - using expectedEventType = await_suspend_result< - traced_awaiter, - void, - std::coroutine_handle - >; + .Promise = expectedPromise, + .TracedPromise = expectedTracedPromise, + .Awaiter = expectedAwaiter, + .AwaiterType = events::initial_suspend_awaiter_type{}, + }; - auto event = CastEventType(anyEvent); + auto expectedEvent = basic_event( + std::source_location(), + events::await_resume_result{}, + expectedEventData + ); - EXPECT_EQ(expectedAwaiter, event->Awaiter); - EXPECT_EQ(true, event->is_void_result); - ExpectIsCoAwait(event); - } - else - { - EXPECT_FALSE(true); - } + auto& actualEvent = CastEventType(actualEventAny, expectedEvent); + ExpectIsInitialSuspend(actualEvent); } - }; + else + { + EXPECT_FALSE(true); + } + } }; - sync_wait(taskLambda(eventChecker, "hello")); - EXPECT_EQ(6, eventIndex); - co_return; + co_await taskLambda(tracedEventsChecker, filter); } -#endif } diff --git a/Phantom.Coroutines/include/Phantom.Coroutines/tracing.h b/Phantom.Coroutines/include/Phantom.Coroutines/tracing.h index 2345dd3..55ba91d 100644 --- a/Phantom.Coroutines/include/Phantom.Coroutines/tracing.h +++ b/Phantom.Coroutines/include/Phantom.Coroutines/tracing.h @@ -169,14 +169,61 @@ struct empty_arguments_value constexpr empty_value empty{}; constexpr empty_arguments_value empty_arguments{ }; +PHANTOM_COROUTINES_MODULE_EXPORT +struct no_awaiter_type +{ + static constexpr bool is_traced_promise_initial_suspend_awaiter = false; + static constexpr bool is_traced_promise_final_suspend_awaiter = false; + static constexpr bool is_traced_promise_co_yield_awaiter = false; + static constexpr bool is_traced_promise_co_await_awaiter = false; +}; + +PHANTOM_COROUTINES_MODULE_EXPORT +struct initial_suspend_awaiter_type +{ + static constexpr bool is_traced_promise_initial_suspend_awaiter = true; + static constexpr bool is_traced_promise_final_suspend_awaiter = false; + static constexpr bool is_traced_promise_co_yield_awaiter = false; + static constexpr bool is_traced_promise_co_await_awaiter = false; +}; + +PHANTOM_COROUTINES_MODULE_EXPORT +struct final_suspend_awaiter_type +{ + static constexpr bool is_traced_promise_initial_suspend_awaiter = false; + static constexpr bool is_traced_promise_final_suspend_awaiter = true; + static constexpr bool is_traced_promise_co_yield_awaiter = false; + static constexpr bool is_traced_promise_co_await_awaiter = false; +}; + +PHANTOM_COROUTINES_MODULE_EXPORT +struct co_yield_awaiter_type +{ + static constexpr bool is_traced_promise_initial_suspend_awaiter = false; + static constexpr bool is_traced_promise_final_suspend_awaiter = false; + static constexpr bool is_traced_promise_co_yield_awaiter = true; + static constexpr bool is_traced_promise_co_await_awaiter = false; +}; + +PHANTOM_COROUTINES_MODULE_EXPORT +struct co_await_awaiter_type +{ + static constexpr bool is_traced_promise_initial_suspend_awaiter = false; + static constexpr bool is_traced_promise_final_suspend_awaiter = false; + static constexpr bool is_traced_promise_co_yield_awaiter = false; + static constexpr bool is_traced_promise_co_await_awaiter = true; +}; + template< typename T -> concept is_empty = +> concept is_empty = std::same_as || std::same_as || -std::same_as; +std::same_as +|| +std::same_as; template< typename T @@ -203,6 +250,23 @@ struct PromiseAccessor return ∅ } } + + static constexpr bool has_traced_promise = requires(Event self) + { + { self.Data.TracedPromise } -> is_not_empty; + }; + + auto* traced_promise(this auto& self) + { + if constexpr (has_traced_promise) + { + return self.Data.TracedPromise; + } + else + { + return ∅ + } + } static constexpr bool has_promise_creation_arguments = requires(Event self) { @@ -245,6 +309,41 @@ struct AwaiterAccessor return ∅ } } + + static constexpr bool has_awaiter_type = requires(Event self) + { + { self.Data.AwaiterType } -> is_not_empty; + }; + + auto* awaiter_type(this auto& self) + { + if constexpr (has_awaiter_type) + { + return self.Data.AwaiterType; + } + else + { + return ∅ + } + } + + static constexpr bool has_traced_awaiter = requires(Event self) + { + { self.Data.TracedAwaiter } -> is_not_empty; + }; + + auto* traced_awaiter(this auto& self) + { + if constexpr (has_awaiter) + { + return self.Data.TracedAwaiter; + } + else + { + return ∅ + } + } + }; template< @@ -465,8 +564,11 @@ template< PHANTOM_COROUTINES_MODULE_EXPORT template< typename TPromise = empty_value, + typename TTracedPromise = empty_value, typename TPromiseCreationArguments = empty_arguments_value, typename TAwaiter = empty_value, + typename TTracedAwaiter = empty_value, + typename TAwaiterType = no_awaiter_type, typename TMethodArguments = empty_arguments_value, typename TMethodResult = empty_value, typename TMethodException = empty_value, @@ -477,8 +579,11 @@ template< struct event_data { [[no_unique_address]] TPromise Promise = empty; + [[no_unique_address]] TTracedPromise TracedPromise = empty; [[no_unique_address]] TPromiseCreationArguments PromiseCreationArguments = empty_arguments; [[no_unique_address]] TAwaiter Awaiter = empty; + [[no_unique_address]] TTracedAwaiter TracedAwaiter = empty; + [[no_unique_address]] TAwaiterType AwaiterType = no_awaiter_type{}; [[no_unique_address]] TMethodArguments MethodArguments = empty_arguments; [[no_unique_address]] TMethodResult MethodResult = empty; [[no_unique_address]] TMethodException MethodException = empty; @@ -512,12 +617,21 @@ struct event_data .Promise = combine( this->Promise, other.Promise), + .TracedPromise = combine( + this->TracedPromise, + other.TracedPromise), .PromiseCreationArguments = combine( this->PromiseCreationArguments, other.PromiseCreationArguments), .Awaiter = combine( this->Awaiter, other.Awaiter), + .TracedAwaiter = combine( + this->TracedAwaiter, + other.TracedAwaiter), + .AwaiterType = combine( + this->AwaiterType, + other.AwaiterType), .MethodArguments = combine( this->MethodArguments, other.MethodArguments), @@ -584,41 +698,6 @@ auto basic_event( namespace detail { -PHANTOM_COROUTINES_MODULE_EXPORT -struct initial_suspend_awaiter_type -{ - static constexpr bool is_traced_promise_initial_suspend_awaiter = true; - static constexpr bool is_traced_promise_final_suspend_awaiter = false; - static constexpr bool is_traced_promise_co_yield_awaiter = false; - static constexpr bool is_traced_promise_co_await_awaiter = false; -}; - -PHANTOM_COROUTINES_MODULE_EXPORT -struct final_suspend_awaiter_type -{ - static constexpr bool is_traced_promise_initial_suspend_awaiter = false; - static constexpr bool is_traced_promise_final_suspend_awaiter = true; - static constexpr bool is_traced_promise_co_yield_awaiter = false; - static constexpr bool is_traced_promise_co_await_awaiter = false; -}; - -PHANTOM_COROUTINES_MODULE_EXPORT -struct co_yield_awaiter_type -{ - static constexpr bool is_traced_promise_initial_suspend_awaiter = false; - static constexpr bool is_traced_promise_final_suspend_awaiter = false; - static constexpr bool is_traced_promise_co_yield_awaiter = true; - static constexpr bool is_traced_promise_co_await_awaiter = false; -}; - -PHANTOM_COROUTINES_MODULE_EXPORT -struct co_await_awaiter_type -{ - static constexpr bool is_traced_promise_initial_suspend_awaiter = false; - static constexpr bool is_traced_promise_final_suspend_awaiter = false; - static constexpr bool is_traced_promise_co_yield_awaiter = false; - static constexpr bool is_traced_promise_co_await_awaiter = true; -}; template< is_trace_sink TraceSink @@ -692,7 +771,7 @@ auto call_traced_method( baseEventData.with( events::event_data { - .MethodResult = &result + .MethodResult = events::value{ result } }))); return result; } @@ -707,7 +786,7 @@ auto call_traced_method( baseEventData.with( events::event_data { - .MethodException = &exception + .MethodException = events::value{ exception } }))); throw; } @@ -749,6 +828,7 @@ struct traced_awaiter : events::event_data { .Awaiter = static_cast(&awaiter.awaiter()), + .AwaiterType = AwaiterType{}, }), call); } @@ -808,9 +888,9 @@ struct traced_awaiter : return self.traced_awaiter::call_awaiter( detail::method_events_group { - events::await_suspend_begin{}, - events::await_suspend_result{}, - events::await_suspend_exception{}, + events::await_resume_begin{}, + events::await_resume_result{}, + events::await_resume_exception{}, }, self.get_event_data( events::empty_arguments), @@ -910,8 +990,9 @@ class traced_promise_trace_sink_storage events::create_promise{}, events::event_data { - .Promise = &self, - .PromiseCreationArguments = make_arguments(args...), + .Promise = &self.get_underlying_promise(), + .TracedPromise = &self, + .PromiseCreationArguments = events::make_arguments(args...), })); } @@ -936,7 +1017,8 @@ class traced_promise_trace_sink_storage .EventType = events::create_promise{}, .EventData = events::event_data { - .Promise = &self, + .Promise = &self.get_underlying_promise(), + .TracedPromise = &self, } } ); @@ -948,7 +1030,8 @@ class traced_promise_trace_sink_storage { return events::event_data { - .Promise = &promise, + .Promise = &promise.get_underlying_promise(), + .TracedPromise = &promise, }; } @@ -1183,7 +1266,7 @@ class traced_promise { sourceLocation, [&]() { return promise.traced_promise_base::initial_suspend(); }, - detail::initial_suspend_awaiter_type{}, + events::initial_suspend_awaiter_type{}, detail::trace_sink_accessor { promise.traced_promise::m_traceSink @@ -1204,7 +1287,7 @@ class traced_promise { sourceLocation, [&]() { return promise.traced_promise_base::final_suspend(); }, - detail::final_suspend_awaiter_type{}, + events::final_suspend_awaiter_type{}, detail::trace_sink_accessor { promise.traced_promise::m_traceSink @@ -1255,7 +1338,7 @@ class traced_promise { promise.traced_promise::m_traceSink }, - detail::co_await_awaiter_type{} + events::co_await_awaiter_type{} }; } @@ -1280,12 +1363,47 @@ class traced_promise traceEvent); return suspend_never{}; } + + template< + typename This + > + auto& get_underlying_promise( + this This& self) + { + if constexpr (std::is_const_v) + { + return static_cast(self); + } + else + { + return static_cast(self); + } + } }; PHANTOM_COROUTINES_MODULE_EXPORT namespace filters { +struct filter; + +template< + std::derived_from Left, + std::derived_from Right +> +struct and_filter; + +template< + std::derived_from Left, + std::derived_from Right +> +struct or_filter; + +template< + std::derived_from Filter +> +struct not_filter; + struct filter { constexpr std::false_type operator()(const auto&) const noexcept { @@ -1301,27 +1419,7 @@ struct filter Right right ) noexcept { - return [=](const auto& event) - { - using leftType = decltype(left(event)); - using rightType = decltype(right(event)); - if constexpr (std::same_as) - { - return leftType{}; - } - else if constexpr (std::same_as) - { - return rightType{}; - } - else if constexpr (std::same_as && std::same_as) - { - return std::true_type{}; - } - else - { - return left(event) && right(event); - } - }; + return and_filter{ left, right }; } template< @@ -1333,27 +1431,7 @@ struct filter const Right& right ) noexcept { - return [=](const auto& event) - { - using leftType = decltype(left(event)); - using rightType = decltype(right(event)); - if constexpr (std::same_as) - { - return leftType{}; - } - else if constexpr (std::same_as) - { - return rightType{}; - } - else if constexpr (std::same_as && std::same_as) - { - return std::false_type{}; - } - else - { - return left(event) || right(event); - } - }; + return or_filter{ left, right }; } template< @@ -1363,25 +1441,108 @@ struct filter const Filter& filter ) noexcept { - return [=](const auto& event) + return not_filter{ filter }; + } +}; + +template< + std::derived_from Left, + std::derived_from Right +> +struct and_filter : filter +{ + Left left; + Right right; + + constexpr bool operator()( + const auto& event + ) const noexcept + { + using leftType = decltype(left(event)); + using rightType = decltype(right(event)); + + if constexpr ( + std::same_as + || + std::same_as) { - using type = decltype(filter(event)); - if constexpr (std::same_as) - { - return std::false_type{}; - } - else if constexpr (std::same_as) - { - return std::true_type{}; - } - else - { - return !filter(event); - } - }; + return std::false_type{}; + } + else if constexpr ( + std::same_as && + std::same_as) + { + return std::true_type{}; + } + else + { + return left(event) && right(event); + } } }; +template< + std::derived_from Left, + std::derived_from Right +> +struct or_filter : filter +{ + Left left; + Right right; + + constexpr bool operator()( + const auto& event + ) const noexcept + { + using leftType = decltype(left(event)); + using rightType = decltype(right(event)); + + if constexpr ( + std::same_as + || + std::same_as) + { + return std::true_type{}; + } + else if constexpr ( + std::same_as && + std::same_as) + { + return std::false_type{}; + } + else + { + return left(event) && right(event); + } + } +}; + +template< + std::derived_from Filter +> +struct not_filter : filter +{ + Filter filter; + + static constexpr auto operator()( + const auto& event) + { + using type = decltype(filter(event)); + if constexpr (std::same_as) + { + return std::false_type{}; + } + else if constexpr (std::same_as) + { + return std::true_type{}; + } + else + { + return !filter(event); + } + }; +}; + struct any_event_fn : filter { constexpr std::true_type operator()(const auto&) const noexcept { @@ -1407,6 +1568,29 @@ struct event_type_filter_fn : filter } }; +template< + typename AwaiterType +> +struct awaiter_type_filter_fn : filter +{ + using filter::operator(); + constexpr std::true_type operator()( + const auto& event + ) const noexcept + requires std::same_as< + typename std::remove_cvref_t::event_data_type::awaiter_type, + AwaiterType + > + { + return {}; + } +}; + +template< + typename AwaiterType +> +constexpr awaiter_type_filter_fn awaiter_type_filter; + template< typename ... EventType > From 8e633402813272076ca968600e65a44bb27b7a7a Mon Sep 17 00:00:00 2001 From: Joshua Rowe Date: Fri, 19 Dec 2025 14:14:01 -0800 Subject: [PATCH 10/46] traces_final_suspend test works --- Phantom.Coroutines.Test/tracing_test.cpp | 228 ++++++++++++++++++++--- 1 file changed, 202 insertions(+), 26 deletions(-) diff --git a/Phantom.Coroutines.Test/tracing_test.cpp b/Phantom.Coroutines.Test/tracing_test.cpp index 4e235bd..4584e8f 100644 --- a/Phantom.Coroutines.Test/tracing_test.cpp +++ b/Phantom.Coroutines.Test/tracing_test.cpp @@ -145,13 +145,18 @@ struct tracing_tests : testing::Test } void ExpectIsFinalSuspend( - const auto* event + const auto& event ) { - EXPECT_EQ(false, event->is_initial_suspend); - EXPECT_EQ(false, event->is_co_await); - EXPECT_EQ(false, event->is_co_yield); - EXPECT_EQ(true, event->is_final_suspend); + static_assert(std::same_as< + decltype(event.EventData.AwaiterType), + events::final_suspend_awaiter_type + >); + + EXPECT_EQ(false, event.EventData.AwaiterType.is_traced_promise_initial_suspend_awaiter); + EXPECT_EQ(false, event.EventData.AwaiterType.is_traced_promise_co_await_awaiter); + EXPECT_EQ(false, event.EventData.AwaiterType.is_traced_promise_co_yield_awaiter); + EXPECT_EQ(true, event.EventData.AwaiterType.is_traced_promise_final_suspend_awaiter); } void ExpectIsCoAwait( @@ -481,7 +486,7 @@ ASYNC_TEST_F(tracing_tests, traces_create_and_destroy_promise) actualEventAny ); } - else if(eventIndex == ++checkingIndex) + else if (eventIndex == ++checkingIndex) { auto expectedEventData = events::event_data { @@ -511,15 +516,7 @@ ASYNC_TEST_F(tracing_tests, traces_create_and_destroy_promise) ASYNC_TEST_F(tracing_tests, traces_initial_suspend) { - auto filter = filters::event_type_filter< - events::await_ready_begin, - events::await_ready_result, - events::await_suspend_begin, - events::await_suspend_result, - events::await_resume_begin, - events::await_resume_result - > - && + auto filter = filters::awaiter_type_filter; auto taskLambda = []( @@ -527,9 +524,9 @@ ASYNC_TEST_F(tracing_tests, traces_initial_suspend) traced_events_checker& eventsChecker, decltype(filter) ) -> test_traced_task<> - { - co_return; - }; + { + co_return; + }; using expected_traced_promise_type = std::coroutine_traits< test_traced_task<>, @@ -552,7 +549,7 @@ ASYNC_TEST_F(tracing_tests, traces_initial_suspend) { ++eventIndex; auto checkingIndex = 0; - + if (eventIndex == ++checkingIndex) { auto expectedEventData = events::event_data @@ -570,14 +567,14 @@ ASYNC_TEST_F(tracing_tests, traces_initial_suspend) ); auto& actualEvent = CastEventType(actualEventAny, expectedEvent); - + expectedPromise = actualEvent.EventData.Promise; expectedTracedPromise = actualEvent.EventData.TracedPromise; expectedAwaiter = actualEvent.EventData.Awaiter; - + ExpectIsInitialSuspend(actualEvent); } - else if(eventIndex == ++checkingIndex) + else if (eventIndex == ++checkingIndex) { auto expectedEventData = events::event_data { @@ -597,7 +594,7 @@ ASYNC_TEST_F(tracing_tests, traces_initial_suspend) auto& actualEvent = CastEventType(actualEventAny, expectedEvent); ExpectIsInitialSuspend(actualEvent); } - else if(eventIndex == ++checkingIndex) + else if (eventIndex == ++checkingIndex) { auto expectedEventData = events::event_data { @@ -619,7 +616,7 @@ ASYNC_TEST_F(tracing_tests, traces_initial_suspend) auto& actualEvent = CastEventType(actualEventAny, expectedEvent); ExpectIsInitialSuspend(actualEvent); } - else if(eventIndex == ++checkingIndex) + else if (eventIndex == ++checkingIndex) { auto expectedEventData = events::event_data { @@ -641,7 +638,7 @@ ASYNC_TEST_F(tracing_tests, traces_initial_suspend) auto& actualEvent = CastEventType(actualEventAny, expectedEvent); ExpectIsInitialSuspend(actualEvent); } - else if(eventIndex == ++checkingIndex) + else if (eventIndex == ++checkingIndex) { auto expectedEventData = events::event_data { @@ -660,7 +657,7 @@ ASYNC_TEST_F(tracing_tests, traces_initial_suspend) auto& actualEvent = CastEventType(actualEventAny, expectedEvent); ExpectIsInitialSuspend(actualEvent); } - else if(eventIndex == ++checkingIndex) + else if (eventIndex == ++checkingIndex) { auto expectedEventData = events::event_data { @@ -688,4 +685,183 @@ ASYNC_TEST_F(tracing_tests, traces_initial_suspend) co_await taskLambda(tracedEventsChecker, filter); } +ASYNC_TEST_F(tracing_tests, traces_final_suspend) +{ + auto filter = filters::event_type_filter< + events::await_ready_begin, + events::await_ready_result, + events::await_suspend_begin, + events::await_suspend_result, + events::await_resume_begin, + events::await_resume_result + > + && + filters::awaiter_type_filter; + + auto taskLambda = []( + this auto& self, + traced_events_checker& eventsChecker, + decltype(filter) + ) -> test_traced_task<> + { + co_return; + }; + + using expected_traced_promise_type = std::coroutine_traits< + test_traced_task<>, + traced_events_checker&, + decltype(filter) + >::promise_type; + + using expected_underlying_promise_type = test_underlying_promise; + + // The unwrapped awaiter type - what the underlying promise's final_suspend() returns + using unwrapped_awaiter_type = std::suspend_always; + + expected_underlying_promise_type* expectedPromise = nullptr; + expected_traced_promise_type* expectedTracedPromise = nullptr; + unwrapped_awaiter_type* expectedAwaiter = nullptr; + + auto eventIndex = 0; + traced_events_checker tracedEventsChecker; + tracedEventsChecker = { [&](const std::any& actualEventAny) + { + ++eventIndex; + auto checkingIndex = 0; + + if (eventIndex == ++checkingIndex) + { + auto expectedEventData = events::event_data + { + .Promise = static_cast(nullptr), + .TracedPromise = static_cast(nullptr), + .Awaiter = static_cast(nullptr), + .AwaiterType = events::final_suspend_awaiter_type{}, + }; + + auto expectedEvent = basic_event( + std::source_location(), + events::await_ready_begin{}, + expectedEventData + ); + + auto& actualEvent = CastEventType(actualEventAny, expectedEvent); + + expectedPromise = actualEvent.EventData.Promise; + expectedTracedPromise = actualEvent.EventData.TracedPromise; + expectedAwaiter = actualEvent.EventData.Awaiter; + + ExpectIsFinalSuspend(actualEvent); + } + else if (eventIndex == ++checkingIndex) + { + auto expectedEventData = events::event_data + { + .Promise = expectedPromise, + .TracedPromise = expectedTracedPromise, + .Awaiter = expectedAwaiter, + .AwaiterType = events::final_suspend_awaiter_type{}, + .MethodResult = events::value { false }, + }; + + auto expectedEvent = basic_event( + std::source_location(), + events::await_ready_result{}, + expectedEventData + ); + + auto& actualEvent = CastEventType(actualEventAny, expectedEvent); + ExpectIsFinalSuspend(actualEvent); + } + else if (eventIndex == ++checkingIndex) + { + auto expectedEventData = events::event_data + { + .Promise = expectedPromise, + .TracedPromise = expectedTracedPromise, + .Awaiter = expectedAwaiter, + .AwaiterType = events::final_suspend_awaiter_type{}, + .MethodArguments = events::make_arguments( + std::coroutine_handle::from_promise(*expectedTracedPromise) + ) + }; + + auto expectedEvent = basic_event( + std::source_location(), + events::await_suspend_begin{}, + expectedEventData + ); + + auto& actualEvent = CastEventType(actualEventAny, expectedEvent); + ExpectIsFinalSuspend(actualEvent); + } + else if (eventIndex == ++checkingIndex) + { + auto expectedEventData = events::event_data + { + .Promise = expectedPromise, + .TracedPromise = expectedTracedPromise, + .Awaiter = expectedAwaiter, + .AwaiterType = events::final_suspend_awaiter_type{}, + .MethodArguments = events::make_arguments( + std::coroutine_handle::from_promise(*expectedTracedPromise) + ) + }; + + auto expectedEvent = basic_event( + std::source_location(), + events::await_suspend_result{}, + expectedEventData + ); + + auto& actualEvent = CastEventType(actualEventAny, expectedEvent); + ExpectIsFinalSuspend(actualEvent); + } + else if (eventIndex == ++checkingIndex) + { + auto expectedEventData = events::event_data + { + .Promise = expectedPromise, + .TracedPromise = expectedTracedPromise, + .Awaiter = expectedAwaiter, + .AwaiterType = events::final_suspend_awaiter_type{}, + }; + + auto expectedEvent = basic_event( + std::source_location(), + events::await_resume_begin{}, + expectedEventData + ); + + auto& actualEvent = CastEventType(actualEventAny, expectedEvent); + ExpectIsFinalSuspend(actualEvent); + } + else if (eventIndex == ++checkingIndex) + { + auto expectedEventData = events::event_data + { + .Promise = expectedPromise, + .TracedPromise = expectedTracedPromise, + .Awaiter = expectedAwaiter, + .AwaiterType = events::final_suspend_awaiter_type{}, + }; + + auto expectedEvent = basic_event( + std::source_location(), + events::await_resume_result{}, + expectedEventData + ); + + auto& actualEvent = CastEventType(actualEventAny, expectedEvent); + ExpectIsFinalSuspend(actualEvent); + } + else + { + EXPECT_FALSE(true); + } + } }; + + co_await taskLambda(tracedEventsChecker, filter); } + +} // namespace Phantom::Coroutines::tracing From 7b760cb81f711eeaed983fae326c8f2919c71db3 Mon Sep 17 00:00:00 2001 From: Joshua Rowe Date: Fri, 19 Dec 2025 16:46:08 -0800 Subject: [PATCH 11/46] Add expect_events_equal to traces_final_suspend, traces_initial_suspend tests --- Phantom.Coroutines.Test/tracing_test.cpp | 212 +++++++----------- .../include/Phantom.Coroutines/tracing.h | 5 +- 2 files changed, 90 insertions(+), 127 deletions(-) diff --git a/Phantom.Coroutines.Test/tracing_test.cpp b/Phantom.Coroutines.Test/tracing_test.cpp index 4584e8f..500dd96 100644 --- a/Phantom.Coroutines.Test/tracing_test.cpp +++ b/Phantom.Coroutines.Test/tracing_test.cpp @@ -129,56 +129,6 @@ struct tracing_tests : testing::Test return CastEventType(anyEvent); } - void ExpectIsInitialSuspend( - const auto& event - ) - { - static_assert(std::same_as< - decltype(event.EventData.AwaiterType), - events::initial_suspend_awaiter_type - >); - - EXPECT_EQ(true, event.EventData.AwaiterType.is_traced_promise_initial_suspend_awaiter); - EXPECT_EQ(false, event.EventData.AwaiterType.is_traced_promise_co_await_awaiter); - EXPECT_EQ(false, event.EventData.AwaiterType.is_traced_promise_co_yield_awaiter); - EXPECT_EQ(false, event.EventData.AwaiterType.is_traced_promise_final_suspend_awaiter); - } - - void ExpectIsFinalSuspend( - const auto& event - ) - { - static_assert(std::same_as< - decltype(event.EventData.AwaiterType), - events::final_suspend_awaiter_type - >); - - EXPECT_EQ(false, event.EventData.AwaiterType.is_traced_promise_initial_suspend_awaiter); - EXPECT_EQ(false, event.EventData.AwaiterType.is_traced_promise_co_await_awaiter); - EXPECT_EQ(false, event.EventData.AwaiterType.is_traced_promise_co_yield_awaiter); - EXPECT_EQ(true, event.EventData.AwaiterType.is_traced_promise_final_suspend_awaiter); - } - - void ExpectIsCoAwait( - const auto* event - ) - { - EXPECT_EQ(false, event->is_initial_suspend); - EXPECT_EQ(true, event->is_co_await); - EXPECT_EQ(false, event->is_co_yield); - EXPECT_EQ(false, event->is_final_suspend); - } - - void ExpectIsCoYield( - const auto* event - ) - { - EXPECT_EQ(false, event->is_initial_suspend); - EXPECT_EQ(false, event->is_co_await); - EXPECT_EQ(true, event->is_co_yield); - EXPECT_EQ(false, event->is_final_suspend); - } - struct test_awaiter_type_lambdas { static constexpr auto await_ready_false = []() { return false; }; @@ -372,6 +322,22 @@ struct tracing_tests : testing::Test { auto& actualEvent = CastEventType(actualAny, expectedEvent); + if (expectedEvent.EventData.Promise != actualEvent.EventData.Promise) + { + return false; + } + if (expectedEvent.EventData.TracedPromise != actualEvent.EventData.TracedPromise) + { + return false; + } + if (expectedEvent.EventData.Awaiter != actualEvent.EventData.Awaiter) + { + return false; + } + if (expectedEvent.EventData.TracedAwaiter != actualEvent.EventData.TracedAwaiter) + { + return false; + } if (expectedEvent.EventType != actualEvent.EventType) { return false; @@ -538,10 +504,12 @@ ASYNC_TEST_F(tracing_tests, traces_initial_suspend) // The unwrapped awaiter type - what the underlying promise's initial_suspend() returns using unwrapped_awaiter_type = std::suspend_always; + using expected_traced_awaiter_type = decltype(std::declval().initial_suspend()); expected_underlying_promise_type* expectedPromise = nullptr; expected_traced_promise_type* expectedTracedPromise = nullptr; unwrapped_awaiter_type* expectedAwaiter = nullptr; + expected_traced_awaiter_type* expectedTracedAwaiter = nullptr; auto eventIndex = 0; traced_events_checker tracedEventsChecker; @@ -557,6 +525,7 @@ ASYNC_TEST_F(tracing_tests, traces_initial_suspend) .Promise = static_cast(nullptr), .TracedPromise = static_cast(nullptr), .Awaiter = static_cast(nullptr), + .TracedAwaiter = static_cast(nullptr), .AwaiterType = events::initial_suspend_awaiter_type{}, }; @@ -568,11 +537,15 @@ ASYNC_TEST_F(tracing_tests, traces_initial_suspend) auto& actualEvent = CastEventType(actualEventAny, expectedEvent); - expectedPromise = actualEvent.EventData.Promise; - expectedTracedPromise = actualEvent.EventData.TracedPromise; - expectedAwaiter = actualEvent.EventData.Awaiter; + expectedPromise = expectedEvent.EventData.Promise = actualEvent.EventData.Promise; + expectedTracedPromise = expectedEvent.EventData.TracedPromise = actualEvent.EventData.TracedPromise; + expectedAwaiter = expectedEvent.EventData.Awaiter = actualEvent.EventData.Awaiter; + expectedTracedAwaiter = expectedEvent.EventData.TracedAwaiter = actualEvent.EventData.TracedAwaiter; - ExpectIsInitialSuspend(actualEvent); + expect_events_equal( + expectedEvent, + actualEventAny + ); } else if (eventIndex == ++checkingIndex) { @@ -581,6 +554,7 @@ ASYNC_TEST_F(tracing_tests, traces_initial_suspend) .Promise = expectedPromise, .TracedPromise = expectedTracedPromise, .Awaiter = expectedAwaiter, + .TracedAwaiter = expectedTracedAwaiter, .AwaiterType = events::initial_suspend_awaiter_type{}, .MethodResult = events::value { false }, }; @@ -591,8 +565,10 @@ ASYNC_TEST_F(tracing_tests, traces_initial_suspend) expectedEventData ); - auto& actualEvent = CastEventType(actualEventAny, expectedEvent); - ExpectIsInitialSuspend(actualEvent); + expect_events_equal( + expectedEvent, + actualEventAny + ); } else if (eventIndex == ++checkingIndex) { @@ -601,6 +577,7 @@ ASYNC_TEST_F(tracing_tests, traces_initial_suspend) .Promise = expectedPromise, .TracedPromise = expectedTracedPromise, .Awaiter = expectedAwaiter, + .TracedAwaiter = expectedTracedAwaiter, .AwaiterType = events::initial_suspend_awaiter_type{}, .MethodArguments = events::make_arguments( std::coroutine_handle::from_promise(*expectedTracedPromise) @@ -613,8 +590,10 @@ ASYNC_TEST_F(tracing_tests, traces_initial_suspend) expectedEventData ); - auto& actualEvent = CastEventType(actualEventAny, expectedEvent); - ExpectIsInitialSuspend(actualEvent); + expect_events_equal( + expectedEvent, + actualEventAny + ); } else if (eventIndex == ++checkingIndex) { @@ -623,6 +602,7 @@ ASYNC_TEST_F(tracing_tests, traces_initial_suspend) .Promise = expectedPromise, .TracedPromise = expectedTracedPromise, .Awaiter = expectedAwaiter, + .TracedAwaiter = expectedTracedAwaiter, .AwaiterType = events::initial_suspend_awaiter_type{}, .MethodArguments = events::make_arguments( std::coroutine_handle::from_promise(*expectedTracedPromise) @@ -635,8 +615,10 @@ ASYNC_TEST_F(tracing_tests, traces_initial_suspend) expectedEventData ); - auto& actualEvent = CastEventType(actualEventAny, expectedEvent); - ExpectIsInitialSuspend(actualEvent); + expect_events_equal( + expectedEvent, + actualEventAny + ); } else if (eventIndex == ++checkingIndex) { @@ -645,6 +627,7 @@ ASYNC_TEST_F(tracing_tests, traces_initial_suspend) .Promise = expectedPromise, .TracedPromise = expectedTracedPromise, .Awaiter = expectedAwaiter, + .TracedAwaiter = expectedTracedAwaiter, .AwaiterType = events::initial_suspend_awaiter_type{}, }; @@ -654,8 +637,10 @@ ASYNC_TEST_F(tracing_tests, traces_initial_suspend) expectedEventData ); - auto& actualEvent = CastEventType(actualEventAny, expectedEvent); - ExpectIsInitialSuspend(actualEvent); + expect_events_equal( + expectedEvent, + actualEventAny + ); } else if (eventIndex == ++checkingIndex) { @@ -664,6 +649,7 @@ ASYNC_TEST_F(tracing_tests, traces_initial_suspend) .Promise = expectedPromise, .TracedPromise = expectedTracedPromise, .Awaiter = expectedAwaiter, + .TracedAwaiter = expectedTracedAwaiter, .AwaiterType = events::initial_suspend_awaiter_type{}, }; @@ -673,8 +659,10 @@ ASYNC_TEST_F(tracing_tests, traces_initial_suspend) expectedEventData ); - auto& actualEvent = CastEventType(actualEventAny, expectedEvent); - ExpectIsInitialSuspend(actualEvent); + expect_events_equal( + expectedEvent, + actualEventAny + ); } else { @@ -683,20 +671,12 @@ ASYNC_TEST_F(tracing_tests, traces_initial_suspend) } }; co_await taskLambda(tracedEventsChecker, filter); + EXPECT_EQ(eventIndex, 6); } ASYNC_TEST_F(tracing_tests, traces_final_suspend) { - auto filter = filters::event_type_filter< - events::await_ready_begin, - events::await_ready_result, - events::await_suspend_begin, - events::await_suspend_result, - events::await_resume_begin, - events::await_resume_result - > - && - filters::awaiter_type_filter; + auto filter = filters::awaiter_type_filter; auto taskLambda = []( this auto& self, @@ -716,11 +696,13 @@ ASYNC_TEST_F(tracing_tests, traces_final_suspend) using expected_underlying_promise_type = test_underlying_promise; // The unwrapped awaiter type - what the underlying promise's final_suspend() returns - using unwrapped_awaiter_type = std::suspend_always; + using expected_awaiter_type = final_suspend_transfer; + using expected_traced_awaiter_type = decltype(std::declval().final_suspend()); expected_underlying_promise_type* expectedPromise = nullptr; expected_traced_promise_type* expectedTracedPromise = nullptr; - unwrapped_awaiter_type* expectedAwaiter = nullptr; + expected_awaiter_type* expectedAwaiter = nullptr; + expected_traced_awaiter_type* expectedTracedAwaiter = nullptr; auto eventIndex = 0; traced_events_checker tracedEventsChecker; @@ -735,7 +717,8 @@ ASYNC_TEST_F(tracing_tests, traces_final_suspend) { .Promise = static_cast(nullptr), .TracedPromise = static_cast(nullptr), - .Awaiter = static_cast(nullptr), + .Awaiter = static_cast(nullptr), + .TracedAwaiter = static_cast(nullptr), .AwaiterType = events::final_suspend_awaiter_type{}, }; @@ -747,11 +730,15 @@ ASYNC_TEST_F(tracing_tests, traces_final_suspend) auto& actualEvent = CastEventType(actualEventAny, expectedEvent); - expectedPromise = actualEvent.EventData.Promise; - expectedTracedPromise = actualEvent.EventData.TracedPromise; - expectedAwaiter = actualEvent.EventData.Awaiter; + expectedPromise = expectedEvent.EventData.Promise = actualEvent.EventData.Promise; + expectedTracedPromise = expectedEvent.EventData.TracedPromise = actualEvent.EventData.TracedPromise; + expectedAwaiter = expectedEvent.EventData.Awaiter = actualEvent.EventData.Awaiter; + expectedTracedAwaiter = expectedEvent.EventData.TracedAwaiter = actualEvent.EventData.TracedAwaiter; - ExpectIsFinalSuspend(actualEvent); + expect_events_equal( + expectedEvent, + actualEventAny + ); } else if (eventIndex == ++checkingIndex) { @@ -760,6 +747,7 @@ ASYNC_TEST_F(tracing_tests, traces_final_suspend) .Promise = expectedPromise, .TracedPromise = expectedTracedPromise, .Awaiter = expectedAwaiter, + .TracedAwaiter = expectedTracedAwaiter, .AwaiterType = events::final_suspend_awaiter_type{}, .MethodResult = events::value { false }, }; @@ -770,8 +758,10 @@ ASYNC_TEST_F(tracing_tests, traces_final_suspend) expectedEventData ); - auto& actualEvent = CastEventType(actualEventAny, expectedEvent); - ExpectIsFinalSuspend(actualEvent); + expect_events_equal( + expectedEvent, + actualEventAny + ); } else if (eventIndex == ++checkingIndex) { @@ -780,6 +770,7 @@ ASYNC_TEST_F(tracing_tests, traces_final_suspend) .Promise = expectedPromise, .TracedPromise = expectedTracedPromise, .Awaiter = expectedAwaiter, + .TracedAwaiter = expectedTracedAwaiter, .AwaiterType = events::final_suspend_awaiter_type{}, .MethodArguments = events::make_arguments( std::coroutine_handle::from_promise(*expectedTracedPromise) @@ -792,8 +783,10 @@ ASYNC_TEST_F(tracing_tests, traces_final_suspend) expectedEventData ); - auto& actualEvent = CastEventType(actualEventAny, expectedEvent); - ExpectIsFinalSuspend(actualEvent); + expect_events_equal( + expectedEvent, + actualEventAny + ); } else if (eventIndex == ++checkingIndex) { @@ -802,10 +795,12 @@ ASYNC_TEST_F(tracing_tests, traces_final_suspend) .Promise = expectedPromise, .TracedPromise = expectedTracedPromise, .Awaiter = expectedAwaiter, + .TracedAwaiter = expectedTracedAwaiter, .AwaiterType = events::final_suspend_awaiter_type{}, .MethodArguments = events::make_arguments( std::coroutine_handle::from_promise(*expectedTracedPromise) - ) + ), + .MethodResult = events::value{ std::coroutine_handle{} }, }; auto expectedEvent = basic_event( @@ -814,46 +809,10 @@ ASYNC_TEST_F(tracing_tests, traces_final_suspend) expectedEventData ); - auto& actualEvent = CastEventType(actualEventAny, expectedEvent); - ExpectIsFinalSuspend(actualEvent); - } - else if (eventIndex == ++checkingIndex) - { - auto expectedEventData = events::event_data - { - .Promise = expectedPromise, - .TracedPromise = expectedTracedPromise, - .Awaiter = expectedAwaiter, - .AwaiterType = events::final_suspend_awaiter_type{}, - }; - - auto expectedEvent = basic_event( - std::source_location(), - events::await_resume_begin{}, - expectedEventData - ); - - auto& actualEvent = CastEventType(actualEventAny, expectedEvent); - ExpectIsFinalSuspend(actualEvent); - } - else if (eventIndex == ++checkingIndex) - { - auto expectedEventData = events::event_data - { - .Promise = expectedPromise, - .TracedPromise = expectedTracedPromise, - .Awaiter = expectedAwaiter, - .AwaiterType = events::final_suspend_awaiter_type{}, - }; - - auto expectedEvent = basic_event( - std::source_location(), - events::await_resume_result{}, - expectedEventData + expect_events_equal( + expectedEvent, + actualEventAny ); - - auto& actualEvent = CastEventType(actualEventAny, expectedEvent); - ExpectIsFinalSuspend(actualEvent); } else { @@ -862,6 +821,9 @@ ASYNC_TEST_F(tracing_tests, traces_final_suspend) } }; co_await taskLambda(tracedEventsChecker, filter); + // Note that await_resume is not expected for final_suspend_transfer, + // so we only expect await_ready, await_suspend events. + EXPECT_EQ(eventIndex, 4); } } // namespace Phantom::Coroutines::tracing diff --git a/Phantom.Coroutines/include/Phantom.Coroutines/tracing.h b/Phantom.Coroutines/include/Phantom.Coroutines/tracing.h index 55ba91d..9d7b8a4 100644 --- a/Phantom.Coroutines/include/Phantom.Coroutines/tracing.h +++ b/Phantom.Coroutines/include/Phantom.Coroutines/tracing.h @@ -828,6 +828,7 @@ struct traced_awaiter : events::event_data { .Awaiter = static_cast(&awaiter.awaiter()), + .TracedAwaiter = &awaiter, .AwaiterType = AwaiterType{}, }), call); @@ -1578,8 +1579,8 @@ struct awaiter_type_filter_fn : filter const auto& event ) const noexcept requires std::same_as< - typename std::remove_cvref_t::event_data_type::awaiter_type, - AwaiterType + const decltype(event.EventData.AwaiterType)&, + const AwaiterType& > { return {}; From f1aaae0a3cc011d4e49558882b7f4e4894f0824b Mon Sep 17 00:00:00 2001 From: Joshua Rowe Date: Fri, 19 Dec 2025 17:07:26 -0800 Subject: [PATCH 12/46] Added tracing_tests traces_await_ready_exception Fixed bug in tracing exceptions --- Phantom.Coroutines.Test/tracing_test.cpp | 127 +++++++++++++++++- .../include/Phantom.Coroutines/tracing.h | 21 +-- 2 files changed, 135 insertions(+), 13 deletions(-) diff --git a/Phantom.Coroutines.Test/tracing_test.cpp b/Phantom.Coroutines.Test/tracing_test.cpp index 500dd96..1065e00 100644 --- a/Phantom.Coroutines.Test/tracing_test.cpp +++ b/Phantom.Coroutines.Test/tracing_test.cpp @@ -395,7 +395,7 @@ namespace Phantom::Coroutines::tracing ASYNC_TEST_F(tracing_tests, traces_create_and_destroy_promise) { - auto filter = filters::event_type_filter< + auto filter = filters::event_type< events::create_promise, events::destroy_promise >; @@ -483,7 +483,7 @@ ASYNC_TEST_F(tracing_tests, traces_create_and_destroy_promise) ASYNC_TEST_F(tracing_tests, traces_initial_suspend) { auto filter = - filters::awaiter_type_filter; + filters::awaiter_type; auto taskLambda = []( this auto& self, @@ -676,7 +676,7 @@ ASYNC_TEST_F(tracing_tests, traces_initial_suspend) ASYNC_TEST_F(tracing_tests, traces_final_suspend) { - auto filter = filters::awaiter_type_filter; + auto filter = filters::awaiter_type; auto taskLambda = []( this auto& self, @@ -826,4 +826,125 @@ ASYNC_TEST_F(tracing_tests, traces_final_suspend) EXPECT_EQ(eventIndex, 4); } +ASYNC_TEST_F(tracing_tests, traces_await_ready_exception) +{ + auto filter = + filters::awaiter_type + && + filters::event_type< + events::await_ready_begin, + events::await_ready_result, + events::await_ready_exception + >; + + auto awaiter = test_awaiter().with_await_ready_exception( + std::runtime_error{ "await_ready exception" }); + + auto taskLambda = [&]( + this auto& self, + traced_events_checker& eventsChecker, + decltype(filter) + ) -> test_traced_task<> + { + co_await awaiter; + }; + + using expected_traced_promise_type = std::coroutine_traits< + test_traced_task<> + , traced_events_checker& + , decltype(filter) + >::promise_type; + + using expected_underlying_promise_type = test_underlying_promise; + + using expected_awaiter_type = decltype(awaiter); + using expected_traced_awaiter_type = decltype(std::declval().await_transform(awaiter)); + + expected_underlying_promise_type* expectedPromise = nullptr; + expected_traced_promise_type* expectedTracedPromise = nullptr; + expected_awaiter_type* expectedAwaiter = nullptr; + expected_traced_awaiter_type* expectedTracedAwaiter = nullptr; + + auto eventIndex = 0; + traced_events_checker tracedEventsChecker; + tracedEventsChecker = { [&](const std::any& actualEventAny) + { + ++eventIndex; + auto checkingIndex = 0; + + if (eventIndex == ++checkingIndex) + { + auto expectedEventData = events::event_data + { + .Promise = static_cast(nullptr), + .TracedPromise = static_cast(nullptr), + .Awaiter = static_cast(nullptr), + .TracedAwaiter = static_cast(nullptr), + .AwaiterType = events::co_await_awaiter_type{}, + }; + + auto expectedEvent = basic_event( + std::source_location(), + events::await_ready_begin{}, + expectedEventData + ); + + auto& actualEvent = CastEventType(actualEventAny, expectedEvent); + + expectedPromise = expectedEvent.EventData.Promise = actualEvent.EventData.Promise; + expectedTracedPromise = expectedEvent.EventData.TracedPromise = actualEvent.EventData.TracedPromise; + expectedAwaiter = expectedEvent.EventData.Awaiter = actualEvent.EventData.Awaiter; + expectedTracedAwaiter = expectedEvent.EventData.TracedAwaiter = actualEvent.EventData.TracedAwaiter; + + expect_events_equal( + expectedEvent, + actualEventAny + ); + } + else if (eventIndex == ++checkingIndex) + { + std::exception_ptr actualException; + + auto expectedEventData = events::event_data + { + .Promise = expectedPromise, + .TracedPromise = expectedTracedPromise, + .Awaiter = expectedAwaiter, + .TracedAwaiter = expectedTracedAwaiter, + .AwaiterType = events::co_await_awaiter_type{}, + .MethodException = events::value{ actualException } + }; + + auto expectedEvent = basic_event( + std::source_location(), + events::await_ready_exception{}, + expectedEventData + ); + + auto& actualEvent = CastEventType(actualEventAny, expectedEvent); + actualException = actualEvent.EventData.MethodException.Value; + + expect_events_equal( + expectedEvent, + actualEventAny + ); + + EXPECT_THROW( + std::rethrow_exception( + actualEvent.EventData.MethodException.Value + ), + std::runtime_error + ); + } + else + { + EXPECT_FALSE(true); + } + } }; + + co_await expect_exception(taskLambda(tracedEventsChecker, filter)); + EXPECT_EQ(eventIndex, 2); +} + + } // namespace Phantom::Coroutines::tracing diff --git a/Phantom.Coroutines/include/Phantom.Coroutines/tracing.h b/Phantom.Coroutines/include/Phantom.Coroutines/tracing.h index 9d7b8a4..73c2dfc 100644 --- a/Phantom.Coroutines/include/Phantom.Coroutines/tracing.h +++ b/Phantom.Coroutines/include/Phantom.Coroutines/tracing.h @@ -782,7 +782,7 @@ auto call_traced_method( traceSink( events::basic_event( sourceLocation, - events.ResultEvent, + events.ExceptionEvent, baseEventData.with( events::event_data { @@ -1335,11 +1335,12 @@ class traced_promise return promise.traced_promise_base::await_transform( std::forward(awaiter)); }, + events::co_await_awaiter_type{}, detail::trace_sink_accessor { promise.traced_promise::m_traceSink }, - events::co_await_awaiter_type{} + promise.get_event_data() }; } @@ -1555,7 +1556,7 @@ constexpr any_event_fn any_event{}; template< typename ... EventType > -struct event_type_filter_fn : filter +struct event_type_fn : filter { using filter::operator(); constexpr std::true_type operator()( @@ -1569,10 +1570,15 @@ struct event_type_filter_fn : filter } }; +template< + typename ... EventType +> +event_type_fn event_type; + template< typename AwaiterType > -struct awaiter_type_filter_fn : filter +struct awaiter_type_fn : filter { using filter::operator(); constexpr std::true_type operator()( @@ -1590,12 +1596,7 @@ struct awaiter_type_filter_fn : filter template< typename AwaiterType > -constexpr awaiter_type_filter_fn awaiter_type_filter; - -template< - typename ... EventType -> -event_type_filter_fn event_type_filter; +constexpr awaiter_type_fn awaiter_type; template< typename Event From f8eeab852b03c23c94fae31f9aaf19d88e6ea626 Mon Sep 17 00:00:00 2001 From: Joshua Rowe Date: Fri, 19 Dec 2025 17:10:45 -0800 Subject: [PATCH 13/46] Added tracing_tests traces_await_suspend_exception --- Phantom.Coroutines.Test/tracing_test.cpp | 174 +++++++++++++++++++++++ 1 file changed, 174 insertions(+) diff --git a/Phantom.Coroutines.Test/tracing_test.cpp b/Phantom.Coroutines.Test/tracing_test.cpp index 1065e00..9c1f4ff 100644 --- a/Phantom.Coroutines.Test/tracing_test.cpp +++ b/Phantom.Coroutines.Test/tracing_test.cpp @@ -946,5 +946,179 @@ ASYNC_TEST_F(tracing_tests, traces_await_ready_exception) EXPECT_EQ(eventIndex, 2); } +ASYNC_TEST_F(tracing_tests, traces_await_suspend_exception) +{ + auto filter = + filters::awaiter_type + && + filters::event_type< + events::await_ready_begin, + events::await_ready_result, + events::await_suspend_begin, + events::await_suspend_result, + events::await_suspend_exception + >; + + auto awaiter = test_awaiter() + .with_await_ready_false() + .with_await_suspend_exception(std::runtime_error{ "await_suspend exception" }); + + auto taskLambda = [&]( + this auto& self, + traced_events_checker& eventsChecker, + decltype(filter) + ) -> test_traced_task<> + { + co_await awaiter; + }; + + using expected_traced_promise_type = std::coroutine_traits< + test_traced_task<> + , traced_events_checker& + , decltype(filter) + >::promise_type; + + using expected_underlying_promise_type = test_underlying_promise; + + using expected_awaiter_type = decltype(awaiter); + using expected_traced_awaiter_type = decltype(std::declval().await_transform(awaiter)); + + expected_underlying_promise_type* expectedPromise = nullptr; + expected_traced_promise_type* expectedTracedPromise = nullptr; + expected_awaiter_type* expectedAwaiter = nullptr; + expected_traced_awaiter_type* expectedTracedAwaiter = nullptr; + + auto eventIndex = 0; + traced_events_checker tracedEventsChecker; + tracedEventsChecker = { [&](const std::any& actualEventAny) + { + ++eventIndex; + auto checkingIndex = 0; + + if (eventIndex == ++checkingIndex) + { + auto expectedEventData = events::event_data + { + .Promise = static_cast(nullptr), + .TracedPromise = static_cast(nullptr), + .Awaiter = static_cast(nullptr), + .TracedAwaiter = static_cast(nullptr), + .AwaiterType = events::co_await_awaiter_type{}, + }; + + auto expectedEvent = basic_event( + std::source_location(), + events::await_ready_begin{}, + expectedEventData + ); + + auto& actualEvent = CastEventType(actualEventAny, expectedEvent); + + expectedPromise = expectedEvent.EventData.Promise = actualEvent.EventData.Promise; + expectedTracedPromise = expectedEvent.EventData.TracedPromise = actualEvent.EventData.TracedPromise; + expectedAwaiter = expectedEvent.EventData.Awaiter = actualEvent.EventData.Awaiter; + expectedTracedAwaiter = expectedEvent.EventData.TracedAwaiter = actualEvent.EventData.TracedAwaiter; + + expect_events_equal( + expectedEvent, + actualEventAny + ); + } + else if (eventIndex == ++checkingIndex) + { + auto expectedEventData = events::event_data + { + .Promise = expectedPromise, + .TracedPromise = expectedTracedPromise, + .Awaiter = expectedAwaiter, + .TracedAwaiter = expectedTracedAwaiter, + .AwaiterType = events::co_await_awaiter_type{}, + .MethodResult = events::value { false }, + }; + + auto expectedEvent = basic_event( + std::source_location(), + events::await_ready_result{}, + expectedEventData + ); + + expect_events_equal( + expectedEvent, + actualEventAny + ); + } + else if (eventIndex == ++checkingIndex) + { + auto expectedEventData = events::event_data + { + .Promise = expectedPromise, + .TracedPromise = expectedTracedPromise, + .Awaiter = expectedAwaiter, + .TracedAwaiter = expectedTracedAwaiter, + .AwaiterType = events::co_await_awaiter_type{}, + .MethodArguments = events::make_arguments( + std::coroutine_handle::from_promise(*expectedTracedPromise) + ) + }; + + auto expectedEvent = basic_event( + std::source_location(), + events::await_suspend_begin{}, + expectedEventData + ); + + expect_events_equal( + expectedEvent, + actualEventAny + ); + } + else if (eventIndex == ++checkingIndex) + { + std::exception_ptr actualException; + + auto expectedEventData = events::event_data + { + .Promise = expectedPromise, + .TracedPromise = expectedTracedPromise, + .Awaiter = expectedAwaiter, + .TracedAwaiter = expectedTracedAwaiter, + .AwaiterType = events::co_await_awaiter_type{}, + .MethodArguments = events::make_arguments( + std::coroutine_handle::from_promise(*expectedTracedPromise) + ), + .MethodException = events::value{ actualException } + }; + + auto expectedEvent = basic_event( + std::source_location(), + events::await_suspend_exception{}, + expectedEventData + ); + + auto& actualEvent = CastEventType(actualEventAny, expectedEvent); + actualException = actualEvent.EventData.MethodException.Value; + + expect_events_equal( + expectedEvent, + actualEventAny + ); + + EXPECT_THROW( + std::rethrow_exception( + actualEvent.EventData.MethodException.Value + ), + std::runtime_error + ); + } + else + { + EXPECT_FALSE(true); + } + } }; + + co_await expect_exception(taskLambda(tracedEventsChecker, filter)); + EXPECT_EQ(eventIndex, 4); +} + } // namespace Phantom::Coroutines::tracing From f559ad8ad34dc6fabdd7b9c8bf0ca0c609c16e7f Mon Sep 17 00:00:00 2001 From: Joshua Rowe Date: Fri, 19 Dec 2025 17:14:35 -0800 Subject: [PATCH 14/46] Added tracing_tests traces_await_resume_exception --- Phantom.Coroutines.Test/tracing_test.cpp | 170 +++++++++++++++++++++++ 1 file changed, 170 insertions(+) diff --git a/Phantom.Coroutines.Test/tracing_test.cpp b/Phantom.Coroutines.Test/tracing_test.cpp index 9c1f4ff..564abb8 100644 --- a/Phantom.Coroutines.Test/tracing_test.cpp +++ b/Phantom.Coroutines.Test/tracing_test.cpp @@ -1120,5 +1120,175 @@ ASYNC_TEST_F(tracing_tests, traces_await_suspend_exception) EXPECT_EQ(eventIndex, 4); } +ASYNC_TEST_F(tracing_tests, traces_await_resume_exception) +{ + auto filter = + filters::awaiter_type + && + filters::event_type< + events::await_ready_begin, + events::await_ready_result, + events::await_suspend_begin, + events::await_suspend_result, + events::await_resume_begin, + events::await_resume_exception + >; + + auto awaiter = test_awaiter() + .with_await_ready_true() + .with_await_resume_exception(std::runtime_error{ "await_resume exception" }); + + auto taskLambda = [&]( + this auto& self, + traced_events_checker& eventsChecker, + decltype(filter) + ) -> test_traced_task<> + { + co_await awaiter; + }; + + using expected_traced_promise_type = std::coroutine_traits< + test_traced_task<> + , traced_events_checker& + , decltype(filter) + >::promise_type; + + using expected_underlying_promise_type = test_underlying_promise; + + using expected_awaiter_type = decltype(awaiter); + using expected_traced_awaiter_type = decltype(std::declval().await_transform(awaiter)); + + expected_underlying_promise_type* expectedPromise = nullptr; + expected_traced_promise_type* expectedTracedPromise = nullptr; + expected_awaiter_type* expectedAwaiter = nullptr; + expected_traced_awaiter_type* expectedTracedAwaiter = nullptr; + + auto eventIndex = 0; + traced_events_checker tracedEventsChecker; + tracedEventsChecker = { [&](const std::any& actualEventAny) + { + ++eventIndex; + auto checkingIndex = 0; + + if (eventIndex == ++checkingIndex) + { + auto expectedEventData = events::event_data + { + .Promise = static_cast(nullptr), + .TracedPromise = static_cast(nullptr), + .Awaiter = static_cast(nullptr), + .TracedAwaiter = static_cast(nullptr), + .AwaiterType = events::co_await_awaiter_type{}, + }; + + auto expectedEvent = basic_event( + std::source_location(), + events::await_ready_begin{}, + expectedEventData + ); + + auto& actualEvent = CastEventType(actualEventAny, expectedEvent); + + expectedPromise = expectedEvent.EventData.Promise = actualEvent.EventData.Promise; + expectedTracedPromise = expectedEvent.EventData.TracedPromise = actualEvent.EventData.TracedPromise; + expectedAwaiter = expectedEvent.EventData.Awaiter = actualEvent.EventData.Awaiter; + expectedTracedAwaiter = expectedEvent.EventData.TracedAwaiter = actualEvent.EventData.TracedAwaiter; + + expect_events_equal( + expectedEvent, + actualEventAny + ); + } + else if (eventIndex == ++checkingIndex) + { + auto expectedEventData = events::event_data + { + .Promise = expectedPromise, + .TracedPromise = expectedTracedPromise, + .Awaiter = expectedAwaiter, + .TracedAwaiter = expectedTracedAwaiter, + .AwaiterType = events::co_await_awaiter_type{}, + .MethodResult = events::value { false }, + }; + + auto expectedEvent = basic_event( + std::source_location(), + events::await_ready_result{}, + expectedEventData + ); + + expect_events_equal( + expectedEvent, + actualEventAny + ); + } + else if (eventIndex == ++checkingIndex) + { + auto expectedEventData = events::event_data + { + .Promise = expectedPromise, + .TracedPromise = expectedTracedPromise, + .Awaiter = expectedAwaiter, + .TracedAwaiter = expectedTracedAwaiter, + .AwaiterType = events::co_await_awaiter_type{}, + }; + + auto expectedEvent = basic_event( + std::source_location(), + events::await_resume_begin{}, + expectedEventData + ); + + expect_events_equal( + expectedEvent, + actualEventAny + ); + } + else if (eventIndex == ++checkingIndex) + { + std::exception_ptr actualException; + + auto expectedEventData = events::event_data + { + .Promise = expectedPromise, + .TracedPromise = expectedTracedPromise, + .Awaiter = expectedAwaiter, + .TracedAwaiter = expectedTracedAwaiter, + .AwaiterType = events::co_await_awaiter_type{}, + .MethodException = events::value{ actualException } + }; + + auto expectedEvent = basic_event( + std::source_location(), + events::await_resume_exception{}, + expectedEventData + ); + + auto& actualEvent = CastEventType(actualEventAny, expectedEvent); + actualException = actualEvent.EventData.MethodException.Value; + + expect_events_equal( + expectedEvent, + actualEventAny + ); + + EXPECT_THROW( + std::rethrow_exception( + actualEvent.EventData.MethodException.Value + ), + std::runtime_error + ); + } + else + { + EXPECT_FALSE(true); + } + } }; + + co_await expect_exception(taskLambda(tracedEventsChecker, filter)); + EXPECT_EQ(eventIndex, 4); +} + + } // namespace Phantom::Coroutines::tracing From 871d4abced4dda533f06af56ba0344eaec224f6f Mon Sep 17 00:00:00 2001 From: Joshua Rowe Date: Fri, 19 Dec 2025 17:29:27 -0800 Subject: [PATCH 15/46] Add test for traces_return_value --- Phantom.Coroutines.Test/tracing_test.cpp | 91 +++++++++++++++++++ .../include/Phantom.Coroutines/tracing.h | 25 ++--- 2 files changed, 99 insertions(+), 17 deletions(-) diff --git a/Phantom.Coroutines.Test/tracing_test.cpp b/Phantom.Coroutines.Test/tracing_test.cpp index 564abb8..3686fbd 100644 --- a/Phantom.Coroutines.Test/tracing_test.cpp +++ b/Phantom.Coroutines.Test/tracing_test.cpp @@ -1289,6 +1289,97 @@ ASYNC_TEST_F(tracing_tests, traces_await_resume_exception) EXPECT_EQ(eventIndex, 4); } +ASYNC_TEST_F(tracing_tests, traces_return_value) +{ + auto filter = + filters::event_type< + events::return_value_begin, + events::return_value_result + >; + + std::string expectedReturnValue = "test return value"; + + auto taskLambda = [&]( + this auto& self, + traced_events_checker& eventsChecker, + decltype(filter) + ) -> test_traced_task + { + co_return expectedReturnValue; + }; + + using expected_traced_promise_type = std::coroutine_traits< + test_traced_task + , traced_events_checker& + , decltype(filter) + >::promise_type; + + using expected_underlying_promise_type = test_underlying_promise; + expected_underlying_promise_type* expectedPromise = nullptr; + expected_traced_promise_type* expectedTracedPromise = nullptr; + + auto eventIndex = 0; + traced_events_checker tracedEventsChecker; + tracedEventsChecker = { [&](const std::any& actualEventAny) + { + ++eventIndex; + auto checkingIndex = 0; + + if (eventIndex == ++checkingIndex) + { + auto expectedEventData = events::event_data + { + .Promise = static_cast(nullptr), + .TracedPromise = static_cast(nullptr), + .MethodArguments = events::make_arguments(expectedReturnValue) + }; + + auto expectedEvent = basic_event( + std::source_location(), + events::return_value_begin{}, + expectedEventData + ); + + auto& actualEvent = CastEventType(actualEventAny, expectedEvent); + + expectedPromise = expectedEvent.EventData.Promise = actualEvent.EventData.Promise; + expectedTracedPromise = expectedEvent.EventData.TracedPromise = actualEvent.EventData.TracedPromise; + + expect_events_equal( + expectedEvent, + actualEventAny + ); + } + else if (eventIndex == ++checkingIndex) + { + auto expectedEventData = events::event_data + { + .Promise = expectedPromise, + .TracedPromise = expectedTracedPromise, + .MethodArguments = events::make_arguments(expectedReturnValue) + }; + + auto expectedEvent = basic_event( + std::source_location(), + events::return_value_result{}, + expectedEventData + ); + + expect_events_equal( + expectedEvent, + actualEventAny + ); + } + else + { + EXPECT_FALSE(true); + } + } }; + + auto result = co_await taskLambda(tracedEventsChecker, filter); + EXPECT_EQ(result, expectedReturnValue); + EXPECT_EQ(eventIndex, 2); +} } // namespace Phantom::Coroutines::tracing diff --git a/Phantom.Coroutines/include/Phantom.Coroutines/tracing.h b/Phantom.Coroutines/include/Phantom.Coroutines/tracing.h index 73c2dfc..7b7c142 100644 --- a/Phantom.Coroutines/include/Phantom.Coroutines/tracing.h +++ b/Phantom.Coroutines/include/Phantom.Coroutines/tracing.h @@ -513,18 +513,6 @@ struct await_transform_result {}; PHANTOM_COROUTINES_MODULE_EXPORT struct await_transform_exception {}; PHANTOM_COROUTINES_MODULE_EXPORT -struct initial_suspend_begin {}; -PHANTOM_COROUTINES_MODULE_EXPORT -struct initial_suspend_result {}; -PHANTOM_COROUTINES_MODULE_EXPORT -struct initial_suspend_exception {}; -PHANTOM_COROUTINES_MODULE_EXPORT -struct final_suspend_begin {}; -PHANTOM_COROUTINES_MODULE_EXPORT -struct final_suspend_result {}; -PHANTOM_COROUTINES_MODULE_EXPORT -struct final_suspend_exception {}; -PHANTOM_COROUTINES_MODULE_EXPORT struct unhandled_exception_begin {}; PHANTOM_COROUTINES_MODULE_EXPORT struct unhandled_exception_result {}; @@ -1048,7 +1036,8 @@ class traced_promise_trace_sink_storage promise.traced_promise_trace_sink_storage::m_traceSink, events, sourceLocation, - baseEventData, + baseEventData.with( + promise.get_event_data()), call); } }; @@ -1096,7 +1085,7 @@ class traced_promise_yield_value sourceLocation, events::event_data { - .MethodArguments = make_arguments(value), + .MethodArguments = events::make_arguments(value), }, [&]() { @@ -1128,8 +1117,7 @@ class traced_promise_return_value_or_void std::source_location sourceLocation = std::source_location::current() ) { - return std::forward(promise).template call_promise_method( - std::forward(promise), + return std::forward(promise).call_promise_method( detail::method_events_group { events::return_value_begin{}, @@ -1137,7 +1125,10 @@ class traced_promise_return_value_or_void events::return_value_exception{}, }, sourceLocation, - events::event_data{}, + events::event_data + { + .MethodArguments = events::make_arguments(value), + }, [&]() { return std::forward(promise).traced_promise_return_value_or_void::traced_promise_yield_value::return_value( From 320f17128a88903b09003ed2387f2d2b78b71145 Mon Sep 17 00:00:00 2001 From: Joshua Rowe Date: Fri, 19 Dec 2025 17:42:12 -0800 Subject: [PATCH 16/46] Added test traces_return_value_exception --- Phantom.Coroutines.Test/tracing_test.cpp | 114 ++++++++++++++++++++++- 1 file changed, 113 insertions(+), 1 deletion(-) diff --git a/Phantom.Coroutines.Test/tracing_test.cpp b/Phantom.Coroutines.Test/tracing_test.cpp index 3686fbd..d1c6f9b 100644 --- a/Phantom.Coroutines.Test/tracing_test.cpp +++ b/Phantom.Coroutines.Test/tracing_test.cpp @@ -1382,4 +1382,116 @@ ASYNC_TEST_F(tracing_tests, traces_return_value) EXPECT_EQ(eventIndex, 2); } -} // namespace Phantom::Coroutines::tracing +ASYNC_TEST_F(tracing_tests, traces_return_value_exception) +{ + auto filter = + filters::event_type< + events::return_value_begin, + events::return_value_exception + >; + + struct ThrowOnCopy + { + ThrowOnCopy() = default; + ThrowOnCopy(const ThrowOnCopy&) + { + throw std::runtime_error("return_value exception"); + } + } return_value; + + auto taskLambda = [&]( + this auto& self, + traced_events_checker& eventsChecker, + decltype(filter) + ) -> test_traced_task + { + co_return return_value; + }; + + using expected_traced_promise_type = std::coroutine_traits< + test_traced_task + , traced_events_checker& + , decltype(filter) + >::promise_type; + + using expected_underlying_promise_type = test_underlying_promise; + + expected_underlying_promise_type* expectedPromise = nullptr; + expected_traced_promise_type* expectedTracedPromise = nullptr; + + auto eventIndex = 0; + traced_events_checker tracedEventsChecker; + tracedEventsChecker = { [&](const std::any& actualEventAny) + { + ++eventIndex; + auto checkingIndex = 0; + + if (eventIndex == ++checkingIndex) + { + auto expectedEventData = events::event_data + { + .Promise = static_cast(nullptr), + .TracedPromise = static_cast(nullptr), + .MethodArguments = events::make_arguments(return_value) + }; + + auto expectedEvent = basic_event( + std::source_location(), + events::return_value_begin{}, + expectedEventData + ); + + auto& actualEvent = CastEventType(actualEventAny, expectedEvent); + + expectedPromise = expectedEvent.EventData.Promise = actualEvent.EventData.Promise; + expectedTracedPromise = expectedEvent.EventData.TracedPromise = actualEvent.EventData.TracedPromise; + + expect_events_equal( + expectedEvent, + actualEventAny + ); + } + else if (eventIndex == ++checkingIndex) + { + std::exception_ptr actualException; + + auto expectedEventData = events::event_data + { + .Promise = expectedPromise, + .TracedPromise = expectedTracedPromise, + .MethodArguments = events::make_arguments(return_value), + .MethodException = events::value{ actualException } + }; + + auto expectedEvent = basic_event( + std::source_location(), + events::return_value_exception{}, + expectedEventData + ); + + auto& actualEvent = CastEventType(actualEventAny, expectedEvent); + actualException = actualEvent.EventData.MethodException.Value; + + expect_events_equal( + expectedEvent, + actualEventAny + ); + + EXPECT_THROW( + std::rethrow_exception( + actualEvent.EventData.MethodException.Value + ), + std::runtime_error + ); + } + else + { + EXPECT_FALSE(true); + } + } }; + + co_await expect_exception(taskLambda(tracedEventsChecker, filter)); + EXPECT_EQ(eventIndex, 2); +} + +} // namespace phantom::coroutines::test From 0c407b9a54f4a76ebb4f58b8922fc1e575359aea Mon Sep 17 00:00:00 2001 From: Joshua Rowe Date: Fri, 19 Dec 2025 17:51:51 -0800 Subject: [PATCH 17/46] Added tracing_tests traces_unhandled_exception --- Phantom.Coroutines.Test/tracing_test.cpp | 90 ++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/Phantom.Coroutines.Test/tracing_test.cpp b/Phantom.Coroutines.Test/tracing_test.cpp index d1c6f9b..7745639 100644 --- a/Phantom.Coroutines.Test/tracing_test.cpp +++ b/Phantom.Coroutines.Test/tracing_test.cpp @@ -1494,4 +1494,94 @@ ASYNC_TEST_F(tracing_tests, traces_return_value_exception) EXPECT_EQ(eventIndex, 2); } + +ASYNC_TEST_F(tracing_tests, traces_unhandled_exception) +{ + auto filter = + filters::event_type< + events::unhandled_exception_begin, + events::unhandled_exception_result + >; + + auto taskLambda = [&]( + this auto& self, + traced_events_checker& eventsChecker, + decltype(filter) + ) -> test_traced_task<> + { + throw std::runtime_error("unhandled exception"); + co_return; + }; + + using expected_traced_promise_type = std::coroutine_traits< + test_traced_task<> + , traced_events_checker& + , decltype(filter) + >::promise_type; + + using expected_underlying_promise_type = test_underlying_promise; + + expected_underlying_promise_type* expectedPromise = nullptr; + expected_traced_promise_type* expectedTracedPromise = nullptr; + + auto eventIndex = 0; + traced_events_checker tracedEventsChecker; + tracedEventsChecker = { [&](const std::any& actualEventAny) + { + ++eventIndex; + auto checkingIndex = 0; + + if (eventIndex == ++checkingIndex) + { + auto expectedEventData = events::event_data + { + .Promise = static_cast(nullptr), + .TracedPromise = static_cast(nullptr), + }; + + auto expectedEvent = basic_event( + std::source_location(), + events::unhandled_exception_begin{}, + expectedEventData + ); + + auto& actualEvent = CastEventType(actualEventAny, expectedEvent); + + expectedPromise = expectedEvent.EventData.Promise = actualEvent.EventData.Promise; + expectedTracedPromise = expectedEvent.EventData.TracedPromise = actualEvent.EventData.TracedPromise; + + expect_events_equal( + expectedEvent, + actualEventAny + ); + } + else if (eventIndex == ++checkingIndex) + { + auto expectedEventData = events::event_data + { + .Promise = expectedPromise, + .TracedPromise = expectedTracedPromise, + }; + + auto expectedEvent = basic_event( + std::source_location(), + events::unhandled_exception_result{}, + expectedEventData + ); + + expect_events_equal( + expectedEvent, + actualEventAny + ); + } + else + { + EXPECT_FALSE(true); + } + } }; + + co_await expect_exception(taskLambda(tracedEventsChecker, filter)); + EXPECT_EQ(eventIndex, 2); +} + } // namespace phantom::coroutines::test From a852757e45350731fe348df2fff5d60f400a916c Mon Sep 17 00:00:00 2001 From: Joshua Rowe Date: Mon, 22 Dec 2025 13:45:05 -0800 Subject: [PATCH 18/46] Expose derived_promise_construction --- .../Phantom.Coroutines/extensible_promise.h | 147 +++++++++++------- 1 file changed, 92 insertions(+), 55 deletions(-) diff --git a/Phantom.Coroutines/include/Phantom.Coroutines/extensible_promise.h b/Phantom.Coroutines/include/Phantom.Coroutines/extensible_promise.h index dae5a2a..848f140 100644 --- a/Phantom.Coroutines/include/Phantom.Coroutines/extensible_promise.h +++ b/Phantom.Coroutines/include/Phantom.Coroutines/extensible_promise.h @@ -54,6 +54,80 @@ is_extensible_promise { promise.template promise() } -> std::same_as; }; + +PHANTOM_COROUTINES_MODULE_EXPORT +template< + typename T, + typename ... Args +> +concept is_constructible_from_promise_arguments = + std::constructible_from + && !is_empty_type_list; + +PHANTOM_COROUTINES_MODULE_EXPORT +template< + typename T, + typename ... Args +> +concept is_default_constructible_from_promise_arguments = + ( + !std::constructible_from + || + is_empty_type_list + ) + && std::constructible_from; + +// Derived classes of a promise type should derive from this class to enable +// delegating promise construction arguments to the base promise type. +// If the base promise does not accept the argument set, but does accept +// the defualt constructor, then arguments are not delegated. +PHANTOM_COROUTINES_MODULE_EXPORT +template< + typename Base +> class derived_promise_construction + : + public Base +{ +public: + // Enable construction by delegating the arguments + // to the base class. + // This constructor is enabled only if the base class + // supports the argument set. + template< + typename ... Args + > + requires + is_constructible_from_promise_arguments + derived_promise_construction( + Args&&... args + ) + : + Base( + std::forward(args)... + ) + { + } + + // Enable construction by invoking the default constructor. + // This constructor is enabled only if the base class + // does not support the argument set and default-constructs + // the base class. + // This constructor allows derived promises to always pass + // arguments to derived_promise, even if the base promise + // does not support accepting arguments. + template< + typename ... Args + > requires + is_default_constructible_from_promise_arguments + derived_promise_construction( + Args&&... + ) + : + Base() + { + } +}; + namespace detail { template< @@ -103,45 +177,6 @@ class derived_promise_identity_await_transform } }; -template< - typename Base -> class derived_promise_base - : - public Base -{ -public: - // Enable construction by delegating the arguments - // to the base class. - // This constructor is enabled only if the base class - // supports the argument set. - template< - typename ... Args - > derived_promise_base( - Args&&... args - ) - requires std::constructible_from - : - Base( - std::forward(args)... - ) - {} - - // Enable construction by invoking the default constructor. - // This constructor is enabled only if the base class - // does not support the argument set and default-constructs - // the base class. - template< - typename ... Args - > derived_promise_base( - Args&&... - ) requires ( - !std::constructible_from - && std::constructible_from) - : - Base() - {} -}; - // The template derived_promise_await_transform ensures there is a valid // await_transform() method in the derived_promise implementation, // so that it can always be called unconditionally by derived classes. @@ -151,9 +186,9 @@ template< > class derived_promise_await_transform : public derived_promise_identity_await_transform, -public derived_promise_base +public derived_promise_construction { - using derived_promise_base::derived_promise_base; + using derived_promise_construction::derived_promise_construction; }; template< @@ -165,9 +200,10 @@ class derived_promise_await_transform< BasePromise, Bases... > : - public derived_promise_base + public derived_promise_construction { public: + using derived_promise_construction::derived_promise_construction; }; // A derived_promise is a promise that wraps an extensible_promise @@ -189,7 +225,7 @@ template< > : public derived_promise_await_transform, - public derived_promise_base... + public derived_promise_construction... { protected: using base_promise_type = BasePromise; @@ -204,13 +240,14 @@ template< > derived_promise_impl( Args&&... args ) - requires std::constructible_from, Args&&...> - && (std::constructible_from, Args&&...> && ...) + requires + std::constructible_from, Args&&...> + && (std::constructible_from, Args&&...> && ...) : derived_promise_await_transform( std::forward(args)... ), - derived_promise_base( + derived_promise_construction( std::forward(args)... )... {} @@ -227,15 +264,15 @@ template< > class derived_promise : public detail::derived_promise_impl< - BasePromise, - std::tuple, - typename detail::filter_types - < - detail::has_await_transform_filter, - detail::derived_promise_await_transform, - detail::derived_promise_base... - >::tuple_type -> + BasePromise, + std::tuple, + typename detail::filter_types + < + detail::has_await_transform_filter, + detail::derived_promise_await_transform, + derived_promise_construction... + >::tuple_type + > { using derived_promise::derived_promise_impl::derived_promise_impl; }; From 1fd48e93398668139af011f528d950b7ed78543b Mon Sep 17 00:00:00 2001 From: Joshua Rowe Date: Mon, 22 Dec 2025 13:49:11 -0800 Subject: [PATCH 19/46] Add is_empty_type_list --- Phantom.Coroutines.Test/type_traits_test.cpp | 4 ++++ Phantom.Coroutines/include/Phantom.Coroutines/type_traits.h | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/Phantom.Coroutines.Test/type_traits_test.cpp b/Phantom.Coroutines.Test/type_traits_test.cpp index 478a989..67c3c18 100644 --- a/Phantom.Coroutines.Test/type_traits_test.cpp +++ b/Phantom.Coroutines.Test/type_traits_test.cpp @@ -20,6 +20,10 @@ namespace Phantom::Coroutines::detail namespace { +static_assert(true == is_empty_type_list<>); +static_assert(false == is_empty_type_list); +static_assert(false == is_empty_type_list); + static_assert(false == is_in_types); static_assert(true == is_in_types); static_assert(false == is_in_types); diff --git a/Phantom.Coroutines/include/Phantom.Coroutines/type_traits.h b/Phantom.Coroutines/include/Phantom.Coroutines/type_traits.h index 0ad30a3..15e08b3 100644 --- a/Phantom.Coroutines/include/Phantom.Coroutines/type_traits.h +++ b/Phantom.Coroutines/include/Phantom.Coroutines/type_traits.h @@ -17,6 +17,12 @@ PHANTOM_COROUTINES_ASSERT_IS_MODULE; namespace Phantom::Coroutines { +PHANTOM_COROUTINES_MODULE_EXPORT +template< + typename ... T +> +concept is_empty_type_list = (sizeof...(T) == 0); + namespace detail { PHANTOM_COROUTINES_MODULE_EXPORT From 0f98dc50f297a893a460843ddd02b87abe384a40 Mon Sep 17 00:00:00 2001 From: Joshua Rowe Date: Mon, 22 Dec 2025 13:49:27 -0800 Subject: [PATCH 20/46] Refactor construction of thread_local_contextual_promise --- .../thread_local_contextual_promise.h | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/Phantom.Coroutines/include/Phantom.Coroutines/thread_local_contextual_promise.h b/Phantom.Coroutines/include/Phantom.Coroutines/thread_local_contextual_promise.h index 0f7d052..adbc455 100644 --- a/Phantom.Coroutines/include/Phantom.Coroutines/thread_local_contextual_promise.h +++ b/Phantom.Coroutines/include/Phantom.Coroutines/thread_local_contextual_promise.h @@ -36,20 +36,10 @@ template< private: std::optional m_scope; - std::optional m_value; + std::optional m_value = ThreadLocalContext::current(); public: - template< - typename... Args - > thread_local_contextual_promise( - Args&&... args - ) : - thread_local_contextual_promise::derived_promise{ std::forward(args)... }, - m_value - { - ThreadLocalContext::current() - } - {} + using base_promise::base_promise; void enter() { From c0ef1dcfe5666979e3a2a12fcff72ae95b2e6e49 Mon Sep 17 00:00:00 2001 From: Joshua Rowe Date: Mon, 22 Dec 2025 13:50:27 -0800 Subject: [PATCH 21/46] Refactor tracing promise and add return/void tests --- Phantom.Coroutines.Test/tracing_test.cpp | 307 ++++++++++++++++-- .../include/Phantom.Coroutines/tracing.h | 141 +++++--- 2 files changed, 362 insertions(+), 86 deletions(-) diff --git a/Phantom.Coroutines.Test/tracing_test.cpp b/Phantom.Coroutines.Test/tracing_test.cpp index 7745639..5a3b9b6 100644 --- a/Phantom.Coroutines.Test/tracing_test.cpp +++ b/Phantom.Coroutines.Test/tracing_test.cpp @@ -56,38 +56,117 @@ struct tracing_tests : testing::Test { const traced_events_checker& checker; + template< + typename ... Args + > + requires requires + { + get(std::tie(std::declval()...)); + } trace_sink( - auto&... args + const Args&... args ) : checker(get(std::tie(args...))) { } - trace_sink( - const trace_sink& other - ) : checker{ other.checker } + void operator()(const auto& traceEvent) const { + if (Filter{}(traceEvent)) + { + checker(&traceEvent); + } } + }; - trace_sink( - trace_sink& other - ) : checker{ other.checker } + struct throw_on_return + { + std::exception_ptr exception = std::make_exception_ptr( + std::runtime_error{ + "Exception thrown on return" + } + ); + }; + + template< + typename Result + > + struct controllable_promise_base + : + derived_promise> + { + std::exception_ptr exception_to_throw_on_return; + + using controllable_promise_base::derived_promise::derived_promise; + using derived_promise>::await_transform; + + Coroutines::detail::suspend_never await_transform( + this auto& self, + throw_on_return throwOnReturn) { + self.exception_to_throw_on_return = throwOnReturn.exception; + return {}; } + }; - void operator()(const auto& traceEvent) const + template< + typename Result + > + struct controllable_promise_return_value_or_void + : + controllable_promise_base + { + using controllable_promise_base::controllable_promise_base; + + auto return_value( + auto&& value) { - if (Filter{}(traceEvent)) + if (this->exception_to_throw_on_return) { - checker(&traceEvent); + std::rethrow_exception(this->exception_to_throw_on_return); } + return this->controllable_promise_base::return_value( + std::forward(value)); } }; + template< + > + struct controllable_promise_return_value_or_void< + void + > + : + controllable_promise_base + { + using controllable_promise_base::controllable_promise_base; + + auto return_void() + { + if (this->exception_to_throw_on_return) + { + std::rethrow_exception(this->exception_to_throw_on_return); + } + else + { + return this->controllable_promise_base::return_void(); + } + } + }; + + template< + typename Result + > + struct controllable_promise + : + controllable_promise_return_value_or_void + { + using controllable_promise_return_value_or_void::controllable_promise_return_value_or_void; + }; + template< typename Result > using test_underlying_promise = polymorphic_promise< - task_promise + controllable_promise >; template< @@ -102,7 +181,7 @@ struct tracing_tests : testing::Test template< typename T = void > - using test_traced_task = basic_task>>; + using test_traced_task = basic_task>; template< typename EventType @@ -1390,31 +1469,26 @@ ASYNC_TEST_F(tracing_tests, traces_return_value_exception) events::return_value_exception >; - struct ThrowOnCopy - { - ThrowOnCopy() = default; - ThrowOnCopy(const ThrowOnCopy&) - { - throw std::runtime_error("return_value exception"); - } - } return_value; + std::string return_value = "test return value"; + throw_on_return throw_on_return_awaiter; auto taskLambda = [&]( this auto& self, traced_events_checker& eventsChecker, decltype(filter) - ) -> test_traced_task + ) -> test_traced_task { + co_await throw_on_return_awaiter; co_return return_value; }; using expected_traced_promise_type = std::coroutine_traits< - test_traced_task + test_traced_task , traced_events_checker& , decltype(filter) >::promise_type; - using expected_underlying_promise_type = test_underlying_promise; + using expected_underlying_promise_type = test_underlying_promise; expected_underlying_promise_type* expectedPromise = nullptr; expected_traced_promise_type* expectedTracedPromise = nullptr; @@ -1460,7 +1534,7 @@ ASYNC_TEST_F(tracing_tests, traces_return_value_exception) .Promise = expectedPromise, .TracedPromise = expectedTracedPromise, .MethodArguments = events::make_arguments(return_value), - .MethodException = events::value{ actualException } + .MethodException = events::value { throw_on_return_awaiter.exception }, }; auto expectedEvent = basic_event( @@ -1469,19 +1543,97 @@ ASYNC_TEST_F(tracing_tests, traces_return_value_exception) expectedEventData ); + expect_events_equal( + expectedEvent, + actualEventAny + ); + } + else + { + EXPECT_FALSE(true); + } + } }; + + co_await expect_exception(taskLambda(tracedEventsChecker, filter)); + EXPECT_EQ(eventIndex, 2); +} + +ASYNC_TEST_F(tracing_tests, traces_return_void) +{ + auto filter = + filters::event_type< + events::return_void_begin, + events::return_void_result + >; + + auto taskLambda = [&]( + this auto& self, + traced_events_checker& eventsChecker, + decltype(filter) + ) -> test_traced_task<> + { + co_return; + }; + + using expected_traced_promise_type = std::coroutine_traits< + test_traced_task<> + , traced_events_checker& + , decltype(filter) + >::promise_type; + + using expected_underlying_promise_type = test_underlying_promise; + + expected_underlying_promise_type* expectedPromise = nullptr; + expected_traced_promise_type* expectedTracedPromise = nullptr; + + auto eventIndex = 0; + traced_events_checker tracedEventsChecker; + tracedEventsChecker = { [&](const std::any& actualEventAny) + { + ++eventIndex; + auto checkingIndex = 0; + + if (eventIndex == ++checkingIndex) + { + auto expectedEventData = events::event_data + { + .Promise = static_cast(nullptr), + .TracedPromise = static_cast(nullptr), + }; + + auto expectedEvent = basic_event( + std::source_location(), + events::return_void_begin{}, + expectedEventData + ); + auto& actualEvent = CastEventType(actualEventAny, expectedEvent); - actualException = actualEvent.EventData.MethodException.Value; - + + expectedPromise = expectedEvent.EventData.Promise = actualEvent.EventData.Promise; + expectedTracedPromise = expectedEvent.EventData.TracedPromise = actualEvent.EventData.TracedPromise; + expect_events_equal( expectedEvent, actualEventAny ); + } + else if (eventIndex == ++checkingIndex) + { + auto expectedEventData = events::event_data + { + .Promise = expectedPromise, + .TracedPromise = expectedTracedPromise, + }; - EXPECT_THROW( - std::rethrow_exception( - actualEvent.EventData.MethodException.Value - ), - std::runtime_error + auto expectedEvent = basic_event( + std::source_location(), + events::return_void_result{}, + expectedEventData + ); + + expect_events_equal( + expectedEvent, + actualEventAny ); } else @@ -1490,10 +1642,101 @@ ASYNC_TEST_F(tracing_tests, traces_return_value_exception) } } }; - co_await expect_exception(taskLambda(tracedEventsChecker, filter)); + co_await taskLambda(tracedEventsChecker, filter); EXPECT_EQ(eventIndex, 2); } +ASYNC_TEST_F(tracing_tests, traces_return_void_exception) +{ + auto filter = + filters::event_type< + events::return_void_begin, + events::return_void_exception + >; + + throw_on_return throw_on_return_awaiter; + + auto taskLambda = [&]( + this auto& self, + traced_events_checker& eventsChecker, + decltype(filter) + ) -> test_traced_task<> + { + co_await throw_on_return_awaiter; + co_return; + }; + + using expected_traced_promise_type = std::coroutine_traits< + test_traced_task<> + , traced_events_checker& + , decltype(filter) + >::promise_type; + + using expected_underlying_promise_type = test_underlying_promise; + + expected_underlying_promise_type* expectedPromise = nullptr; + expected_traced_promise_type* expectedTracedPromise = nullptr; + + auto eventIndex = 0; + traced_events_checker tracedEventsChecker; + tracedEventsChecker = { [&](const std::any& actualEventAny) + { + ++eventIndex; + auto checkingIndex = 0; + + if (eventIndex == ++checkingIndex) + { + auto expectedEventData = events::event_data + { + .Promise = static_cast(nullptr), + .TracedPromise = static_cast(nullptr), + }; + + auto expectedEvent = basic_event( + std::source_location(), + events::return_void_begin{}, + expectedEventData + ); + + auto& actualEvent = CastEventType(actualEventAny, expectedEvent); + + expectedPromise = expectedEvent.EventData.Promise = actualEvent.EventData.Promise; + expectedTracedPromise = expectedEvent.EventData.TracedPromise = actualEvent.EventData.TracedPromise; + + expect_events_equal( + expectedEvent, + actualEventAny + ); + } + else if (eventIndex == ++checkingIndex) + { + auto expectedEventData = events::event_data + { + .Promise = expectedPromise, + .TracedPromise = expectedTracedPromise, + .MethodException = events::value{ throw_on_return_awaiter.exception } + }; + + auto expectedEvent = basic_event( + std::source_location(), + events::return_void_exception{}, + expectedEventData + ); + + expect_events_equal( + expectedEvent, + actualEventAny + ); + } + else + { + EXPECT_FALSE(true); + } + } }; + + co_await expect_exception(taskLambda(tracedEventsChecker, filter)); + EXPECT_EQ(eventIndex, 2); +} ASYNC_TEST_F(tracing_tests, traces_unhandled_exception) { diff --git a/Phantom.Coroutines/include/Phantom.Coroutines/tracing.h b/Phantom.Coroutines/include/Phantom.Coroutines/tracing.h index 7b7c142..c9d841c 100644 --- a/Phantom.Coroutines/include/Phantom.Coroutines/tracing.h +++ b/Phantom.Coroutines/include/Phantom.Coroutines/tracing.h @@ -25,6 +25,7 @@ namespace Phantom::Coroutines namespace tracing { +PHANTOM_COROUTINES_MODULE_EXPORT template< typename TraceSink > concept is_trace_sink = true; @@ -150,6 +151,7 @@ struct event : event_data_type EventData; }; +PHANTOM_COROUTINES_MODULE_EXPORT struct empty_value { using value_type = void; @@ -158,6 +160,7 @@ struct empty_value friend auto operator<=>(const empty_value&, const empty_value&) = default; }; +PHANTOM_COROUTINES_MODULE_EXPORT struct empty_arguments_value { using value_type = std::tuple<>; @@ -166,7 +169,9 @@ struct empty_arguments_value friend auto operator<=>(const empty_arguments_value&, const empty_arguments_value&) = default; }; +PHANTOM_COROUTINES_MODULE_EXPORT constexpr empty_value empty{}; +PHANTOM_COROUTINES_MODULE_EXPORT constexpr empty_arguments_value empty_arguments{ }; PHANTOM_COROUTINES_MODULE_EXPORT @@ -946,6 +951,7 @@ namespace detail { // This class stored the trace sink and produces non-result-specific tracing events. +PHANTOM_COROUTINES_MODULE_EXPORT template< is_trace_sink TraceSink > @@ -959,58 +965,32 @@ class traced_promise_trace_sink_storage public: using trace_sink_type = TraceSink; -protected: - TraceSink m_traceSink; +public: + trace_sink_type m_traceSink; template< - typename TPromise + typename ... Args > + requires + is_constructible_from_promise_arguments traced_promise_trace_sink_storage( - TPromise& self, - const auto& ... args - ) - requires std::constructible_from - : - m_traceSink{ args... } + Args&& ... args + ) : + m_traceSink(std::forward(args)...) { - m_traceSink( - events::basic_event( - std::source_location::current(), - events::create_promise{}, - events::event_data - { - .Promise = &self.get_underlying_promise(), - .TracedPromise = &self, - .PromiseCreationArguments = events::make_arguments(args...), - })); } + // Allow default construction of the trace sink when arguments are provided. template< - typename TPromise + typename ... Args > + requires + is_default_constructible_from_promise_arguments traced_promise_trace_sink_storage( - TPromise& self, - auto& ... args - ) - requires ( - !std::constructible_from - && std::constructible_from - ) - : - m_traceSink{} + Args&& ... args + ) : + m_traceSink() { - m_traceSink( - events::event - { - .SourceLocation = std::source_location::current(), - .EventType = events::create_promise{}, - .EventData = events::event_data - { - .Promise = &self.get_underlying_promise(), - .TracedPromise = &self, - } - } - ); } auto get_event_data( @@ -1042,28 +1022,65 @@ class traced_promise_trace_sink_storage } }; +// This class embodies constructors for traced promises. template< is_trace_sink TraceSink, typename BasePromise > -class traced_promise_yield_value +class traced_promise_construction : public traced_promise_trace_sink_storage, public derived_promise { public: template< - typename TPromise + typename ... Args > - traced_promise_yield_value( - TPromise& self, - auto&& ... args - ) : - traced_promise_yield_value::traced_promise_trace_sink_storage{ self, std::forward(args)... }, - traced_promise_yield_value::derived_promise{ std::forward(args)... } + requires + std::constructible_from, Args&&...> + && + std::constructible_from, Args&&...> + traced_promise_construction( + Args&& ... args + ) + : + traced_promise_construction::traced_promise_trace_sink_storage(std::forward(args)...), + traced_promise_construction::derived_promise(std::forward(args)... ) { } +}; +// This class provides an implementation of yield_value for a promise +// if it has a yield_value implementation. +// The main template does not provide yield_value. +template< + is_trace_sink TraceSink, + typename BasePromise +> +class traced_promise_yield_value + : + public traced_promise_construction +{ +public: + using traced_promise_yield_value::traced_promise_construction::traced_promise_construction; +}; + +// The specialization for promises that have yield_value. +template< + is_trace_sink TraceSink, + has_yield_value BasePromise +> +class traced_promise_yield_value< + TraceSink, + BasePromise +> + : + public traced_promise_construction +{ +public: + using traced_promise_yield_value::traced_promise_construction::traced_promise_construction; + + // Trace an delegate the base class's yield_value implementation. template< typename TPromise > @@ -1072,7 +1089,6 @@ class traced_promise_yield_value auto&& value, std::source_location sourceLocation = std::source_location::current() ) - requires has_yield_value { return promise.traced_promise_yield_value::call_promise_method( promise, @@ -1107,6 +1123,8 @@ class traced_promise_return_value_or_void using traced_promise_return_value_or_void::traced_promise_yield_value::traced_promise_yield_value; public: + using traced_promise_return_value_or_void::traced_promise_yield_value::call_promise_method; + template< typename TPromise, typename Value @@ -1150,9 +1168,10 @@ class traced_promise_return_value_or_void< public traced_promise_yield_value { using traced_promise_return_value_or_void::traced_promise_yield_value::traced_promise_yield_value; - using traced_promise_return_value_or_void::traced_promise_yield_value::call_promise_method; public: + using traced_promise_return_value_or_void::traced_promise_yield_value::call_promise_method; + template< typename TPromise > @@ -1229,11 +1248,25 @@ class traced_promise struct traced_promise_trace_sink_accessor; public: + template< + typename ... Args + > traced_promise( - auto&& ... args - ) : - traced_promise_base{ *this, std::forward(args)... } + Args&& ... args + ) + : + traced_promise_base{ std::forward(args)... } { + m_traceSink( + events::basic_event( + std::source_location::current(), + events::create_promise{}, + events::event_data + { + .Promise = &this->get_underlying_promise(), + .TracedPromise = this, + .PromiseCreationArguments = events::make_arguments(args...), + })); } ~traced_promise() From 2a136df0e820667ba492ca32c2a9c28d4c46f7ba Mon Sep 17 00:00:00 2001 From: Joshua Rowe Date: Mon, 22 Dec 2025 18:27:01 -0800 Subject: [PATCH 22/46] Add no_unique_address config macro --- .../include/Phantom.Coroutines/detail/config_macros_clang.h | 2 ++ .../Phantom.Coroutines/detail/config_macros_clang_msvc.h | 2 ++ .../include/Phantom.Coroutines/detail/config_macros_msvc.h | 2 ++ 3 files changed, 6 insertions(+) diff --git a/Phantom.Coroutines/include/Phantom.Coroutines/detail/config_macros_clang.h b/Phantom.Coroutines/include/Phantom.Coroutines/detail/config_macros_clang.h index 8eb6a85..6b2e467 100644 --- a/Phantom.Coroutines/include/Phantom.Coroutines/detail/config_macros_clang.h +++ b/Phantom.Coroutines/include/Phantom.Coroutines/detail/config_macros_clang.h @@ -21,3 +21,5 @@ #define PHANTOM_COROUTINES_PUSH_DISABLE_INTERNAL_LINKAGE_WARNING() PHANTOM_COROUTINES_CLANG_PUSH_DISABLE_WARNING("clang diagnostic ignored \"-Wundefined-internal\"") #define PHANTOM_COROUTINES_POP_WARNINGS() PHANTOM_COROUTINES_CLANG_POP_WARNINGS() + +#define PHANTOM_COROUTINES_NO_UNIQUE_ADDRESS [[no_unique_address]] diff --git a/Phantom.Coroutines/include/Phantom.Coroutines/detail/config_macros_clang_msvc.h b/Phantom.Coroutines/include/Phantom.Coroutines/detail/config_macros_clang_msvc.h index 27528e6..c34136c 100644 --- a/Phantom.Coroutines/include/Phantom.Coroutines/detail/config_macros_clang_msvc.h +++ b/Phantom.Coroutines/include/Phantom.Coroutines/detail/config_macros_clang_msvc.h @@ -25,3 +25,5 @@ #define PHANTOM_COROUTINES_PUSH_DISABLE_INTERNAL_LINKAGE_WARNING() PHANTOM_COROUTINES_CLANG_PUSH_DISABLE_WARNING("clang diagnostic ignored \"-Wundefined-internal\"") #define PHANTOM_COROUTINES_POP_WARNINGS() PHANTOM_COROUTINES_CLANG_POP_WARNINGS() + +#define PHANTOM_COROUTINES_NO_UNIQUE_ADDRESS [[msvc::no_unique_address]] diff --git a/Phantom.Coroutines/include/Phantom.Coroutines/detail/config_macros_msvc.h b/Phantom.Coroutines/include/Phantom.Coroutines/detail/config_macros_msvc.h index bc73c38..bd9f8e7 100644 --- a/Phantom.Coroutines/include/Phantom.Coroutines/detail/config_macros_msvc.h +++ b/Phantom.Coroutines/include/Phantom.Coroutines/detail/config_macros_msvc.h @@ -40,3 +40,5 @@ #define PHANTOM_COROUTINES_PUSH_DISABLE_INTERNAL_LINKAGE_WARNING() PHANTOM_COROUTINES_MSVC_PUSH_DISABLE_WARNING(5046) #define PHANTOM_COROUTINES_POP_WARNINGS() PHANTOM_COROUTINES_MSVC_POP_WARNINGS() + +#define PHANTOM_COROUTINES_NO_UNIQUE_ADDRESS [[msvc::no_unique_address]] From fdafd36a7a86799520cd83c50fc78ad0d492e247 Mon Sep 17 00:00:00 2001 From: Joshua Rowe Date: Mon, 22 Dec 2025 18:27:19 -0800 Subject: [PATCH 23/46] Export more things from std:: --- .../include/Phantom.Coroutines/detail/coroutine.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Phantom.Coroutines/include/Phantom.Coroutines/detail/coroutine.h b/Phantom.Coroutines/include/Phantom.Coroutines/detail/coroutine.h index 8c6fc7a..45f78fb 100644 --- a/Phantom.Coroutines/include/Phantom.Coroutines/detail/coroutine.h +++ b/Phantom.Coroutines/include/Phantom.Coroutines/detail/coroutine.h @@ -14,6 +14,7 @@ namespace Phantom::Coroutines { namespace detail { +PHANTOM_COROUTINES_MODULE_EXPORT using std::coroutine_traits; PHANTOM_COROUTINES_MODULE_EXPORT @@ -90,6 +91,12 @@ using detail::noop_coroutine; namespace std { +PHANTOM_COROUTINES_MODULE_EXPORT +using std::coroutine_handle; + +PHANTOM_COROUTINES_MODULE_EXPORT +using std::coroutine_traits; + PHANTOM_COROUTINES_MODULE_EXPORT using std::coroutine_traits; From 268f8d59122b99294900901f0daa6eb6f0e5221f Mon Sep 17 00:00:00 2001 From: Joshua Rowe Date: Mon, 22 Dec 2025 18:27:35 -0800 Subject: [PATCH 24/46] Use correct type qualification in contextual_promise --- .../include/Phantom.Coroutines/contextual_promise.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Phantom.Coroutines/include/Phantom.Coroutines/contextual_promise.h b/Phantom.Coroutines/include/Phantom.Coroutines/contextual_promise.h index ffc3005..1c28329 100644 --- a/Phantom.Coroutines/include/Phantom.Coroutines/contextual_promise.h +++ b/Phantom.Coroutines/include/Phantom.Coroutines/contextual_promise.h @@ -129,7 +129,7 @@ public derived_promise auto initial_suspend( this auto& self ) noexcept(noexcept( - self.derived_promise::initial_suspend() + self.contextual_promise::derived_promise::initial_suspend() )) { static_assert(is_contextual_promise); @@ -141,7 +141,7 @@ public derived_promise self, [&]() -> decltype(auto) { - return self.derived_promise::initial_suspend(); + return self.contextual_promise::derived_promise::initial_suspend(); } }; } @@ -159,7 +159,7 @@ public derived_promise self, [&]() noexcept -> decltype(auto) { - return self.derived_promise::final_suspend(); + return self.contextual_promise::derived_promise::final_suspend(); } }; } @@ -176,11 +176,11 @@ public derived_promise DoLeaveOnSuspend{}, self, [&]() noexcept(noexcept( - self.derived_promise::await_transform( + self.contextual_promise::derived_promise::await_transform( std::forward(awaitable)) )) -> decltype(auto) { - return self.derived_promise::await_transform( + return self.contextual_promise::derived_promise::await_transform( std::forward(awaitable)); } }; From b585255b73e14d8d383cb9eecb0dcf197cae981c Mon Sep 17 00:00:00 2001 From: Joshua Rowe Date: Wed, 24 Dec 2025 12:19:20 -0800 Subject: [PATCH 25/46] Add PHANTOM_COROUTINES_MSVC_INCORRECTLY_COPIES_CO_AWAIT_RVALUE_REFERENCE config macro --- .../include/Phantom.Coroutines/detail/config_macros_msvc.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Phantom.Coroutines/include/Phantom.Coroutines/detail/config_macros_msvc.h b/Phantom.Coroutines/include/Phantom.Coroutines/detail/config_macros_msvc.h index bd9f8e7..ad7b129 100644 --- a/Phantom.Coroutines/include/Phantom.Coroutines/detail/config_macros_msvc.h +++ b/Phantom.Coroutines/include/Phantom.Coroutines/detail/config_macros_msvc.h @@ -21,6 +21,11 @@ #define PHANTOM_COROUTINES_NO_REJECT_LAMBDA_WITH_INVALID_MEMBER 1 #endif +// Bug https://developercommunity.visualstudio.com/t/Incorrect-Code-Generation:-Assigning-res/11021227 +#ifndef PHANTOM_COROUTINES_MSVC_INCORRECTLY_COPIES_CO_AWAIT_RVALUE_REFERENCE +#define PHANTOM_COROUTINES_MSVC_INCORRECTLY_COPIES_CO_AWAIT_RVALUE_REFERENCE 1 +#endif + // MSVC captures the result of get_return_object as-is. #define PHANTOM_COROUTINES_USE_REFERENCE_WRAPPER_RETURN_TYPE_FOR_GET_RETURN_OBJECT 0 From f473340a55c8a58f1f526f6d104abbad50bdff88 Mon Sep 17 00:00:00 2001 From: Joshua Rowe Date: Wed, 24 Dec 2025 12:19:48 -0800 Subject: [PATCH 26/46] value_awaiter now return references where possible --- Phantom.Coroutines.Modules/value_awaiter.ixx | 1 + .../value_awaiter_test.cpp | 97 +++++++++++++++++-- .../Phantom.Coroutines/value_awaiter.h | 38 +++++++- 3 files changed, 125 insertions(+), 11 deletions(-) diff --git a/Phantom.Coroutines.Modules/value_awaiter.ixx b/Phantom.Coroutines.Modules/value_awaiter.ixx index 4e1e3d4..cb5f781 100644 --- a/Phantom.Coroutines.Modules/value_awaiter.ixx +++ b/Phantom.Coroutines.Modules/value_awaiter.ixx @@ -1,4 +1,5 @@ module; +#include #include "Phantom.Coroutines/detail/config_macros.h" export module Phantom.Coroutines.value_awaiter; #include "Phantom.Coroutines/value_awaiter.h" diff --git a/Phantom.Coroutines.Test/value_awaiter_test.cpp b/Phantom.Coroutines.Test/value_awaiter_test.cpp index 8c68c2b..c11d72b 100644 --- a/Phantom.Coroutines.Test/value_awaiter_test.cpp +++ b/Phantom.Coroutines.Test/value_awaiter_test.cpp @@ -1,30 +1,109 @@ +#include +#include "Phantom.Coroutines/detail/config_macros.h" #include "async_test.h" #if defined(PHANTOM_COROUTINES_TESTING_SINGLE_MODULE) import Phantom.Coroutines; +import Phantom.Coroutines.Test.lifetime_tracker; #elif defined(PHANTOM_COROUTINES_TESTING_MODULES) import Phantom.Coroutines.value_awaiter; +import Phantom.Coroutines.Test.lifetime_tracker; #elif defined(PHANTOM_COROUTINES_TESTING_HEADERS) #include "Phantom.Coroutines/value_awaiter.h" +#include "lifetime_tracker.h" #endif namespace Phantom::Coroutines { -ASYNC_TEST(value_awaiter_test, can_await_by_value) +ASYNC_TEST(value_awaiter_test, await_value_from_lvalue_returns_const_reference) { - value_awaiter awaiter{ "hello" }; + lifetime_statistics lifetimeStatistics; + value_awaiter awaiter{ lifetimeStatistics.tracker() }; decltype(auto) result = co_await awaiter; - static_assert(std::same_as); - EXPECT_EQ("hello", result); + static_assert(std::same_as); + EXPECT_EQ(0, lifetimeStatistics.copy_construction_count); + EXPECT_EQ(0, lifetimeStatistics.move_construction_count); + EXPECT_EQ(lifetimeStatistics, result); + EXPECT_EQ(std::addressof(awaiter.m_value), std::addressof(result)); } -ASYNC_TEST(value_awaiter_test, can_await_by_reference) +ASYNC_TEST(value_awaiter_test, await_value_from_rvalue_returns_rvalue_reference) { - std::string expected = "hello"; - value_awaiter awaiter{ expected }; + lifetime_statistics lifetimeStatistics; + value_awaiter awaiter{ lifetimeStatistics.tracker() }; +#if PHANTOM_COROUTINES_MSVC_INCORRECTLY_COPIES_CO_AWAIT_RVALUE_REFERENCE + // The extra "move" is to workaround a compiler bug in MSVC where it copies the result of await_resume. + decltype(auto) result = std::move(co_await std::move(awaiter)); +#else decltype(auto) result = co_await awaiter; - static_assert(std::same_as); - EXPECT_EQ(&expected, &result); +#endif + static_assert(std::same_as); + EXPECT_EQ(0, lifetimeStatistics.copy_construction_count); + EXPECT_EQ(0, lifetimeStatistics.move_construction_count); + EXPECT_EQ(lifetimeStatistics, result); + EXPECT_EQ(std::addressof(awaiter.m_value), std::addressof(result)); +} + +ASYNC_TEST(value_awaiter_test, await_lvalue_reference_from_lvalue_returns_lvalue_reference) +{ + lifetime_statistics lifetimeStatistics; + lifetime_tracker tracker = lifetimeStatistics.tracker(); + value_awaiter awaiter{ tracker }; + decltype(auto) result = co_await awaiter; + static_assert(std::same_as); + EXPECT_EQ(0, lifetimeStatistics.copy_construction_count); + EXPECT_EQ(0, lifetimeStatistics.move_construction_count); + EXPECT_EQ(lifetimeStatistics, result); + EXPECT_EQ(std::addressof(awaiter.m_value), std::addressof(result)); +} + +ASYNC_TEST(value_awaiter_test, await_lvalue_reference_from_rvalue_returns_lvalue_reference) +{ + lifetime_statistics lifetimeStatistics; + lifetime_tracker tracker = lifetimeStatistics.tracker(); + value_awaiter awaiter{ tracker }; + decltype(auto) result = co_await std::move(awaiter); + static_assert(std::same_as); + EXPECT_EQ(0, lifetimeStatistics.copy_construction_count); + EXPECT_EQ(0, lifetimeStatistics.move_construction_count); + EXPECT_EQ(lifetimeStatistics, result); + EXPECT_EQ(std::addressof(awaiter.m_value), std::addressof(result)); +} + +ASYNC_TEST(value_awaiter_test, await_rvalue_reference_from_lvalue_returns_rvalue_reference) +{ + lifetime_statistics lifetimeStatistics; + lifetime_tracker tracker = lifetimeStatistics.tracker(); + value_awaiter awaiter{ std::move(tracker) }; +#if PHANTOM_COROUTINES_MSVC_INCORRECTLY_COPIES_CO_AWAIT_RVALUE_REFERENCE + // The extra "move" is to workaround a compiler bug in MSVC where it copies the result of await_resume. + decltype(auto) result = std::move(co_await awaiter); +#else + decltype(auto) result = co_await awaiter; +#endif + static_assert(std::same_as); + EXPECT_EQ(0, lifetimeStatistics.copy_construction_count); + EXPECT_EQ(0, lifetimeStatistics.move_construction_count); + EXPECT_EQ(lifetimeStatistics, result); + EXPECT_EQ(std::addressof(awaiter.m_value), std::addressof(result)); +} + +ASYNC_TEST(value_awaiter_test, await_rvalue_reference_from_rvalue_returns_rvalue_reference) +{ + lifetime_statistics lifetimeStatistics; + lifetime_tracker tracker = lifetimeStatistics.tracker(); + value_awaiter awaiter{ std::move(tracker) }; +#if PHANTOM_COROUTINES_MSVC_INCORRECTLY_COPIES_CO_AWAIT_RVALUE_REFERENCE + // The extra "move" is to workaround a compiler bug in MSVC where it copies the result of await_resume. + decltype(auto) result = std::move(co_await awaiter); +#else + decltype(auto) result = co_await awaiter; +#endif + static_assert(std::same_as); + EXPECT_EQ(0, lifetimeStatistics.copy_construction_count); + EXPECT_EQ(0, lifetimeStatistics.move_construction_count); + EXPECT_EQ(lifetimeStatistics, result); + EXPECT_EQ(std::addressof(awaiter.m_value), std::addressof(result)); } } \ No newline at end of file diff --git a/Phantom.Coroutines/include/Phantom.Coroutines/value_awaiter.h b/Phantom.Coroutines/include/Phantom.Coroutines/value_awaiter.h index 6e57fa3..788c310 100644 --- a/Phantom.Coroutines/include/Phantom.Coroutines/value_awaiter.h +++ b/Phantom.Coroutines/include/Phantom.Coroutines/value_awaiter.h @@ -1,6 +1,7 @@ #ifndef PHANTOM_COROUTINES_INCLUDE_VALUE_AWAITER_H #define PHANTOM_COROUTINES_INCLUDE_VALUE_AWAITER_H #ifndef PHANTOM_COROUTINES_COMPILING_MODULES +#include #endif static_assert(PHANTOM_COROUTINES_IS_CONFIGURED); @@ -26,9 +27,42 @@ struct value_awaiter { } - Value await_resume() const noexcept + template< + typename Self + > + decltype(auto) await_resume( + this Self&& self + ) { - return m_value; + if constexpr (std::is_rvalue_reference_v) + { + return std::move(self.m_value); + } + else if constexpr (std::is_reference_v) + { + return self.m_value; + } + else + { + return std::as_const(self.m_value); + } + } + + // This allows value_awaiter to be used as an r-value and get r-value semantics from the co_await. + // If we're of value_type, we remap to Value&& to move the value out. + decltype(auto) operator co_await( + this auto&& self) + { + if constexpr ( + std::is_rvalue_reference_v + && !std::is_reference_v) + { + return value_awaiter{ std::move(self.m_value) }; + } + else + { + return self; + } } }; From c071ddba80b64e1085d834aa5568ed7bd7b47d75 Mon Sep 17 00:00:00 2001 From: Joshua Rowe Date: Wed, 24 Dec 2025 12:28:02 -0800 Subject: [PATCH 27/46] Add missing import and includes and use Phantom::Coroutines versions of types --- Phantom.Coroutines.Test/async_test.h | 1 + Phantom.Coroutines.Test/sync_wait_test.cpp | 2 +- Phantom.Coroutines.Test/type_traits_test.cpp | 6 ++++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Phantom.Coroutines.Test/async_test.h b/Phantom.Coroutines.Test/async_test.h index 6098d36..c2af1c9 100644 --- a/Phantom.Coroutines.Test/async_test.h +++ b/Phantom.Coroutines.Test/async_test.h @@ -5,6 +5,7 @@ #if defined(PHANTOM_COROUTINES_TESTING_SINGLE_MODULE) import Phantom.Coroutines; #elif defined(PHANTOM_COROUTINES_TESTING_MODULES) +import Phantom.Coroutines.coroutine; import Phantom.Coroutines.reusable_task; #elif defined(PHANTOM_COROUTINES_TESTING_HEADERS) #include "Phantom.Coroutines/reusable_task.h" diff --git a/Phantom.Coroutines.Test/sync_wait_test.cpp b/Phantom.Coroutines.Test/sync_wait_test.cpp index c77b878..d2fa180 100644 --- a/Phantom.Coroutines.Test/sync_wait_test.cpp +++ b/Phantom.Coroutines.Test/sync_wait_test.cpp @@ -149,7 +149,7 @@ TEST(as_future_test, rethrows_exception_thrown_by_task_lambda) TEST(as_future_test, rethrows_exception_thrown_by_await_resume) { - struct awaitable : std::suspend_never + struct awaitable : suspend_never { void await_resume() { diff --git a/Phantom.Coroutines.Test/type_traits_test.cpp b/Phantom.Coroutines.Test/type_traits_test.cpp index 67c3c18..69f060f 100644 --- a/Phantom.Coroutines.Test/type_traits_test.cpp +++ b/Phantom.Coroutines.Test/type_traits_test.cpp @@ -5,9 +5,11 @@ #if defined(PHANTOM_COROUTINES_TESTING_SINGLE_MODULE) import Phantom.Coroutines; #elif defined(PHANTOM_COROUTINES_TESTING_MODULES) +import Phantom.Coroutines.coroutine; import Phantom.Coroutines.type_traits; import Phantom.Coroutines.value_awaiter; #elif defined(PHANTOM_COROUTINES_TESTING_HEADERS) +#include "Phantom.Coroutines/detail/coroutine.h" #include "Phantom.Coroutines/type_traits.h" #include "Phantom.Coroutines/value_awaiter.h" #endif @@ -87,8 +89,8 @@ template< > struct test_coroutine_function_traits_promise { - std::suspend_always initial_suspend(); - std::suspend_always final_suspend() noexcept; + suspend_always initial_suspend(); + suspend_always final_suspend() noexcept; void return_void(); void unhandled_exception(); test_coroutine_function_traits_task get_return_object(); From a86a07f61a42f0fc7e920ce2bc2222a5b7b36b98 Mon Sep 17 00:00:00 2001 From: Joshua Rowe Date: Wed, 24 Dec 2025 12:28:28 -0800 Subject: [PATCH 28/46] Use PHANTOM_COROUTINES_MSVC_INCORRECTLY_COPIES_CO_AWAIT_RVALUE_REFERENCE macro in Can_use_returned_rvalue_reference_with_same_address test --- Phantom.Coroutines.Test/task_test.cpp | 31 +++++++++++++-------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/Phantom.Coroutines.Test/task_test.cpp b/Phantom.Coroutines.Test/task_test.cpp index 9912720..dff5e37 100644 --- a/Phantom.Coroutines.Test/task_test.cpp +++ b/Phantom.Coroutines.Test/task_test.cpp @@ -1,6 +1,7 @@ #include #include #include +#include "Phantom.Coroutines/detail/config_macros.h" #include "async_test.h" #if defined(PHANTOM_COROUTINES_TESTING_SINGLE_MODULE) import Phantom.Coroutines; @@ -279,10 +280,8 @@ ASYNC_TEST(task_test, Can_use_returned_rvalue_reference) co_return std::move(initialValue); }(); - [&]() { - EXPECT_EQ(2, statistics.instance_count); - EXPECT_EQ(1, statistics.move_construction_count); - }(); + EXPECT_EQ(2, statistics.instance_count); + EXPECT_EQ(1, statistics.move_construction_count); }()); EXPECT_TRUE(initialValue.moved_from()); @@ -294,22 +293,22 @@ ASYNC_TEST(task_test, Can_use_returned_rvalue_reference_with_same_address) lifetime_statistics statistics; lifetime_tracker initialValue = statistics.tracker(); - co_await([&]() -> task<> + auto taskLambda = [&]() -> task { - [&](lifetime_tracker&& endValue) { + co_return std::move(initialValue); + }; - endValue.use(); - EXPECT_EQ(1, statistics.instance_count); - EXPECT_EQ(0, statistics.move_construction_count); - }( - co_await[&]() -> task - { - co_return std::move(initialValue); - }()); - }()); +#if PHANTOM_COROUTINES_MSVC_INCORRECTLY_COPIES_CO_AWAIT_RVALUE_REFERENCE + auto&& endValue = std::move(co_await taskLambda()); +#else + auto&& endValue = co_await taskLambda(); +#endif + + static_assert(std::same_as); - EXPECT_FALSE(initialValue.moved_from()); EXPECT_EQ(1, statistics.instance_count); + EXPECT_EQ(0, statistics.move_construction_count); + EXPECT_EQ(std::addressof(initialValue), std::addressof(endValue)); EXPECT_FALSE(statistics.used_after_move); } From 649aa347b8685deeb9c7e487395ad5ebf7abec56 Mon Sep 17 00:00:00 2001 From: Joshua Rowe Date: Wed, 24 Dec 2025 12:54:39 -0800 Subject: [PATCH 29/46] Move awaiter_wrapper_test, awaiters out of detail --- Phantom.Coroutines.Test/CMakeLists.txt | 4 ++-- .../{detail => }/awaiter_wrapper_test.cpp | 3 ++- Phantom.Coroutines.Test/{detail => }/awaiters.h | 2 +- Phantom.Coroutines.Test/suspend_result_test.cpp | 5 +---- Phantom.Coroutines.Test/sync_wait_test.cpp | 2 +- Phantom.Coroutines.Test/type_traits_test.cpp | 2 +- 6 files changed, 8 insertions(+), 10 deletions(-) rename Phantom.Coroutines.Test/{detail => }/awaiter_wrapper_test.cpp (98%) rename Phantom.Coroutines.Test/{detail => }/awaiters.h (99%) diff --git a/Phantom.Coroutines.Test/CMakeLists.txt b/Phantom.Coroutines.Test/CMakeLists.txt index fbcec5e..207aad6 100644 --- a/Phantom.Coroutines.Test/CMakeLists.txt +++ b/Phantom.Coroutines.Test/CMakeLists.txt @@ -18,9 +18,9 @@ set( "async_test.h" "atomic_state_test.cpp" "awaiter_list_test.cpp" + "awaiters.h" + "awaiter_wrapper_test.cpp" "contextual_promise_test.cpp" - "detail/awaiter_wrapper_test.cpp" - "detail/awaiters.h" "detail/fibonacci_heap_test.cpp" "detail/non_copyable_test.cpp" "detail/scope_guard_test.cpp" diff --git a/Phantom.Coroutines.Test/detail/awaiter_wrapper_test.cpp b/Phantom.Coroutines.Test/awaiter_wrapper_test.cpp similarity index 98% rename from Phantom.Coroutines.Test/detail/awaiter_wrapper_test.cpp rename to Phantom.Coroutines.Test/awaiter_wrapper_test.cpp index 12ea133..919a793 100644 --- a/Phantom.Coroutines.Test/detail/awaiter_wrapper_test.cpp +++ b/Phantom.Coroutines.Test/awaiter_wrapper_test.cpp @@ -10,8 +10,9 @@ import Phantom.Coroutines; #include "Phantom.Coroutines/awaiter_wrapper.h" #endif -namespace Phantom::Coroutines::detail +namespace Phantom::Coroutines { + template< typename Awaiter > struct awaiter_wrapper_test_awaiter diff --git a/Phantom.Coroutines.Test/detail/awaiters.h b/Phantom.Coroutines.Test/awaiters.h similarity index 99% rename from Phantom.Coroutines.Test/detail/awaiters.h rename to Phantom.Coroutines.Test/awaiters.h index 2984b5e..6d75b1f 100644 --- a/Phantom.Coroutines.Test/detail/awaiters.h +++ b/Phantom.Coroutines.Test/awaiters.h @@ -8,7 +8,7 @@ import Phantom.Coroutines.coroutine; #include "Phantom.Coroutines/detail/coroutine.h" #endif -namespace Phantom::Coroutines::detail +namespace Phantom::Coroutines { // These types are used as placeholders in tests. diff --git a/Phantom.Coroutines.Test/suspend_result_test.cpp b/Phantom.Coroutines.Test/suspend_result_test.cpp index f56a8d7..528b84c 100644 --- a/Phantom.Coroutines.Test/suspend_result_test.cpp +++ b/Phantom.Coroutines.Test/suspend_result_test.cpp @@ -9,13 +9,10 @@ import Phantom.Coroutines.task; #include "Phantom.Coroutines/suspend_result.h" #include "Phantom.Coroutines/task.h" #endif -#include "detail/awaiters.h" +#include "awaiters.h" namespace Phantom::Coroutines { -using detail::generic_awaiter; -using detail::get_unusable_task; - ASYNC_TEST(suspend_result_test, reports_not_suspended_for_await_ready_is_true) { auto testAwaiter = generic_awaiter diff --git a/Phantom.Coroutines.Test/sync_wait_test.cpp b/Phantom.Coroutines.Test/sync_wait_test.cpp index d2fa180..77b139a 100644 --- a/Phantom.Coroutines.Test/sync_wait_test.cpp +++ b/Phantom.Coroutines.Test/sync_wait_test.cpp @@ -12,7 +12,7 @@ import Phantom.Coroutines.task; #include "Phantom.Coroutines/sync_wait.h" #include "Phantom.Coroutines/task.h" #endif -#include "detail/awaiters.h" +#include "awaiters.h" #include #include diff --git a/Phantom.Coroutines.Test/type_traits_test.cpp b/Phantom.Coroutines.Test/type_traits_test.cpp index 69f060f..88a330f 100644 --- a/Phantom.Coroutines.Test/type_traits_test.cpp +++ b/Phantom.Coroutines.Test/type_traits_test.cpp @@ -13,7 +13,7 @@ import Phantom.Coroutines.value_awaiter; #include "Phantom.Coroutines/type_traits.h" #include "Phantom.Coroutines/value_awaiter.h" #endif -#include "detail/awaiters.h" +#include "awaiters.h" PHANTOM_COROUTINES_PUSH_DISABLE_INTERNAL_LINKAGE_WARNING() From 21e21355c76baf2ca732401d52049cb49ddc45f6 Mon Sep 17 00:00:00 2001 From: Joshua Rowe Date: Wed, 24 Dec 2025 13:37:10 -0800 Subject: [PATCH 30/46] Use operator co_await in get_awaiter on simple awaiters that also have operator co_await Add is_simple_awaiter --- Phantom.Coroutines.Test/awaiters.h | 2 +- Phantom.Coroutines.Test/type_traits_test.cpp | 51 +++++++++++++++++++ .../include/Phantom.Coroutines/type_traits.h | 29 ++++++++--- 3 files changed, 75 insertions(+), 7 deletions(-) diff --git a/Phantom.Coroutines.Test/awaiters.h b/Phantom.Coroutines.Test/awaiters.h index 6d75b1f..47f6c7f 100644 --- a/Phantom.Coroutines.Test/awaiters.h +++ b/Phantom.Coroutines.Test/awaiters.h @@ -18,7 +18,7 @@ template< > struct typed_awaiter { - bool await_ready() { std::unreachable(); } + bool await_ready() const noexcept { std::unreachable(); } TSuspendResult await_suspend(coroutine_handle<>) { std::unreachable(); } TResumeResult await_resume() { std::unreachable(); } }; diff --git a/Phantom.Coroutines.Test/type_traits_test.cpp b/Phantom.Coroutines.Test/type_traits_test.cpp index 88a330f..2b12831 100644 --- a/Phantom.Coroutines.Test/type_traits_test.cpp +++ b/Phantom.Coroutines.Test/type_traits_test.cpp @@ -246,6 +246,15 @@ static_assert(is_awaiter>); static_assert(is_awaiter>); static_assert(!is_awaiter); +static_assert(is_awaitable>); +static_assert(is_awaitable&>); +static_assert(is_awaitable&&>); + +static_assert(is_simple_awaiter>); +static_assert(!is_simple_awaiter>); +static_assert(!is_simple_awaiter&>); +static_assert(!is_simple_awaiter&&>); + static_assert(has_co_await_member>); static_assert(!has_co_await_member); @@ -319,6 +328,48 @@ TEST(type_traits_test, get_awaiter_returns_awaiter_lvalue_as_lvalue_reference) ASSERT_EQ(&result, &awaiter); } +TEST(type_traits_test, get_awaiter_returns_co_await_on_awaiter_with_member_co_await) +{ + struct test_awaiter : generic_awaiter + { + generic_awaiter m_awaiter; + + auto& operator co_await() + { + return m_awaiter; + } + } awaitable; + + decltype(auto) result = get_awaiter(awaitable); + static_assert(std::same_as&, decltype(result)>); + ASSERT_EQ(&awaitable.m_awaiter, &result); +} + +namespace +{ + +struct test_awaitable_with_friend_co_await : generic_awaiter +{ + generic_awaiter m_awaiter; + + friend auto& operator co_await( + test_awaitable_with_friend_co_await& awaitable) + { + return awaitable.m_awaiter; + } +}; + +static_assert(has_co_await_non_member); +} + +TEST(type_traits_test, get_awaiter_returns_co_await_on_awaiter_with_friend_co_await) +{ + test_awaitable_with_friend_co_await awaitable; + decltype(auto) result = get_awaiter(awaitable); + static_assert(std::same_as&, decltype(result)>); + ASSERT_EQ(&awaitable.m_awaiter, &result); +} + TEST(type_traits_test, get_awaiter_returns_awaiter_rvalue_reference_as_rvalue_reference) { generic_awaiter<> awaiter; diff --git a/Phantom.Coroutines/include/Phantom.Coroutines/type_traits.h b/Phantom.Coroutines/include/Phantom.Coroutines/type_traits.h index 15e08b3..5d9b621 100644 --- a/Phantom.Coroutines/include/Phantom.Coroutines/type_traits.h +++ b/Phantom.Coroutines/include/Phantom.Coroutines/type_traits.h @@ -374,8 +374,7 @@ template< PHANTOM_COROUTINES_MODULE_EXPORT template< - typename TAwaiter, - typename CoroutineHandle = coroutine_handle<> + typename TAwaiter > concept is_awaiter = requires ( @@ -409,12 +408,28 @@ template< PHANTOM_COROUTINES_MODULE_EXPORT template< typename TAwaitable -> concept is_awaitable = +> concept has_co_await = has_co_await_member || -has_co_await_non_member +has_co_await_non_member; + +// A simple awaiter is an awaiter that has no co_await member. +PHANTOM_COROUTINES_MODULE_EXPORT +template< + typename TAwaiter +> +concept is_simple_awaiter = +is_awaiter +&& +!has_co_await; + +PHANTOM_COROUTINES_MODULE_EXPORT +template< + typename TAwaitable +> concept is_awaitable = +has_co_await || -is_awaiter; +is_simple_awaiter; PHANTOM_COROUTINES_MODULE_EXPORT template< @@ -440,7 +455,7 @@ decltype(auto) get_awaiter( PHANTOM_COROUTINES_MODULE_EXPORT template< - is_awaiter Awaiter + is_simple_awaiter Awaiter > decltype(auto) get_awaiter( Awaiter&& awaitable @@ -721,6 +736,8 @@ using detail::is_awaitable; PHANTOM_COROUTINES_MODULE_EXPORT using detail::is_awaiter; PHANTOM_COROUTINES_MODULE_EXPORT +using detail::is_simple_awaiter; +PHANTOM_COROUTINES_MODULE_EXPORT using detail::has_co_await_member; PHANTOM_COROUTINES_MODULE_EXPORT using detail::has_co_await_non_member; From f236a8b49ede749e481ef09ba61739b2e4dff57c Mon Sep 17 00:00:00 2001 From: Joshua Rowe Date: Wed, 24 Dec 2025 13:42:07 -0800 Subject: [PATCH 31/46] awaiter_wrapper invokes co_await on is_awaiter objects that have operator co_await --- .../awaiter_wrapper_test.cpp | 18 ++++++++++++++++++ .../Phantom.Coroutines/awaiter_wrapper.h | 4 ++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/Phantom.Coroutines.Test/awaiter_wrapper_test.cpp b/Phantom.Coroutines.Test/awaiter_wrapper_test.cpp index 919a793..458d6ba 100644 --- a/Phantom.Coroutines.Test/awaiter_wrapper_test.cpp +++ b/Phantom.Coroutines.Test/awaiter_wrapper_test.cpp @@ -78,4 +78,22 @@ TEST(awaiter_wrapper_test, can_wrap_awaitable_rvalue) ASSERT_EQ(&wrapper.awaiter(), &awaitable.m_awaiter); } +TEST(awaiter_wrapper_test, can_wrap_awaiter_with_co_await) +{ + // This class is both an awaitable and an awaiter. + // We check that the awaiter_wrapper picks the awaiter returned by co_await. + struct test_awaiter_with_co_await : generic_awaiter + { + generic_awaiter m_awaiter; + + auto& operator co_await() + { + return m_awaiter; + } + } awaitable; + + awaiter_wrapper_test_awaiter wrapper{ [&]() -> auto&& { return std::move(awaitable); } }; + ASSERT_EQ(&wrapper.awaiter(), &awaitable.m_awaiter); +} + } \ No newline at end of file diff --git a/Phantom.Coroutines/include/Phantom.Coroutines/awaiter_wrapper.h b/Phantom.Coroutines/include/Phantom.Coroutines/awaiter_wrapper.h index 0650b9a..5b18327 100644 --- a/Phantom.Coroutines/include/Phantom.Coroutines/awaiter_wrapper.h +++ b/Phantom.Coroutines/include/Phantom.Coroutines/awaiter_wrapper.h @@ -105,9 +105,9 @@ template< > class awaiter_wrapper; // The is_awaiter specialization provides the behavior of wrapping -// an actual awaiter object. +// an actual awaiter object that has no operator co_await. template< - is_awaiter Awaiter + is_simple_awaiter Awaiter > class awaiter_wrapper< Awaiter > : From f8b0b1c912762b3f2897fe4099aaa5ef6dea1270 Mon Sep 17 00:00:00 2001 From: Joshua Rowe Date: Wed, 24 Dec 2025 13:46:16 -0800 Subject: [PATCH 32/46] Add test tracing_tests::trace_return_value_moves_value Mae tracing mostly compile on CLang --- Phantom.Coroutines.Test/tracing_test.cpp | 49 +++++++- .../include/Phantom.Coroutines/tracing.h | 114 +++++++++--------- 2 files changed, 103 insertions(+), 60 deletions(-) diff --git a/Phantom.Coroutines.Test/tracing_test.cpp b/Phantom.Coroutines.Test/tracing_test.cpp index 5a3b9b6..7b90cdf 100644 --- a/Phantom.Coroutines.Test/tracing_test.cpp +++ b/Phantom.Coroutines.Test/tracing_test.cpp @@ -4,6 +4,7 @@ #include "Phantom.Coroutines/detail/config_macros.h" #if defined(PHANTOM_COROUTINES_TESTING_SINGLE_MODULE) import Phantom.Coroutines; +import Phantom.Coroutines.Test.lifetime_tracker; #elif defined(PHANTOM_COROUTINES_TESTING_MODULES) import Phantom.Coroutines.config_globals; import Phantom.Coroutines.coroutine; @@ -13,6 +14,8 @@ import Phantom.Coroutines.sync_wait; import Phantom.Coroutines.tracing; import Phantom.Coroutines.task; import Phantom.Coroutines.type_traits; +import Phantom.Coroutines.value_awaiter; +import Phantom.Coroutines.Test.lifetime_tracker; #elif defined(PHANTOM_COROUTINES_TESTING_HEADERS) #include "Phantom.Coroutines/detail/config_globals.h" #include "Phantom.Coroutines/polymorphic_promise.h" @@ -20,6 +23,8 @@ import Phantom.Coroutines.type_traits; #include "Phantom.Coroutines/tracing.h" #include "Phantom.Coroutines/task.h" #include "Phantom.Coroutines/type_traits.h" +#include "Phantom.Coroutines/value_awaiter.h" +#include "lifetime_tracker.h" #endif namespace Phantom::Coroutines::tracing @@ -117,14 +122,14 @@ struct tracing_tests : testing::Test { using controllable_promise_base::controllable_promise_base; - auto return_value( + void return_value( auto&& value) { if (this->exception_to_throw_on_return) { std::rethrow_exception(this->exception_to_throw_on_return); } - return this->controllable_promise_base::return_value( + this->controllable_promise_base::return_value( std::forward(value)); } }; @@ -139,7 +144,7 @@ struct tracing_tests : testing::Test { using controllable_promise_base::controllable_promise_base; - auto return_void() + void return_void() { if (this->exception_to_throw_on_return) { @@ -472,6 +477,41 @@ struct coroutine_traits< namespace Phantom::Coroutines::tracing { +ASYNC_TEST_F(tracing_tests, trace_return_value_moves_value) +{ + auto filter = filters::event_type< + events::await_resume_result + > && filters::awaiter_type< + events::co_await_awaiter_type + >; + + lifetime_statistics lifetimeStatistics; + + auto taskLambda = [&]( + this auto& self, + traced_events_checker& eventsChecker, + auto filter + ) -> test_traced_task + { + EXPECT_EQ(0, lifetimeStatistics.copy_construction_count); + EXPECT_EQ(0, lifetimeStatistics.move_construction_count); + auto tracker = co_await value_awaiter { lifetimeStatistics.tracker() }; + EXPECT_EQ(0, lifetimeStatistics.copy_construction_count); + EXPECT_EQ(1, lifetimeStatistics.move_construction_count); + }; + + traced_events_checker tracedEventsChecker; + tracedEventsChecker = { [&] (const std::any&) + { + } }; + + co_await taskLambda( + tracedEventsChecker, + filter); + + EXPECT_EQ(1, lifetimeStatistics.move_construction_count); +} + ASYNC_TEST_F(tracing_tests, traces_create_and_destroy_promise) { auto filter = filters::event_type< @@ -582,7 +622,7 @@ ASYNC_TEST_F(tracing_tests, traces_initial_suspend) using expected_underlying_promise_type = test_underlying_promise; // The unwrapped awaiter type - what the underlying promise's initial_suspend() returns - using unwrapped_awaiter_type = std::suspend_always; + using unwrapped_awaiter_type = suspend_always; using expected_traced_awaiter_type = decltype(std::declval().initial_suspend()); expected_underlying_promise_type* expectedPromise = nullptr; @@ -1827,4 +1867,5 @@ ASYNC_TEST_F(tracing_tests, traces_unhandled_exception) EXPECT_EQ(eventIndex, 2); } + } // namespace phantom::coroutines::test diff --git a/Phantom.Coroutines/include/Phantom.Coroutines/tracing.h b/Phantom.Coroutines/include/Phantom.Coroutines/tracing.h index c9d841c..2242bc5 100644 --- a/Phantom.Coroutines/include/Phantom.Coroutines/tracing.h +++ b/Phantom.Coroutines/include/Phantom.Coroutines/tracing.h @@ -136,15 +136,15 @@ consteval bool operator!=( PHANTOM_COROUTINES_MODULE_EXPORT template< - typename EventType, - typename EventData, + typename TEventType, + typename TEventData, template typename ... Accessors > struct event : - Accessors>... + Accessors>... { - using event_type = event_type; - using event_data_type = EventData; + using event_type = event_type; + using event_data_type = TEventData; std::source_location SourceLocation; event_type EventType; @@ -571,18 +571,18 @@ template< > struct event_data { - [[no_unique_address]] TPromise Promise = empty; - [[no_unique_address]] TTracedPromise TracedPromise = empty; - [[no_unique_address]] TPromiseCreationArguments PromiseCreationArguments = empty_arguments; - [[no_unique_address]] TAwaiter Awaiter = empty; - [[no_unique_address]] TTracedAwaiter TracedAwaiter = empty; - [[no_unique_address]] TAwaiterType AwaiterType = no_awaiter_type{}; - [[no_unique_address]] TMethodArguments MethodArguments = empty_arguments; - [[no_unique_address]] TMethodResult MethodResult = empty; - [[no_unique_address]] TMethodException MethodException = empty; - [[no_unique_address]] TUnhandledException UnhandledException = empty; - [[no_unique_address]] TYieldValue YieldValue = empty; - [[no_unique_address]] TReturnValue ReturnValue = empty; + PHANTOM_COROUTINES_NO_UNIQUE_ADDRESS TPromise Promise = empty; + PHANTOM_COROUTINES_NO_UNIQUE_ADDRESS TTracedPromise TracedPromise = empty; + PHANTOM_COROUTINES_NO_UNIQUE_ADDRESS TPromiseCreationArguments PromiseCreationArguments = empty_arguments; + PHANTOM_COROUTINES_NO_UNIQUE_ADDRESS TAwaiter Awaiter = empty; + PHANTOM_COROUTINES_NO_UNIQUE_ADDRESS TTracedAwaiter TracedAwaiter = empty; + PHANTOM_COROUTINES_NO_UNIQUE_ADDRESS TAwaiterType AwaiterType = no_awaiter_type{}; + PHANTOM_COROUTINES_NO_UNIQUE_ADDRESS TMethodArguments MethodArguments = empty_arguments; + PHANTOM_COROUTINES_NO_UNIQUE_ADDRESS TMethodResult MethodResult = empty; + PHANTOM_COROUTINES_NO_UNIQUE_ADDRESS TMethodException MethodException = empty; + PHANTOM_COROUTINES_NO_UNIQUE_ADDRESS TUnhandledException UnhandledException = empty; + PHANTOM_COROUTINES_NO_UNIQUE_ADDRESS TYieldValue YieldValue = empty; + PHANTOM_COROUTINES_NO_UNIQUE_ADDRESS TReturnValue ReturnValue = empty; friend auto operator<=>(const event_data&, const event_data&) = default; @@ -735,7 +735,7 @@ auto call_traced_method( std::source_location sourceLocation, const auto& baseEventData, std::invocable<> auto call -) +) -> std::invoke_result_t { using result_type = std::invoke_result_t; try @@ -766,7 +766,7 @@ auto call_traced_method( { .MethodResult = events::value{ result } }))); - return result; + return std::forward(result); } } catch (...) @@ -800,7 +800,7 @@ struct traced_awaiter : using awaiter_wrapper = awaiter_wrapper; using trace_sink_accessor = traced_awaiter::trace_sink_accessor; using traced_awaiter::trace_sink_accessor::trace_sink; - using wrapped_awaiter_type = typename awaiter_wrapper::awaiter_type; + using wrapped_awaiter_type = std::remove_reference_t; std::source_location m_sourceLocation; BaseEventData m_baseEventData; @@ -820,8 +820,8 @@ struct traced_awaiter : baseEventData.with( events::event_data { - .Awaiter = static_cast(&awaiter.awaiter()), - .TracedAwaiter = &awaiter, + .Awaiter = static_cast(std::addressof(awaiter.awaiter())), + .TracedAwaiter = std::addressof(awaiter), .AwaiterType = AwaiterType{}, }), call); @@ -855,9 +855,6 @@ struct traced_awaiter : Arg&& arg ) noexcept(noexcept(self.awaiter_wrapper::await_suspend(std::forward(arg)))) { - std::tuple argumentsTuple{ arg }; - events::arguments arguments{ argumentsTuple }; - return self.traced_awaiter::call_awaiter( detail::method_events_group { @@ -866,7 +863,7 @@ struct traced_awaiter : events::await_suspend_exception{}, }, self.get_event_data( - arguments), + make_arguments(arg)), [&]() -> decltype(auto) { return self.awaiter_wrapper::await_suspend( @@ -1129,13 +1126,13 @@ class traced_promise_return_value_or_void typename TPromise, typename Value > - decltype(auto) return_value( + void return_value( this TPromise&& promise, Value&& value, std::source_location sourceLocation = std::source_location::current() ) { - return std::forward(promise).call_promise_method( + std::forward(promise).call_promise_method( detail::method_events_group { events::return_value_begin{}, @@ -1149,7 +1146,7 @@ class traced_promise_return_value_or_void }, [&]() { - return std::forward(promise).traced_promise_return_value_or_void::traced_promise_yield_value::return_value( + std::forward(promise).traced_promise_return_value_or_void::traced_promise_yield_value::return_value( std::forward(value)); } ); @@ -1175,12 +1172,12 @@ class traced_promise_return_value_or_void< template< typename TPromise > - decltype(auto) return_void( + void return_void( this TPromise&& promise, std::source_location sourceLocation = std::source_location::current() ) { - return std::forward(promise).call_promise_method( + std::forward(promise).call_promise_method( detail::method_events_group { events::return_void_begin{}, @@ -1193,7 +1190,7 @@ class traced_promise_return_value_or_void< }, [&]() { - return std::forward(promise).traced_promise_return_value_or_void::traced_promise_yield_value::return_void(); + std::forward(promise).traced_promise_return_value_or_void::traced_promise_yield_value::return_void(); } ); } @@ -1354,7 +1351,7 @@ class traced_promise return traced_awaiter { sourceLocation, - [&]() + [&]() -> decltype(auto) { return promise.traced_promise_base::await_transform( std::forward(awaiter)); @@ -1414,19 +1411,24 @@ namespace filters struct filter; template< - std::derived_from Left, - std::derived_from Right + typename T +> +concept is_filter = std::derived_from; + +template< + is_filter Left, + is_filter Right > struct and_filter; template< - std::derived_from Left, - std::derived_from Right + is_filter Left, + is_filter Right > struct or_filter; template< - std::derived_from Filter + is_filter Filter > struct not_filter; @@ -1437,43 +1439,43 @@ struct filter } template< - std::derived_from Left, - std::derived_from Right + is_filter Left, + is_filter Right > friend constexpr auto operator&&( Left left, Right right ) noexcept { - return and_filter{ left, right }; + return and_filter{ left, right }; } template< - std::derived_from Left, - std::derived_from Right + is_filter Left, + is_filter Right > friend constexpr auto operator||( - const Left& left, - const Right& right + Left left, + Right right ) noexcept { - return or_filter{ left, right }; + return or_filter{ left, right }; } template< - std::derived_from Filter + is_filter Filter > friend constexpr auto operator!( - const Filter& filter + Filter filter ) noexcept { - return not_filter{ filter }; + return not_filter{ filter }; } }; template< - std::derived_from Left, - std::derived_from Right + is_filter Left, + is_filter Right > struct and_filter : filter { @@ -1508,8 +1510,8 @@ struct and_filter : filter }; template< - std::derived_from Left, - std::derived_from Right + is_filter Left, + is_filter Right > struct or_filter : filter { @@ -1544,13 +1546,13 @@ struct or_filter : filter }; template< - std::derived_from Filter + is_filter Filter > struct not_filter : filter { Filter filter; - static constexpr auto operator()( + constexpr auto operator()( const auto& event) { using type = decltype(filter(event)); @@ -1628,7 +1630,7 @@ template< struct check_constexpr_fn { template< - std::derived_from Filter + is_filter Filter > constexpr auto operator()( Filter filter ) const noexcept From d81aa7165a8ab87aac7923ba6acf25de26c5023c Mon Sep 17 00:00:00 2001 From: Joshua Rowe Date: Wed, 24 Dec 2025 13:49:40 -0800 Subject: [PATCH 33/46] Fix value_await_test compilation under clang --- Phantom.Coroutines.Test/value_awaiter_test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Phantom.Coroutines.Test/value_awaiter_test.cpp b/Phantom.Coroutines.Test/value_awaiter_test.cpp index c11d72b..d8811c3 100644 --- a/Phantom.Coroutines.Test/value_awaiter_test.cpp +++ b/Phantom.Coroutines.Test/value_awaiter_test.cpp @@ -35,7 +35,7 @@ ASYNC_TEST(value_awaiter_test, await_value_from_rvalue_returns_rvalue_reference) // The extra "move" is to workaround a compiler bug in MSVC where it copies the result of await_resume. decltype(auto) result = std::move(co_await std::move(awaiter)); #else - decltype(auto) result = co_await awaiter; + decltype(auto) result = co_await std::move(awaiter); #endif static_assert(std::same_as); EXPECT_EQ(0, lifetimeStatistics.copy_construction_count); From a1a473b27240352834fb51a42e494004786e63ca Mon Sep 17 00:00:00 2001 From: Joshua Rowe Date: Wed, 24 Dec 2025 14:09:29 -0800 Subject: [PATCH 34/46] Add missing #include --- Phantom.Coroutines.Test/tracing_test.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Phantom.Coroutines.Test/tracing_test.cpp b/Phantom.Coroutines.Test/tracing_test.cpp index 7b90cdf..8ee97ed 100644 --- a/Phantom.Coroutines.Test/tracing_test.cpp +++ b/Phantom.Coroutines.Test/tracing_test.cpp @@ -1,4 +1,5 @@ #include "async_test.h" +#include #include #include #include "Phantom.Coroutines/detail/config_macros.h" From d0792f15fcf8d839593ecb349e72142c0a0033ed Mon Sep 17 00:00:00 2001 From: Joshua Rowe Date: Wed, 24 Dec 2025 14:13:36 -0800 Subject: [PATCH 35/46] Fix missing global module fragment --- Phantom.Coroutines.Modules/await_none_await_transform.ixx | 1 + 1 file changed, 1 insertion(+) diff --git a/Phantom.Coroutines.Modules/await_none_await_transform.ixx b/Phantom.Coroutines.Modules/await_none_await_transform.ixx index 62afaa6..2825543 100644 --- a/Phantom.Coroutines.Modules/await_none_await_transform.ixx +++ b/Phantom.Coroutines.Modules/await_none_await_transform.ixx @@ -1,3 +1,4 @@ +module; #include "Phantom.Coroutines/detail/config_macros.h" export module Phantom.Coroutines.await_none_await_transform; import Phantom.Coroutines.type_traits; From 2118901590ccec32add768d0dd416a1a3f0d6cd9 Mon Sep 17 00:00:00 2001 From: Joshua Rowe Date: Wed, 24 Dec 2025 15:16:56 -0800 Subject: [PATCH 36/46] Add tracing return value capability for non-copyable non-movable types --- Phantom.Coroutines.Modules/tracing.ixx | 1 + Phantom.Coroutines.Test/tracing_test.cpp | 119 +- .../include/Phantom.Coroutines/tracing.h | 3411 +++++++++-------- 3 files changed, 1846 insertions(+), 1685 deletions(-) diff --git a/Phantom.Coroutines.Modules/tracing.ixx b/Phantom.Coroutines.Modules/tracing.ixx index c173423..2d08a1e 100644 --- a/Phantom.Coroutines.Modules/tracing.ixx +++ b/Phantom.Coroutines.Modules/tracing.ixx @@ -12,5 +12,6 @@ export module Phantom.Coroutines.tracing; import Phantom.Coroutines.awaiter_wrapper; import Phantom.Coroutines.coroutine; import Phantom.Coroutines.extensible_promise; +import Phantom.Coroutines.scope_guard; import Phantom.Coroutines.type_traits; #include "Phantom.Coroutines/tracing.h" diff --git a/Phantom.Coroutines.Test/tracing_test.cpp b/Phantom.Coroutines.Test/tracing_test.cpp index 8ee97ed..01b0fec 100644 --- a/Phantom.Coroutines.Test/tracing_test.cpp +++ b/Phantom.Coroutines.Test/tracing_test.cpp @@ -481,7 +481,9 @@ namespace Phantom::Coroutines::tracing ASYNC_TEST_F(tracing_tests, trace_return_value_moves_value) { auto filter = filters::event_type< - events::await_resume_result + events::await_resume_result, + // Verifies that we -only- get the result event. + events::await_resume_exception > && filters::awaiter_type< events::co_await_awaiter_type >; @@ -513,6 +515,121 @@ ASYNC_TEST_F(tracing_tests, trace_return_value_moves_value) EXPECT_EQ(1, lifetimeStatistics.move_construction_count); } +ASYNC_TEST_F(tracing_tests, trace_return_value_of_non_movable_value_copies_value) +{ + struct value_type + { + lifetime_tracker tracker; + + value_type(lifetime_statistics& lifetimeStatistics) + : tracker(lifetimeStatistics) + { + } + + value_type(const value_type&) = default; + value_type(value_type&&) = delete; + }; + + static_assert(!std::is_move_constructible_v); + static_assert(std::is_copy_constructible_v); + static_assert(std::is_move_constructible_v); + static_assert(std::is_copy_constructible_v); + + auto filter = filters::event_type< + events::await_resume_result, + // Verifies that we -only- get the result event. + events::await_resume_exception + > && filters::awaiter_type< + events::co_await_awaiter_type + >; + + lifetime_statistics lifetimeStatistics; + + auto taskLambda = [&]( + this auto& self, + traced_events_checker& eventsChecker, + auto filter + ) -> test_traced_task + { + EXPECT_EQ(0, lifetimeStatistics.copy_construction_count); + EXPECT_EQ(0, lifetimeStatistics.move_construction_count); + auto awaiter = value_awaiter{ value_type { lifetimeStatistics } }; + auto result = co_await awaiter; + static_assert(std::same_as); + EXPECT_EQ(1, lifetimeStatistics.copy_construction_count); + EXPECT_EQ(0, lifetimeStatistics.move_construction_count); + }; + + traced_events_checker tracedEventsChecker; + tracedEventsChecker = { [&] (const std::any&) + { + } }; + + co_await taskLambda( + tracedEventsChecker, + filter); + + EXPECT_EQ(1, lifetimeStatistics.copy_construction_count); + EXPECT_EQ(0, lifetimeStatistics.move_construction_count); +} + +ASYNC_TEST_F(tracing_tests, trace_return_value_of_non_copyable_value_does_not_trace_value) +{ + struct value_type + { + lifetime_tracker tracker; + + value_type(lifetime_statistics& lifetimeStatistics) + : tracker(lifetimeStatistics) + { + } + + value_type(const value_type&) = delete; + }; + + static_assert(!std::is_move_constructible_v); + static_assert(!std::is_copy_constructible_v); + static_assert(std::is_move_constructible_v); + static_assert(std::is_copy_constructible_v); + + auto filter = filters::event_type< + events::await_resume_result, + // Verifies that we -only- get the result event. + events::await_resume_exception + > && filters::awaiter_type< + events::co_await_awaiter_type + >; + + lifetime_statistics lifetimeStatistics; + + auto taskLambda = [&]( + this auto& self, + traced_events_checker& eventsChecker, + auto filter + ) -> test_traced_task + { + EXPECT_EQ(0, lifetimeStatistics.copy_construction_count); + EXPECT_EQ(0, lifetimeStatistics.move_construction_count); + auto awaiter = value_awaiter{ value_type { lifetimeStatistics } }; + auto& result = co_await awaiter; + static_assert(std::same_as); + EXPECT_EQ(0, lifetimeStatistics.copy_construction_count); + EXPECT_EQ(0, lifetimeStatistics.move_construction_count); + }; + + traced_events_checker tracedEventsChecker; + tracedEventsChecker = { [&] (const std::any&) + { + } }; + + co_await taskLambda( + tracedEventsChecker, + filter); + + EXPECT_EQ(0, lifetimeStatistics.copy_construction_count); + EXPECT_EQ(0, lifetimeStatistics.move_construction_count); +} + ASYNC_TEST_F(tracing_tests, traces_create_and_destroy_promise) { auto filter = filters::event_type< diff --git a/Phantom.Coroutines/include/Phantom.Coroutines/tracing.h b/Phantom.Coroutines/include/Phantom.Coroutines/tracing.h index 2242bc5..9b9ea86 100644 --- a/Phantom.Coroutines/include/Phantom.Coroutines/tracing.h +++ b/Phantom.Coroutines/include/Phantom.Coroutines/tracing.h @@ -1,1684 +1,1727 @@ -#ifndef PHANTOM_COROUTINES_INCLUDE_TRACING_H -#define PHANTOM_COROUTINES_INCLUDE_TRACING_H -#ifndef PHANTOM_COROUTINES_COMPILING_MODULES -#include -#include -#include -#include -#include -#include -#include -#include -#include "detail/config_macros.h" -#include "detail/coroutine.h" -#include "awaiter_wrapper.h" -#include "extensible_promise.h" -#include "type_traits.h" -#endif - -static_assert(PHANTOM_COROUTINES_IS_CONFIGURED); -PHANTOM_COROUTINES_ASSERT_IS_MODULE; - -namespace Phantom::Coroutines -{ - -namespace tracing -{ - -PHANTOM_COROUTINES_MODULE_EXPORT -template< - typename TraceSink -> concept is_trace_sink = true; - -namespace events -{ -PHANTOM_COROUTINES_MODULE_EXPORT -template< - typename Awaiter -> concept is_traced_promise_initial_suspend_awaiter = std::remove_cvref_t::is_traced_promise_initial_suspend_awaiter; - -PHANTOM_COROUTINES_MODULE_EXPORT -template< - typename Awaiter -> concept is_traced_promise_final_suspend_awaiter = std::remove_cvref_t::is_traced_promise_final_suspend_awaiter; - -PHANTOM_COROUTINES_MODULE_EXPORT -template< - typename Awaiter -> concept is_traced_promise_co_yield_awaiter = std::remove_cvref_t::is_traced_promise_co_yield_awaiter; - -PHANTOM_COROUTINES_MODULE_EXPORT -template< - typename Awaiter -> concept is_traced_promise_co_await_awaiter = std::remove_cvref_t::is_traced_promise_co_await_awaiter; - -PHANTOM_COROUTINES_MODULE_EXPORT -template< - typename TValue -> -struct value -{ - using value_type = TValue; - const value_type& Value; - - friend auto operator<=>(const value&, const value&) = default; -}; - -PHANTOM_COROUTINES_MODULE_EXPORT -template< - typename ... Args -> -using arguments = value>; - -PHANTOM_COROUTINES_MODULE_EXPORT -constexpr auto make_arguments( - const auto&... args -) -{ - return value{ std::tie(std::as_const(args)...) }; -} - -PHANTOM_COROUTINES_MODULE_EXPORT -template< - typename Label -> -struct event_type -{ - event_type() = default; - event_type( - Label - ) - { - } - - using label_type = Label; - - template< - typename Label1, - typename Label2 - > - friend consteval bool operator==( - const event_type&, - const event_type&); - - template< - typename Label1, - typename Label2 - > - friend consteval bool operator!=( - const event_type&, - const event_type&); -}; - -PHANTOM_COROUTINES_MODULE_EXPORT -template< - typename Label1, - typename Label2 -> -consteval bool operator==( - const event_type&, - const event_type&) -{ - return std::same_as; -} - -PHANTOM_COROUTINES_MODULE_EXPORT -template< - typename Label1, - typename Label2 -> -consteval bool operator!=( - const event_type&, - const event_type&) -{ - return !std::same_as; -} - -PHANTOM_COROUTINES_MODULE_EXPORT -template< - typename TEventType, - typename TEventData, - template typename ... Accessors -> -struct event : - Accessors>... -{ - using event_type = event_type; - using event_data_type = TEventData; - - std::source_location SourceLocation; - event_type EventType; - event_data_type EventData; -}; - -PHANTOM_COROUTINES_MODULE_EXPORT -struct empty_value -{ - using value_type = void; - static constexpr void* Value = nullptr; - - friend auto operator<=>(const empty_value&, const empty_value&) = default; -}; - -PHANTOM_COROUTINES_MODULE_EXPORT -struct empty_arguments_value -{ - using value_type = std::tuple<>; - static constexpr value_type Value = {}; - - friend auto operator<=>(const empty_arguments_value&, const empty_arguments_value&) = default; -}; - -PHANTOM_COROUTINES_MODULE_EXPORT -constexpr empty_value empty{}; -PHANTOM_COROUTINES_MODULE_EXPORT -constexpr empty_arguments_value empty_arguments{ }; - -PHANTOM_COROUTINES_MODULE_EXPORT -struct no_awaiter_type -{ - static constexpr bool is_traced_promise_initial_suspend_awaiter = false; - static constexpr bool is_traced_promise_final_suspend_awaiter = false; - static constexpr bool is_traced_promise_co_yield_awaiter = false; - static constexpr bool is_traced_promise_co_await_awaiter = false; -}; - -PHANTOM_COROUTINES_MODULE_EXPORT -struct initial_suspend_awaiter_type -{ - static constexpr bool is_traced_promise_initial_suspend_awaiter = true; - static constexpr bool is_traced_promise_final_suspend_awaiter = false; - static constexpr bool is_traced_promise_co_yield_awaiter = false; - static constexpr bool is_traced_promise_co_await_awaiter = false; -}; - -PHANTOM_COROUTINES_MODULE_EXPORT -struct final_suspend_awaiter_type -{ - static constexpr bool is_traced_promise_initial_suspend_awaiter = false; - static constexpr bool is_traced_promise_final_suspend_awaiter = true; - static constexpr bool is_traced_promise_co_yield_awaiter = false; - static constexpr bool is_traced_promise_co_await_awaiter = false; -}; - -PHANTOM_COROUTINES_MODULE_EXPORT -struct co_yield_awaiter_type -{ - static constexpr bool is_traced_promise_initial_suspend_awaiter = false; - static constexpr bool is_traced_promise_final_suspend_awaiter = false; - static constexpr bool is_traced_promise_co_yield_awaiter = true; - static constexpr bool is_traced_promise_co_await_awaiter = false; -}; - -PHANTOM_COROUTINES_MODULE_EXPORT -struct co_await_awaiter_type -{ - static constexpr bool is_traced_promise_initial_suspend_awaiter = false; - static constexpr bool is_traced_promise_final_suspend_awaiter = false; - static constexpr bool is_traced_promise_co_yield_awaiter = false; - static constexpr bool is_traced_promise_co_await_awaiter = true; -}; - -template< - typename T -> concept is_empty = -std::same_as -|| -std::same_as -|| -std::same_as -|| -std::same_as; - -template< - typename T -> concept is_not_empty = !is_empty; - -template< - typename Event -> -struct PromiseAccessor -{ - static constexpr bool has_promise = requires(Event self) - { - { self.Data.Promise } -> is_not_empty; - }; - - auto* promise(this auto& self) - { - if constexpr (has_promise) - { - return self.Data.Promise; - } - else - { - return ∅ - } - } - - static constexpr bool has_traced_promise = requires(Event self) - { - { self.Data.TracedPromise } -> is_not_empty; - }; - - auto* traced_promise(this auto& self) - { - if constexpr (has_traced_promise) - { - return self.Data.TracedPromise; - } - else - { - return ∅ - } - } - - static constexpr bool has_promise_creation_arguments = requires(Event self) - { - { self.Data.PromiseCreationArguments } -> is_not_empty; - }; - - auto* promise_creation_arguments( - this auto& self - ) - { - if constexpr (has_promise_creation_arguments) - { - return self.Data.PromiseCreationArguments; - } - else - { - return &empty_arguments; - } - } -}; - -template< - typename Event -> -struct AwaiterAccessor -{ - static constexpr bool has_awaiter = requires(Event self) - { - { self.Data.Awaiter } -> is_not_empty; - }; - - auto* awaiter(this auto& self) - { - if constexpr (has_awaiter) - { - return self.Data.Awaiter; - } - else - { - return ∅ - } - } - - static constexpr bool has_awaiter_type = requires(Event self) - { - { self.Data.AwaiterType } -> is_not_empty; - }; - - auto* awaiter_type(this auto& self) - { - if constexpr (has_awaiter_type) - { - return self.Data.AwaiterType; - } - else - { - return ∅ - } - } - - static constexpr bool has_traced_awaiter = requires(Event self) - { - { self.Data.TracedAwaiter } -> is_not_empty; - }; - - auto* traced_awaiter(this auto& self) - { - if constexpr (has_awaiter) - { - return self.Data.TracedAwaiter; - } - else - { - return ∅ - } - } - -}; - -template< - typename Event -> -struct MethodArgumentsAccessor -{ - static constexpr bool has_method_arguments = requires(Event self) - { - { self.Data.MethodArguments } -> is_not_empty; - }; - - auto* method_arguments(this auto& self) - { - if constexpr (has_method_arguments) - { - return self.Data.MethodArguments; - } - else - { - return &empty_arguments; - } - } -}; - -template< - typename Event -> -struct MethodResultAccessor -{ - static constexpr bool has_method_result = requires(Event self) - { - { self.Data.MethodResult } -> is_not_empty; - }; - - auto* method_result(this auto& self) - { - if constexpr (has_method_result) - { - return self.Data.MethodResult; - } - else - { - return ∅ - } - } -}; - -template< - typename Event -> -struct MethodExceptionAccessor -{ - static constexpr bool has_method_exception = requires(Event self) - { - { self.Data.MethodException } -> is_not_empty; - }; - - auto* method_exception(this auto& self) - { - if constexpr (has_method_exception) - { - return self.Data.MethodException; - } - else - { - return ∅ - } - } -}; - -template< - typename Event -> -struct UnhandledExceptionAccessor -{ - static constexpr bool has_unhandled_exception = requires(Event self) - { - { self.Data.UnhandledException } -> is_not_empty; - }; - - auto* unhandled_exception(this auto& self) - { - if constexpr (has_unhandled_exception) - { - return self.Data.UnhandledException; - } - else - { - return ∅ - } - } -}; - -template< - typename Event -> -struct YieldValueAccessor -{ - static constexpr bool has_yield_value = requires(Event self) - { - { self.Data.YieldValue } -> is_not_empty; - }; - - auto* yield_value(this auto& self) - { - if constexpr (has_yield_value) - { - return self.Data.YieldValue; - } - else - { - return ∅ - } - } -}; - -template< - typename Event -> -struct ReturnValueAccessor -{ - static constexpr bool has_return_value = requires(Event self) - { - { self.Data.ReturnValue } -> is_not_empty; - }; - - auto* return_value(this auto& self) - { - if constexpr (has_return_value) - { - return self.Data.ReturnValue; - } - else - { - return ∅ - } - } -}; - -PHANTOM_COROUTINES_MODULE_EXPORT -struct create_promise {}; -PHANTOM_COROUTINES_MODULE_EXPORT -struct destroy_promise {}; -PHANTOM_COROUTINES_MODULE_EXPORT -struct await_ready_begin {}; -PHANTOM_COROUTINES_MODULE_EXPORT -struct await_ready_result {}; -PHANTOM_COROUTINES_MODULE_EXPORT -struct await_ready_exception {}; -PHANTOM_COROUTINES_MODULE_EXPORT -struct await_suspend_begin {}; -PHANTOM_COROUTINES_MODULE_EXPORT -struct await_suspend_result {}; -PHANTOM_COROUTINES_MODULE_EXPORT -struct await_suspend_exception {}; -PHANTOM_COROUTINES_MODULE_EXPORT -struct await_resume_begin {}; -PHANTOM_COROUTINES_MODULE_EXPORT -struct await_resume_result {}; -PHANTOM_COROUTINES_MODULE_EXPORT -struct await_resume_exception {}; -PHANTOM_COROUTINES_MODULE_EXPORT -struct await_transform_begin {}; -PHANTOM_COROUTINES_MODULE_EXPORT -struct await_transform_result {}; -PHANTOM_COROUTINES_MODULE_EXPORT -struct await_transform_exception {}; -PHANTOM_COROUTINES_MODULE_EXPORT -struct unhandled_exception_begin {}; -PHANTOM_COROUTINES_MODULE_EXPORT -struct unhandled_exception_result {}; -PHANTOM_COROUTINES_MODULE_EXPORT -struct unhandled_exception_exception {}; -PHANTOM_COROUTINES_MODULE_EXPORT -struct get_return_object_begin {}; -PHANTOM_COROUTINES_MODULE_EXPORT -struct get_return_object_result {}; -PHANTOM_COROUTINES_MODULE_EXPORT -struct get_return_object_exception {}; -PHANTOM_COROUTINES_MODULE_EXPORT -struct yield_value_begin {}; -PHANTOM_COROUTINES_MODULE_EXPORT -struct yield_value_result {}; -PHANTOM_COROUTINES_MODULE_EXPORT -struct yield_value_exception {}; -PHANTOM_COROUTINES_MODULE_EXPORT -struct return_value_begin {}; -PHANTOM_COROUTINES_MODULE_EXPORT -struct return_value_result {}; -PHANTOM_COROUTINES_MODULE_EXPORT -struct return_value_exception {}; -PHANTOM_COROUTINES_MODULE_EXPORT -struct return_void_begin {}; -PHANTOM_COROUTINES_MODULE_EXPORT -struct return_void_result {}; -PHANTOM_COROUTINES_MODULE_EXPORT -struct return_void_exception {}; - -PHANTOM_COROUTINES_MODULE_EXPORT -template< - typename Event, - typename EventType -> concept is_event_type = std::same_as; - -PHANTOM_COROUTINES_MODULE_EXPORT -template< - typename TPromise = empty_value, - typename TTracedPromise = empty_value, - typename TPromiseCreationArguments = empty_arguments_value, - typename TAwaiter = empty_value, - typename TTracedAwaiter = empty_value, - typename TAwaiterType = no_awaiter_type, - typename TMethodArguments = empty_arguments_value, - typename TMethodResult = empty_value, - typename TMethodException = empty_value, - typename TUnhandledException = empty_value, - typename TYieldValue = empty_value, - typename TReturnValue = empty_value -> -struct event_data -{ - PHANTOM_COROUTINES_NO_UNIQUE_ADDRESS TPromise Promise = empty; - PHANTOM_COROUTINES_NO_UNIQUE_ADDRESS TTracedPromise TracedPromise = empty; - PHANTOM_COROUTINES_NO_UNIQUE_ADDRESS TPromiseCreationArguments PromiseCreationArguments = empty_arguments; - PHANTOM_COROUTINES_NO_UNIQUE_ADDRESS TAwaiter Awaiter = empty; - PHANTOM_COROUTINES_NO_UNIQUE_ADDRESS TTracedAwaiter TracedAwaiter = empty; - PHANTOM_COROUTINES_NO_UNIQUE_ADDRESS TAwaiterType AwaiterType = no_awaiter_type{}; - PHANTOM_COROUTINES_NO_UNIQUE_ADDRESS TMethodArguments MethodArguments = empty_arguments; - PHANTOM_COROUTINES_NO_UNIQUE_ADDRESS TMethodResult MethodResult = empty; - PHANTOM_COROUTINES_NO_UNIQUE_ADDRESS TMethodException MethodException = empty; - PHANTOM_COROUTINES_NO_UNIQUE_ADDRESS TUnhandledException UnhandledException = empty; - PHANTOM_COROUTINES_NO_UNIQUE_ADDRESS TYieldValue YieldValue = empty; - PHANTOM_COROUTINES_NO_UNIQUE_ADDRESS TReturnValue ReturnValue = empty; - - friend auto operator<=>(const event_data&, const event_data&) = default; - - static constexpr auto combine( - const auto& lhs, - const auto& rhs - ) - { - if constexpr (is_empty>) - { - return lhs; - } - else - { - return rhs; - } - } - - auto with( - const auto& other - ) const - { - return events::event_data - { - .Promise = combine( - this->Promise, - other.Promise), - .TracedPromise = combine( - this->TracedPromise, - other.TracedPromise), - .PromiseCreationArguments = combine( - this->PromiseCreationArguments, - other.PromiseCreationArguments), - .Awaiter = combine( - this->Awaiter, - other.Awaiter), - .TracedAwaiter = combine( - this->TracedAwaiter, - other.TracedAwaiter), - .AwaiterType = combine( - this->AwaiterType, - other.AwaiterType), - .MethodArguments = combine( - this->MethodArguments, - other.MethodArguments), - .MethodResult = combine( - this->MethodResult, - other.MethodResult), - .MethodException = combine( - this->MethodException, - other.MethodException), - .UnhandledException = combine( - this->UnhandledException, - other.UnhandledException), - .YieldValue = combine( - this->YieldValue, - other.YieldValue), - .ReturnValue = combine( - this->ReturnValue, - other.ReturnValue), - }; - } -}; - -template< - typename EventType, - typename EventData -> -using basic_event_type = event< - EventType, - EventData, - PromiseAccessor, - AwaiterAccessor, - MethodArgumentsAccessor, - MethodResultAccessor, - MethodExceptionAccessor, - UnhandledExceptionAccessor, - YieldValueAccessor, - ReturnValueAccessor ->; - -PHANTOM_COROUTINES_MODULE_EXPORT -template< - typename EventType, - typename EventData -> -auto basic_event( - const std::source_location& sourceLocation, - const EventType& eventType, - const EventData& eventData -) -{ - return basic_event_type< - EventType, - EventData - > - { - .SourceLocation = sourceLocation, - .EventType = eventType, - .EventData = eventData, - }; -} - -// namespace events -} - -namespace detail -{ - -template< - is_trace_sink TraceSink -> -struct trace_sink_accessor -{ - TraceSink m_traceSink; - - auto& trace_sink() - { - return m_traceSink; - } -}; - -template< - typename BeginEventType, - typename ResultEventType, - typename ExceptionEventType -> -struct method_events_group -{ - method_events_group( - BeginEventType, - ResultEventType, - ExceptionEventType) - { } - - using begin_event_type = BeginEventType; - using result_event_type = ResultEventType; - using exception_event_type = ExceptionEventType; - - BeginEventType BeginEvent; - ResultEventType ResultEvent; - ExceptionEventType ExceptionEvent; -}; - -} // namespace detail - -auto call_traced_method( - const auto& traceSink, - auto events, - std::source_location sourceLocation, - const auto& baseEventData, - std::invocable<> auto call -) -> std::invoke_result_t -{ - using result_type = std::invoke_result_t; - try - { - traceSink( - events::basic_event( - sourceLocation, - events.BeginEvent, - baseEventData)); - if constexpr (std::same_as) - { - call(); - traceSink( - events::basic_event( - sourceLocation, - events.ResultEvent, - baseEventData)); - } - else - { - decltype(auto) result = call(); - traceSink( - events::basic_event( - sourceLocation, - events.ResultEvent, - baseEventData.with( - events::event_data - { - .MethodResult = events::value{ result } - }))); - return std::forward(result); - } - } - catch (...) - { - auto exception = std::current_exception(); - traceSink( - events::basic_event( - sourceLocation, - events.ExceptionEvent, - baseEventData.with( - events::event_data - { - .MethodException = events::value{ exception } - }))); - throw; - } -} - -PHANTOM_COROUTINES_MODULE_EXPORT -template< - is_trace_sink TraceSink, - typename Awaitable, - typename AwaiterType, - typename BaseEventData -> -struct traced_awaiter : - detail::trace_sink_accessor, - awaiter_wrapper, - AwaiterType -{ - using awaiter_wrapper = awaiter_wrapper; - using trace_sink_accessor = traced_awaiter::trace_sink_accessor; - using traced_awaiter::trace_sink_accessor::trace_sink; - using wrapped_awaiter_type = std::remove_reference_t; - - std::source_location m_sourceLocation; - BaseEventData m_baseEventData; - - auto call_awaiter( - this auto& awaiter, - auto events, - const auto& baseEventData, - std::invocable<> auto call - - ) -> std::invoke_result_t - { - return call_traced_method( - awaiter.traced_awaiter::trace_sink(), - events, - awaiter.m_sourceLocation, - baseEventData.with( - events::event_data - { - .Awaiter = static_cast(std::addressof(awaiter.awaiter())), - .TracedAwaiter = std::addressof(awaiter), - .AwaiterType = AwaiterType{}, - }), - call); - } - - decltype(auto) await_ready( - this auto& self - ) noexcept(noexcept(self.awaiter_wrapper::await_ready())) - { - return self.traced_awaiter::call_awaiter( - detail::method_events_group - { - events::await_ready_begin{}, - events::await_ready_result{}, - events::await_ready_exception{}, - }, - self.get_event_data( - events::empty_arguments), - [&]() -> decltype(auto) - { - return self.awaiter_wrapper::await_ready(); - } - ); - } - - template< - typename Arg - > - decltype(auto) await_suspend( - this auto& self, - Arg&& arg - ) noexcept(noexcept(self.awaiter_wrapper::await_suspend(std::forward(arg)))) - { - return self.traced_awaiter::call_awaiter( - detail::method_events_group - { - events::await_suspend_begin{}, - events::await_suspend_result{}, - events::await_suspend_exception{}, - }, - self.get_event_data( - make_arguments(arg)), - [&]() -> decltype(auto) - { - return self.awaiter_wrapper::await_suspend( - std::forward(arg)); - } - ); - } - - decltype(auto) await_resume( - this auto& self - ) noexcept(noexcept(self.awaiter_wrapper::await_resume())) - { - return self.traced_awaiter::call_awaiter( - detail::method_events_group - { - events::await_resume_begin{}, - events::await_resume_result{}, - events::await_resume_exception{}, - }, - self.get_event_data( - events::empty_arguments), - [&]() -> decltype(auto) - { - return self.awaiter_wrapper::await_resume(); - } - ); - } - - traced_awaiter( - std::source_location sourceLocation, - std::invocable auto awaiterFunction, - AwaiterType, - trace_sink_accessor traceSinkAccessor, - BaseEventData baseEventData - ) - : - trace_sink_accessor{ traceSinkAccessor }, - m_sourceLocation{ sourceLocation }, - awaiter_wrapper{ std::move(awaiterFunction) }, - m_baseEventData{ std::move(baseEventData) } - { - } - - template< - typename ... Args - > - auto get_event_data( - this auto& awaiter, - const auto& methodArguments - ) - { - return awaiter.m_baseEventData.with( - events::event_data - { - .Awaiter = &awaiter, - .MethodArguments = methodArguments, - }); - } -}; - -template< - is_trace_sink TraceSink, - std::invocable<> AwaiterFunction, - typename AwaiterType, - typename BaseEventData -> -traced_awaiter( - std::source_location, - AwaiterFunction, - AwaiterType, - detail::trace_sink_accessor, - BaseEventData -) -> traced_awaiter< - TraceSink, - std::invoke_result_t, - AwaiterType, - BaseEventData ->; - - -namespace detail -{ - -// This class stored the trace sink and produces non-result-specific tracing events. -PHANTOM_COROUTINES_MODULE_EXPORT -template< - is_trace_sink TraceSink -> -class traced_promise_trace_sink_storage -{ - template< - typename Declaration - > - struct traced_promise_trace_sink_accessor; - -public: - using trace_sink_type = TraceSink; - -public: - trace_sink_type m_traceSink; - - template< - typename ... Args - > - requires - is_constructible_from_promise_arguments - traced_promise_trace_sink_storage( - Args&& ... args - ) : - m_traceSink(std::forward(args)...) - { - } - - // Allow default construction of the trace sink when arguments are provided. - template< - typename ... Args - > - requires - is_default_constructible_from_promise_arguments - traced_promise_trace_sink_storage( - Args&& ... args - ) : - m_traceSink() - { - } - - auto get_event_data( - this auto& promise - ) - { - return events::event_data - { - .Promise = &promise.get_underlying_promise(), - .TracedPromise = &promise, - }; - } - - decltype(auto) call_promise_method( - this auto& promise, - auto events, - std::source_location sourceLocation, - const auto& baseEventData, - std::invocable auto call - ) - { - return call_traced_method( - promise.traced_promise_trace_sink_storage::m_traceSink, - events, - sourceLocation, - baseEventData.with( - promise.get_event_data()), - call); - } -}; - -// This class embodies constructors for traced promises. -template< - is_trace_sink TraceSink, - typename BasePromise -> -class traced_promise_construction - : - public traced_promise_trace_sink_storage, - public derived_promise -{ -public: - template< - typename ... Args - > - requires - std::constructible_from, Args&&...> - && - std::constructible_from, Args&&...> - traced_promise_construction( - Args&& ... args - ) - : - traced_promise_construction::traced_promise_trace_sink_storage(std::forward(args)...), - traced_promise_construction::derived_promise(std::forward(args)... ) - { - } -}; - -// This class provides an implementation of yield_value for a promise -// if it has a yield_value implementation. -// The main template does not provide yield_value. -template< - is_trace_sink TraceSink, - typename BasePromise -> -class traced_promise_yield_value - : - public traced_promise_construction -{ -public: - using traced_promise_yield_value::traced_promise_construction::traced_promise_construction; -}; - -// The specialization for promises that have yield_value. -template< - is_trace_sink TraceSink, - has_yield_value BasePromise -> -class traced_promise_yield_value< - TraceSink, - BasePromise -> - : - public traced_promise_construction -{ -public: - using traced_promise_yield_value::traced_promise_construction::traced_promise_construction; - - // Trace an delegate the base class's yield_value implementation. - template< - typename TPromise - > - decltype(auto) yield_value( - this TPromise&& promise, - auto&& value, - std::source_location sourceLocation = std::source_location::current() - ) - { - return promise.traced_promise_yield_value::call_promise_method( - promise, - detail::method_events_group - { - events::yield_value_begin{}, - events::yield_value_result{}, - events::yield_value_exception{}, - }, - sourceLocation, - events::event_data - { - .MethodArguments = events::make_arguments(value), - }, - [&]() - { - return std::forward(promise).traced_promise_yield_value::yield_value( - std::forward(value)); - } - ); - } -}; - -template< - is_trace_sink TraceSink, - typename BasePromise -> -class traced_promise_return_value_or_void - : - public traced_promise_yield_value -{ - using traced_promise_return_value_or_void::traced_promise_yield_value::traced_promise_yield_value; - -public: - using traced_promise_return_value_or_void::traced_promise_yield_value::call_promise_method; - - template< - typename TPromise, - typename Value - > - void return_value( - this TPromise&& promise, - Value&& value, - std::source_location sourceLocation = std::source_location::current() - ) - { - std::forward(promise).call_promise_method( - detail::method_events_group - { - events::return_value_begin{}, - events::return_value_result{}, - events::return_value_exception{}, - }, - sourceLocation, - events::event_data - { - .MethodArguments = events::make_arguments(value), - }, - [&]() - { - std::forward(promise).traced_promise_return_value_or_void::traced_promise_yield_value::return_value( - std::forward(value)); - } - ); - } -}; - -template< - is_trace_sink TraceSink, - has_return_void BasePromise -> -class traced_promise_return_value_or_void< - TraceSink, - BasePromise -> - : - public traced_promise_yield_value -{ - using traced_promise_return_value_or_void::traced_promise_yield_value::traced_promise_yield_value; - -public: - using traced_promise_return_value_or_void::traced_promise_yield_value::call_promise_method; - - template< - typename TPromise - > - void return_void( - this TPromise&& promise, - std::source_location sourceLocation = std::source_location::current() - ) - { - std::forward(promise).call_promise_method( - detail::method_events_group - { - events::return_void_begin{}, - events::return_void_result{}, - events::return_void_exception{}, - }, - sourceLocation, - events::event_data - { - }, - [&]() - { - std::forward(promise).traced_promise_return_value_or_void::traced_promise_yield_value::return_void(); - } - ); - } -}; - -template< - is_trace_sink TraceSink, - typename BasePromise -> -using traced_promise_base = traced_promise_return_value_or_void; - -} // namespace detail - -// Use suppress_trace to suppress tracing of an awaitable. -// Example: -// co_await suppress_trace{ m_event.Wait() }; -PHANTOM_COROUTINES_MODULE_EXPORT -template< - typename Awaiter -> -struct suppress_trace -{ - Awaiter&& value; -}; - -// Use trace to send a value to the trace sink. -// Example: -// co_await trace{ my_trace_event_information{} }; -PHANTOM_COROUTINES_MODULE_EXPORT -struct trace -{ -}; - -PHANTOM_COROUTINES_MODULE_EXPORT -template< - is_trace_sink TraceSink, - is_extensible_promise BasePromise -> -class traced_promise - : - public detail::traced_promise_base -{ -public: - using traced_promise_base = detail::traced_promise_base; - using traced_promise_base::call_promise_method; - using traced_promise_base::get_event_data; - using traced_promise_base::m_traceSink; - - template< - typename Declaration - > - struct traced_promise_trace_sink_accessor; - -public: - template< - typename ... Args - > - traced_promise( - Args&& ... args - ) - : - traced_promise_base{ std::forward(args)... } - { - m_traceSink( - events::basic_event( - std::source_location::current(), - events::create_promise{}, - events::event_data - { - .Promise = &this->get_underlying_promise(), - .TracedPromise = this, - .PromiseCreationArguments = events::make_arguments(args...), - })); - } - - ~traced_promise() - { - m_traceSink( - basic_event( - std::source_location::current(), - events::destroy_promise{}, - get_event_data() - )); - } - - template< - typename TPromise - > - auto initial_suspend( - this TPromise& promise, - std::source_location sourceLocation = std::source_location::current() - ) - { - return traced_awaiter - { - sourceLocation, - [&]() { return promise.traced_promise_base::initial_suspend(); }, - events::initial_suspend_awaiter_type{}, - detail::trace_sink_accessor - { - promise.traced_promise::m_traceSink - }, - promise.get_event_data() - }; - } - - template< - typename TPromise - > - auto final_suspend( - this TPromise& promise, - std::source_location sourceLocation = std::source_location::current() - ) noexcept - { - return traced_awaiter - { - sourceLocation, - [&]() { return promise.traced_promise_base::final_suspend(); }, - events::final_suspend_awaiter_type{}, - detail::trace_sink_accessor - { - promise.traced_promise::m_traceSink - }, - promise.get_event_data() - }; - } - - template< - typename TPromise - > - void unhandled_exception( - this TPromise& promise, - std::source_location sourceLocation = std::source_location::current() - ) - { - promise.call_promise_method( - detail::method_events_group - { - events::unhandled_exception_begin{}, - events::unhandled_exception_result{}, - events::unhandled_exception_exception{}, - }, - sourceLocation, - events::event_data{}, - [&]() - { - return promise.traced_promise_base::unhandled_exception(); - } - ); - } - - auto await_transform( - this auto& promise, - auto&& awaiter, - std::source_location sourceLocation = std::source_location::current() - ) - { - return traced_awaiter - { - sourceLocation, - [&]() -> decltype(auto) - { - return promise.traced_promise_base::await_transform( - std::forward(awaiter)); - }, - events::co_await_awaiter_type{}, - detail::trace_sink_accessor - { - promise.traced_promise::m_traceSink - }, - promise.get_event_data() - }; - } - - template< - typename Awaiter - > decltype(auto) await_transform( - this auto& promise, - const suppress_trace& noTraceAwaiter - ) - { - return promise.traced_promise_base::await_transform( - noTraceAwaiter.value - ); - } - - suspend_never await_transform( - this auto& promise, - std::derived_from auto& traceEvent - ) - { - promise.m_traceSink( - traceEvent); - return suspend_never{}; - } - - template< - typename This - > - auto& get_underlying_promise( - this This& self) - { - if constexpr (std::is_const_v) - { - return static_cast(self); - } - else - { - return static_cast(self); - } - } -}; - -PHANTOM_COROUTINES_MODULE_EXPORT -namespace filters -{ - -struct filter; - -template< - typename T -> -concept is_filter = std::derived_from; - -template< - is_filter Left, - is_filter Right -> -struct and_filter; - -template< - is_filter Left, - is_filter Right -> -struct or_filter; - -template< - is_filter Filter -> -struct not_filter; - -struct filter -{ - constexpr std::false_type operator()(const auto&) const noexcept { - return {}; - } - - template< - is_filter Left, - is_filter Right - > - friend constexpr auto operator&&( - Left left, - Right right - ) noexcept - { - return and_filter{ left, right }; - } - - template< - is_filter Left, - is_filter Right - > - friend constexpr auto operator||( - Left left, - Right right - ) noexcept - { - return or_filter{ left, right }; - } - - template< - is_filter Filter - > - friend constexpr auto operator!( - Filter filter - ) noexcept - { - return not_filter{ filter }; - } -}; - -template< - is_filter Left, - is_filter Right -> -struct and_filter : filter -{ - Left left; - Right right; - - constexpr bool operator()( - const auto& event - ) const noexcept - { - using leftType = decltype(left(event)); - using rightType = decltype(right(event)); - - if constexpr ( - std::same_as - || - std::same_as) - { - return std::false_type{}; - } - else if constexpr ( - std::same_as && - std::same_as) - { - return std::true_type{}; - } - else - { - return left(event) && right(event); - } - } -}; - -template< - is_filter Left, - is_filter Right -> -struct or_filter : filter -{ - Left left; - Right right; - - constexpr bool operator()( - const auto& event - ) const noexcept - { - using leftType = decltype(left(event)); - using rightType = decltype(right(event)); - - if constexpr ( - std::same_as - || - std::same_as) - { - return std::true_type{}; - } - else if constexpr ( - std::same_as && - std::same_as) - { - return std::false_type{}; - } - else - { - return left(event) && right(event); - } - } -}; - -template< - is_filter Filter -> -struct not_filter : filter -{ - Filter filter; - - constexpr auto operator()( - const auto& event) - { - using type = decltype(filter(event)); - if constexpr (std::same_as) - { - return std::false_type{}; - } - else if constexpr (std::same_as) - { - return std::true_type{}; - } - else - { - return !filter(event); - } - }; -}; - -struct any_event_fn : filter -{ - constexpr std::true_type operator()(const auto&) const noexcept { - return {}; - } -}; -constexpr any_event_fn any_event{}; - -template< - typename ... EventType -> -struct event_type_fn : filter -{ - using filter::operator(); - constexpr std::true_type operator()( - const auto& event - ) const noexcept - requires ( - events::is_event_type, EventType> || ... - ) - { - return {}; - } -}; - -template< - typename ... EventType -> -event_type_fn event_type; - -template< - typename AwaiterType -> -struct awaiter_type_fn : filter -{ - using filter::operator(); - constexpr std::true_type operator()( - const auto& event - ) const noexcept - requires std::same_as< - const decltype(event.EventData.AwaiterType)&, - const AwaiterType& - > - { - return {}; - } -}; - -template< - typename AwaiterType -> -constexpr awaiter_type_fn awaiter_type; - -template< - typename Event -> -struct check_constexpr_fn -{ - template< - is_filter Filter - > constexpr auto operator()( - Filter filter - ) const noexcept - { - return decltype(filter(std::declval())){}; - } -}; - -template< - typename Event -> constexpr check_constexpr_fn check_constexpr{}; - -constexpr auto constant_filtered_trace_sink( - auto filter, - auto&& traceSink -) -{ - return [traceSink = std::forward(traceSink), filter](const auto& event) - { - if constexpr (filter(event)) - { - traceSink(event); - } - }; -} - -constexpr auto runtime_filtered_trace_sink( - auto filter, - auto&& traceSink -) -{ - return [traceSink = std::forward(traceSink), filter](const auto& event) - { - if (filter(event)) - { - traceSink(event); - } - }; -} - -// namespace filters -} - -// namespace tracing -} - - -// namespace Phantom::Coroutines -} - -#endif +#ifndef PHANTOM_COROUTINES_INCLUDE_TRACING_H +#define PHANTOM_COROUTINES_INCLUDE_TRACING_H +#ifndef PHANTOM_COROUTINES_COMPILING_MODULES +#include +#include +#include +#include +#include +#include +#include +#include +#include "detail/config_macros.h" +#include "detail/coroutine.h" +#include "detail/scope_guard.h" +#include "awaiter_wrapper.h" +#include "extensible_promise.h" +#include "type_traits.h" +#endif + +static_assert(PHANTOM_COROUTINES_IS_CONFIGURED); +PHANTOM_COROUTINES_ASSERT_IS_MODULE; + +namespace Phantom::Coroutines +{ + +namespace tracing +{ + +PHANTOM_COROUTINES_MODULE_EXPORT +template< + typename TraceSink +> concept is_trace_sink = true; + +namespace events +{ +PHANTOM_COROUTINES_MODULE_EXPORT +template< + typename Awaiter +> concept is_traced_promise_initial_suspend_awaiter = std::remove_cvref_t::is_traced_promise_initial_suspend_awaiter; + +PHANTOM_COROUTINES_MODULE_EXPORT +template< + typename Awaiter +> concept is_traced_promise_final_suspend_awaiter = std::remove_cvref_t::is_traced_promise_final_suspend_awaiter; + +PHANTOM_COROUTINES_MODULE_EXPORT +template< + typename Awaiter +> concept is_traced_promise_co_yield_awaiter = std::remove_cvref_t::is_traced_promise_co_yield_awaiter; + +PHANTOM_COROUTINES_MODULE_EXPORT +template< + typename Awaiter +> concept is_traced_promise_co_await_awaiter = std::remove_cvref_t::is_traced_promise_co_await_awaiter; + +PHANTOM_COROUTINES_MODULE_EXPORT +template< + typename TValue +> +struct value +{ + using value_type = TValue; + const value_type& Value; + + friend auto operator<=>(const value&, const value&) = default; +}; + +PHANTOM_COROUTINES_MODULE_EXPORT +template< + typename ... Args +> +using arguments = value>; + +PHANTOM_COROUTINES_MODULE_EXPORT +constexpr auto make_arguments( + const auto&... args +) +{ + return value{ std::tie(std::as_const(args)...) }; +} + +PHANTOM_COROUTINES_MODULE_EXPORT +template< + typename Label +> +struct event_type +{ + event_type() = default; + event_type( + Label + ) + { + } + + using label_type = Label; + + template< + typename Label1, + typename Label2 + > + friend consteval bool operator==( + const event_type&, + const event_type&); + + template< + typename Label1, + typename Label2 + > + friend consteval bool operator!=( + const event_type&, + const event_type&); +}; + +PHANTOM_COROUTINES_MODULE_EXPORT +template< + typename Label1, + typename Label2 +> +consteval bool operator==( + const event_type&, + const event_type&) +{ + return std::same_as; +} + +PHANTOM_COROUTINES_MODULE_EXPORT +template< + typename Label1, + typename Label2 +> +consteval bool operator!=( + const event_type&, + const event_type&) +{ + return !std::same_as; +} + +PHANTOM_COROUTINES_MODULE_EXPORT +template< + typename TEventType, + typename TEventData, + template typename ... Accessors +> +struct event : + Accessors>... +{ + using event_type = event_type; + using event_data_type = TEventData; + + std::source_location SourceLocation; + event_type EventType; + event_data_type EventData; +}; + +PHANTOM_COROUTINES_MODULE_EXPORT +struct empty_value +{ + using value_type = void; + static constexpr void* Value = nullptr; + + friend auto operator<=>(const empty_value&, const empty_value&) = default; +}; + +PHANTOM_COROUTINES_MODULE_EXPORT +struct empty_arguments_value +{ + using value_type = std::tuple<>; + static constexpr value_type Value = {}; + + friend auto operator<=>(const empty_arguments_value&, const empty_arguments_value&) = default; +}; + +PHANTOM_COROUTINES_MODULE_EXPORT +constexpr empty_value empty{}; +PHANTOM_COROUTINES_MODULE_EXPORT +constexpr empty_arguments_value empty_arguments{ }; + +PHANTOM_COROUTINES_MODULE_EXPORT +struct no_awaiter_type +{ + static constexpr bool is_traced_promise_initial_suspend_awaiter = false; + static constexpr bool is_traced_promise_final_suspend_awaiter = false; + static constexpr bool is_traced_promise_co_yield_awaiter = false; + static constexpr bool is_traced_promise_co_await_awaiter = false; +}; + +PHANTOM_COROUTINES_MODULE_EXPORT +struct initial_suspend_awaiter_type +{ + static constexpr bool is_traced_promise_initial_suspend_awaiter = true; + static constexpr bool is_traced_promise_final_suspend_awaiter = false; + static constexpr bool is_traced_promise_co_yield_awaiter = false; + static constexpr bool is_traced_promise_co_await_awaiter = false; +}; + +PHANTOM_COROUTINES_MODULE_EXPORT +struct final_suspend_awaiter_type +{ + static constexpr bool is_traced_promise_initial_suspend_awaiter = false; + static constexpr bool is_traced_promise_final_suspend_awaiter = true; + static constexpr bool is_traced_promise_co_yield_awaiter = false; + static constexpr bool is_traced_promise_co_await_awaiter = false; +}; + +PHANTOM_COROUTINES_MODULE_EXPORT +struct co_yield_awaiter_type +{ + static constexpr bool is_traced_promise_initial_suspend_awaiter = false; + static constexpr bool is_traced_promise_final_suspend_awaiter = false; + static constexpr bool is_traced_promise_co_yield_awaiter = true; + static constexpr bool is_traced_promise_co_await_awaiter = false; +}; + +PHANTOM_COROUTINES_MODULE_EXPORT +struct co_await_awaiter_type +{ + static constexpr bool is_traced_promise_initial_suspend_awaiter = false; + static constexpr bool is_traced_promise_final_suspend_awaiter = false; + static constexpr bool is_traced_promise_co_yield_awaiter = false; + static constexpr bool is_traced_promise_co_await_awaiter = true; +}; + +template< + typename T +> concept is_empty = +std::same_as +|| +std::same_as +|| +std::same_as +|| +std::same_as; + +template< + typename T +> concept is_not_empty = !is_empty; + +template< + typename Event +> +struct PromiseAccessor +{ + static constexpr bool has_promise = requires(Event self) + { + { self.Data.Promise } -> is_not_empty; + }; + + auto* promise(this auto& self) + { + if constexpr (has_promise) + { + return self.Data.Promise; + } + else + { + return ∅ + } + } + + static constexpr bool has_traced_promise = requires(Event self) + { + { self.Data.TracedPromise } -> is_not_empty; + }; + + auto* traced_promise(this auto& self) + { + if constexpr (has_traced_promise) + { + return self.Data.TracedPromise; + } + else + { + return ∅ + } + } + + static constexpr bool has_promise_creation_arguments = requires(Event self) + { + { self.Data.PromiseCreationArguments } -> is_not_empty; + }; + + auto* promise_creation_arguments( + this auto& self + ) + { + if constexpr (has_promise_creation_arguments) + { + return self.Data.PromiseCreationArguments; + } + else + { + return &empty_arguments; + } + } +}; + +template< + typename Event +> +struct AwaiterAccessor +{ + static constexpr bool has_awaiter = requires(Event self) + { + { self.Data.Awaiter } -> is_not_empty; + }; + + auto* awaiter(this auto& self) + { + if constexpr (has_awaiter) + { + return self.Data.Awaiter; + } + else + { + return ∅ + } + } + + static constexpr bool has_awaiter_type = requires(Event self) + { + { self.Data.AwaiterType } -> is_not_empty; + }; + + auto* awaiter_type(this auto& self) + { + if constexpr (has_awaiter_type) + { + return self.Data.AwaiterType; + } + else + { + return ∅ + } + } + + static constexpr bool has_traced_awaiter = requires(Event self) + { + { self.Data.TracedAwaiter } -> is_not_empty; + }; + + auto* traced_awaiter(this auto& self) + { + if constexpr (has_awaiter) + { + return self.Data.TracedAwaiter; + } + else + { + return ∅ + } + } + +}; + +template< + typename Event +> +struct MethodArgumentsAccessor +{ + static constexpr bool has_method_arguments = requires(Event self) + { + { self.Data.MethodArguments } -> is_not_empty; + }; + + auto* method_arguments(this auto& self) + { + if constexpr (has_method_arguments) + { + return self.Data.MethodArguments; + } + else + { + return &empty_arguments; + } + } +}; + +template< + typename Event +> +struct MethodResultAccessor +{ + static constexpr bool has_method_result = requires(Event self) + { + { self.Data.MethodResult } -> is_not_empty; + }; + + auto* method_result(this auto& self) + { + if constexpr (has_method_result) + { + return self.Data.MethodResult; + } + else + { + return ∅ + } + } +}; + +template< + typename Event +> +struct MethodExceptionAccessor +{ + static constexpr bool has_method_exception = requires(Event self) + { + { self.Data.MethodException } -> is_not_empty; + }; + + auto* method_exception(this auto& self) + { + if constexpr (has_method_exception) + { + return self.Data.MethodException; + } + else + { + return ∅ + } + } +}; + +template< + typename Event +> +struct UnhandledExceptionAccessor +{ + static constexpr bool has_unhandled_exception = requires(Event self) + { + { self.Data.UnhandledException } -> is_not_empty; + }; + + auto* unhandled_exception(this auto& self) + { + if constexpr (has_unhandled_exception) + { + return self.Data.UnhandledException; + } + else + { + return ∅ + } + } +}; + +template< + typename Event +> +struct YieldValueAccessor +{ + static constexpr bool has_yield_value = requires(Event self) + { + { self.Data.YieldValue } -> is_not_empty; + }; + + auto* yield_value(this auto& self) + { + if constexpr (has_yield_value) + { + return self.Data.YieldValue; + } + else + { + return ∅ + } + } +}; + +template< + typename Event +> +struct ReturnValueAccessor +{ + static constexpr bool has_return_value = requires(Event self) + { + { self.Data.ReturnValue } -> is_not_empty; + }; + + auto* return_value(this auto& self) + { + if constexpr (has_return_value) + { + return self.Data.ReturnValue; + } + else + { + return ∅ + } + } +}; + +PHANTOM_COROUTINES_MODULE_EXPORT +struct create_promise {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct destroy_promise {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct await_ready_begin {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct await_ready_result {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct await_ready_exception {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct await_suspend_begin {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct await_suspend_result {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct await_suspend_exception {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct await_resume_begin {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct await_resume_result {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct await_resume_exception {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct await_transform_begin {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct await_transform_result {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct await_transform_exception {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct unhandled_exception_begin {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct unhandled_exception_result {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct unhandled_exception_exception {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct get_return_object_begin {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct get_return_object_result {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct get_return_object_exception {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct yield_value_begin {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct yield_value_result {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct yield_value_exception {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct return_value_begin {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct return_value_result {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct return_value_exception {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct return_void_begin {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct return_void_result {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct return_void_exception {}; + +PHANTOM_COROUTINES_MODULE_EXPORT +template< + typename Event, + typename EventType +> concept is_event_type = std::same_as; + +PHANTOM_COROUTINES_MODULE_EXPORT +template< + typename TPromise = empty_value, + typename TTracedPromise = empty_value, + typename TPromiseCreationArguments = empty_arguments_value, + typename TAwaiter = empty_value, + typename TTracedAwaiter = empty_value, + typename TAwaiterType = no_awaiter_type, + typename TMethodArguments = empty_arguments_value, + typename TMethodResult = empty_value, + typename TMethodException = empty_value, + typename TUnhandledException = empty_value, + typename TYieldValue = empty_value, + typename TReturnValue = empty_value +> +struct event_data +{ + PHANTOM_COROUTINES_NO_UNIQUE_ADDRESS TPromise Promise = empty; + PHANTOM_COROUTINES_NO_UNIQUE_ADDRESS TTracedPromise TracedPromise = empty; + PHANTOM_COROUTINES_NO_UNIQUE_ADDRESS TPromiseCreationArguments PromiseCreationArguments = empty_arguments; + PHANTOM_COROUTINES_NO_UNIQUE_ADDRESS TAwaiter Awaiter = empty; + PHANTOM_COROUTINES_NO_UNIQUE_ADDRESS TTracedAwaiter TracedAwaiter = empty; + PHANTOM_COROUTINES_NO_UNIQUE_ADDRESS TAwaiterType AwaiterType = no_awaiter_type{}; + PHANTOM_COROUTINES_NO_UNIQUE_ADDRESS TMethodArguments MethodArguments = empty_arguments; + PHANTOM_COROUTINES_NO_UNIQUE_ADDRESS TMethodResult MethodResult = empty; + PHANTOM_COROUTINES_NO_UNIQUE_ADDRESS TMethodException MethodException = empty; + PHANTOM_COROUTINES_NO_UNIQUE_ADDRESS TUnhandledException UnhandledException = empty; + PHANTOM_COROUTINES_NO_UNIQUE_ADDRESS TYieldValue YieldValue = empty; + PHANTOM_COROUTINES_NO_UNIQUE_ADDRESS TReturnValue ReturnValue = empty; + + friend auto operator<=>(const event_data&, const event_data&) = default; + + static constexpr auto combine( + const auto& lhs, + const auto& rhs + ) + { + if constexpr (is_empty>) + { + return lhs; + } + else + { + return rhs; + } + } + + auto with( + const auto& other + ) const + { + return events::event_data + { + .Promise = combine( + this->Promise, + other.Promise), + .TracedPromise = combine( + this->TracedPromise, + other.TracedPromise), + .PromiseCreationArguments = combine( + this->PromiseCreationArguments, + other.PromiseCreationArguments), + .Awaiter = combine( + this->Awaiter, + other.Awaiter), + .TracedAwaiter = combine( + this->TracedAwaiter, + other.TracedAwaiter), + .AwaiterType = combine( + this->AwaiterType, + other.AwaiterType), + .MethodArguments = combine( + this->MethodArguments, + other.MethodArguments), + .MethodResult = combine( + this->MethodResult, + other.MethodResult), + .MethodException = combine( + this->MethodException, + other.MethodException), + .UnhandledException = combine( + this->UnhandledException, + other.UnhandledException), + .YieldValue = combine( + this->YieldValue, + other.YieldValue), + .ReturnValue = combine( + this->ReturnValue, + other.ReturnValue), + }; + } +}; + +template< + typename EventType, + typename EventData +> +using basic_event_type = event< + EventType, + EventData, + PromiseAccessor, + AwaiterAccessor, + MethodArgumentsAccessor, + MethodResultAccessor, + MethodExceptionAccessor, + UnhandledExceptionAccessor, + YieldValueAccessor, + ReturnValueAccessor +>; + +PHANTOM_COROUTINES_MODULE_EXPORT +template< + typename EventType, + typename EventData +> +auto basic_event( + const std::source_location& sourceLocation, + const EventType& eventType, + const EventData& eventData +) +{ + return basic_event_type< + EventType, + EventData + > + { + .SourceLocation = sourceLocation, + .EventType = eventType, + .EventData = eventData, + }; +} + +// namespace events +} + +namespace detail +{ + +template< + is_trace_sink TraceSink +> +struct trace_sink_accessor +{ + TraceSink m_traceSink; + + auto& trace_sink() + { + return m_traceSink; + } +}; + +template< + typename BeginEventType, + typename ResultEventType, + typename ExceptionEventType +> +struct method_events_group +{ + method_events_group( + BeginEventType, + ResultEventType, + ExceptionEventType) + { } + + using begin_event_type = BeginEventType; + using result_event_type = ResultEventType; + using exception_event_type = ExceptionEventType; + + BeginEventType BeginEvent; + ResultEventType ResultEvent; + ExceptionEventType ExceptionEvent; +}; + +} // namespace detail + +auto call_traced_method( + const auto& traceSink, + auto events, + std::source_location sourceLocation, + const auto& baseEventData, + std::invocable<> auto call +) -> std::invoke_result_t +{ + using result_type = std::invoke_result_t; + constexpr bool is_move_constructible = std::is_move_constructible_v; + constexpr bool is_copy_constructible = std::is_copy_constructible_v; + constexpr bool can_trace_return_value = + ( + is_move_constructible + || + is_copy_constructible + ); + + try + { + traceSink( + events::basic_event( + sourceLocation, + events.BeginEvent, + baseEventData)); + + if (std::same_as) + { + call(); + traceSink( + events::basic_event( + sourceLocation, + events.ResultEvent, + baseEventData)); + } + else if constexpr (!can_trace_return_value) + { + auto uncaughtExceptions = std::uncaught_exceptions(); + + Coroutines::detail::scope_guard guard = [&]() + { + if (std::uncaught_exceptions() != uncaughtExceptions) + { + // If the number of uncaught exceptions has changed in between + // the call being performed and this guard being deactivated, + // it means an exception was thrown by the call, + // and we should not report a successful result. + return; + } + + traceSink( + events::basic_event( + sourceLocation, + events.ExceptionEvent, + baseEventData)); + }; + + call(); + } + else + { + decltype(auto) result = call(); + traceSink( + events::basic_event( + sourceLocation, + events.ResultEvent, + baseEventData.with( + events::event_data + { + .MethodResult = events::value{ result } + }))); + + if constexpr (is_move_constructible) + { + return std::forward(result); + } + else + { + return result; + } + } + } + catch (...) + { + auto exception = std::current_exception(); + traceSink( + events::basic_event( + sourceLocation, + events.ExceptionEvent, + baseEventData.with( + events::event_data + { + .MethodException = events::value{ exception } + }))); + throw; + } +} + +PHANTOM_COROUTINES_MODULE_EXPORT +template< + is_trace_sink TraceSink, + typename Awaitable, + typename AwaiterType, + typename BaseEventData +> +struct traced_awaiter : + detail::trace_sink_accessor, + awaiter_wrapper, + AwaiterType +{ + using awaiter_wrapper = awaiter_wrapper; + using trace_sink_accessor = traced_awaiter::trace_sink_accessor; + using traced_awaiter::trace_sink_accessor::trace_sink; + using wrapped_awaiter_type = std::remove_reference_t; + + std::source_location m_sourceLocation; + BaseEventData m_baseEventData; + + auto call_awaiter( + this auto& awaiter, + auto events, + const auto& baseEventData, + std::invocable<> auto call + + ) -> std::invoke_result_t + { + return call_traced_method( + awaiter.traced_awaiter::trace_sink(), + events, + awaiter.m_sourceLocation, + baseEventData.with( + events::event_data + { + .Awaiter = static_cast(std::addressof(awaiter.awaiter())), + .TracedAwaiter = std::addressof(awaiter), + .AwaiterType = AwaiterType{}, + }), + call); + } + + decltype(auto) await_ready( + this auto& self + ) noexcept(noexcept(self.awaiter_wrapper::await_ready())) + { + return self.traced_awaiter::call_awaiter( + detail::method_events_group + { + events::await_ready_begin{}, + events::await_ready_result{}, + events::await_ready_exception{}, + }, + self.get_event_data( + events::empty_arguments), + [&]() -> decltype(auto) + { + return self.awaiter_wrapper::await_ready(); + } + ); + } + + template< + typename Arg + > + decltype(auto) await_suspend( + this auto& self, + Arg&& arg + ) noexcept(noexcept(self.awaiter_wrapper::await_suspend(std::forward(arg)))) + { + return self.traced_awaiter::call_awaiter( + detail::method_events_group + { + events::await_suspend_begin{}, + events::await_suspend_result{}, + events::await_suspend_exception{}, + }, + self.get_event_data( + make_arguments(arg)), + [&]() -> decltype(auto) + { + return self.awaiter_wrapper::await_suspend( + std::forward(arg)); + } + ); + } + + decltype(auto) await_resume( + this auto& self + ) noexcept(noexcept(self.awaiter_wrapper::await_resume())) + { + return self.traced_awaiter::call_awaiter( + detail::method_events_group + { + events::await_resume_begin{}, + events::await_resume_result{}, + events::await_resume_exception{}, + }, + self.get_event_data( + events::empty_arguments), + [&]() -> decltype(auto) + { + return self.awaiter_wrapper::await_resume(); + } + ); + } + + traced_awaiter( + std::source_location sourceLocation, + std::invocable auto awaiterFunction, + AwaiterType, + trace_sink_accessor traceSinkAccessor, + BaseEventData baseEventData + ) + : + trace_sink_accessor{ traceSinkAccessor }, + m_sourceLocation{ sourceLocation }, + awaiter_wrapper{ std::move(awaiterFunction) }, + m_baseEventData{ std::move(baseEventData) } + { + } + + template< + typename ... Args + > + auto get_event_data( + this auto& awaiter, + const auto& methodArguments + ) + { + return awaiter.m_baseEventData.with( + events::event_data + { + .Awaiter = &awaiter, + .MethodArguments = methodArguments, + }); + } +}; + +template< + is_trace_sink TraceSink, + std::invocable<> AwaiterFunction, + typename AwaiterType, + typename BaseEventData +> +traced_awaiter( + std::source_location, + AwaiterFunction, + AwaiterType, + detail::trace_sink_accessor, + BaseEventData +) -> traced_awaiter< + TraceSink, + std::invoke_result_t, + AwaiterType, + BaseEventData +>; + + +namespace detail +{ + +// This class stored the trace sink and produces non-result-specific tracing events. +PHANTOM_COROUTINES_MODULE_EXPORT +template< + is_trace_sink TraceSink +> +class traced_promise_trace_sink_storage +{ + template< + typename Declaration + > + struct traced_promise_trace_sink_accessor; + +public: + using trace_sink_type = TraceSink; + +public: + trace_sink_type m_traceSink; + + template< + typename ... Args + > + requires + is_constructible_from_promise_arguments + traced_promise_trace_sink_storage( + Args&& ... args + ) : + m_traceSink(std::forward(args)...) + { + } + + // Allow default construction of the trace sink when arguments are provided. + template< + typename ... Args + > + requires + is_default_constructible_from_promise_arguments + traced_promise_trace_sink_storage( + Args&& ... args + ) : + m_traceSink() + { + } + + auto get_event_data( + this auto& promise + ) + { + return events::event_data + { + .Promise = &promise.get_underlying_promise(), + .TracedPromise = &promise, + }; + } + + decltype(auto) call_promise_method( + this auto& promise, + auto events, + std::source_location sourceLocation, + const auto& baseEventData, + std::invocable auto call + ) + { + return call_traced_method( + promise.traced_promise_trace_sink_storage::m_traceSink, + events, + sourceLocation, + baseEventData.with( + promise.get_event_data()), + call); + } +}; + +// This class embodies constructors for traced promises. +template< + is_trace_sink TraceSink, + typename BasePromise +> +class traced_promise_construction + : + public traced_promise_trace_sink_storage, + public derived_promise +{ +public: + template< + typename ... Args + > + requires + std::constructible_from, Args&&...> + && + std::constructible_from, Args&&...> + traced_promise_construction( + Args&& ... args + ) + : + traced_promise_construction::traced_promise_trace_sink_storage(std::forward(args)...), + traced_promise_construction::derived_promise(std::forward(args)... ) + { + } +}; + +// This class provides an implementation of yield_value for a promise +// if it has a yield_value implementation. +// The main template does not provide yield_value. +template< + is_trace_sink TraceSink, + typename BasePromise +> +class traced_promise_yield_value + : + public traced_promise_construction +{ +public: + using traced_promise_yield_value::traced_promise_construction::traced_promise_construction; +}; + +// The specialization for promises that have yield_value. +template< + is_trace_sink TraceSink, + has_yield_value BasePromise +> +class traced_promise_yield_value< + TraceSink, + BasePromise +> + : + public traced_promise_construction +{ +public: + using traced_promise_yield_value::traced_promise_construction::traced_promise_construction; + + // Trace an delegate the base class's yield_value implementation. + template< + typename TPromise + > + decltype(auto) yield_value( + this TPromise&& promise, + auto&& value, + std::source_location sourceLocation = std::source_location::current() + ) + { + return promise.traced_promise_yield_value::call_promise_method( + promise, + detail::method_events_group + { + events::yield_value_begin{}, + events::yield_value_result{}, + events::yield_value_exception{}, + }, + sourceLocation, + events::event_data + { + .MethodArguments = events::make_arguments(value), + }, + [&]() + { + return std::forward(promise).traced_promise_yield_value::yield_value( + std::forward(value)); + } + ); + } +}; + +template< + is_trace_sink TraceSink, + typename BasePromise +> +class traced_promise_return_value_or_void + : + public traced_promise_yield_value +{ + using traced_promise_return_value_or_void::traced_promise_yield_value::traced_promise_yield_value; + +public: + using traced_promise_return_value_or_void::traced_promise_yield_value::call_promise_method; + + template< + typename TPromise, + typename Value + > + void return_value( + this TPromise&& promise, + Value&& value, + std::source_location sourceLocation = std::source_location::current() + ) + { + std::forward(promise).call_promise_method( + detail::method_events_group + { + events::return_value_begin{}, + events::return_value_result{}, + events::return_value_exception{}, + }, + sourceLocation, + events::event_data + { + .MethodArguments = events::make_arguments(value), + }, + [&]() + { + std::forward(promise).traced_promise_return_value_or_void::traced_promise_yield_value::return_value( + std::forward(value)); + } + ); + } +}; + +template< + is_trace_sink TraceSink, + has_return_void BasePromise +> +class traced_promise_return_value_or_void< + TraceSink, + BasePromise +> + : + public traced_promise_yield_value +{ + using traced_promise_return_value_or_void::traced_promise_yield_value::traced_promise_yield_value; + +public: + using traced_promise_return_value_or_void::traced_promise_yield_value::call_promise_method; + + template< + typename TPromise + > + void return_void( + this TPromise&& promise, + std::source_location sourceLocation = std::source_location::current() + ) + { + std::forward(promise).call_promise_method( + detail::method_events_group + { + events::return_void_begin{}, + events::return_void_result{}, + events::return_void_exception{}, + }, + sourceLocation, + events::event_data + { + }, + [&]() + { + std::forward(promise).traced_promise_return_value_or_void::traced_promise_yield_value::return_void(); + } + ); + } +}; + +template< + is_trace_sink TraceSink, + typename BasePromise +> +using traced_promise_base = traced_promise_return_value_or_void; + +} // namespace detail + +// Use suppress_trace to suppress tracing of an awaitable. +// Example: +// co_await suppress_trace{ m_event.Wait() }; +PHANTOM_COROUTINES_MODULE_EXPORT +template< + typename Awaiter +> +struct suppress_trace +{ + Awaiter&& value; +}; + +// Use trace to send a value to the trace sink. +// Example: +// co_await trace{ my_trace_event_information{} }; +PHANTOM_COROUTINES_MODULE_EXPORT +struct trace +{ +}; + +PHANTOM_COROUTINES_MODULE_EXPORT +template< + is_trace_sink TraceSink, + is_extensible_promise BasePromise +> +class traced_promise + : + public detail::traced_promise_base +{ +public: + using traced_promise_base = detail::traced_promise_base; + using traced_promise_base::call_promise_method; + using traced_promise_base::get_event_data; + using traced_promise_base::m_traceSink; + + template< + typename Declaration + > + struct traced_promise_trace_sink_accessor; + +public: + template< + typename ... Args + > + traced_promise( + Args&& ... args + ) + : + traced_promise_base{ std::forward(args)... } + { + m_traceSink( + events::basic_event( + std::source_location::current(), + events::create_promise{}, + events::event_data + { + .Promise = &this->get_underlying_promise(), + .TracedPromise = this, + .PromiseCreationArguments = events::make_arguments(args...), + })); + } + + ~traced_promise() + { + m_traceSink( + basic_event( + std::source_location::current(), + events::destroy_promise{}, + get_event_data() + )); + } + + template< + typename TPromise + > + auto initial_suspend( + this TPromise& promise, + std::source_location sourceLocation = std::source_location::current() + ) + { + return traced_awaiter + { + sourceLocation, + [&]() { return promise.traced_promise_base::initial_suspend(); }, + events::initial_suspend_awaiter_type{}, + detail::trace_sink_accessor + { + promise.traced_promise::m_traceSink + }, + promise.get_event_data() + }; + } + + template< + typename TPromise + > + auto final_suspend( + this TPromise& promise, + std::source_location sourceLocation = std::source_location::current() + ) noexcept + { + return traced_awaiter + { + sourceLocation, + [&]() { return promise.traced_promise_base::final_suspend(); }, + events::final_suspend_awaiter_type{}, + detail::trace_sink_accessor + { + promise.traced_promise::m_traceSink + }, + promise.get_event_data() + }; + } + + template< + typename TPromise + > + void unhandled_exception( + this TPromise& promise, + std::source_location sourceLocation = std::source_location::current() + ) + { + promise.call_promise_method( + detail::method_events_group + { + events::unhandled_exception_begin{}, + events::unhandled_exception_result{}, + events::unhandled_exception_exception{}, + }, + sourceLocation, + events::event_data{}, + [&]() + { + return promise.traced_promise_base::unhandled_exception(); + } + ); + } + + auto await_transform( + this auto& promise, + auto&& awaiter, + std::source_location sourceLocation = std::source_location::current() + ) + { + return traced_awaiter + { + sourceLocation, + [&]() -> decltype(auto) + { + return promise.traced_promise_base::await_transform( + std::forward(awaiter)); + }, + events::co_await_awaiter_type{}, + detail::trace_sink_accessor + { + promise.traced_promise::m_traceSink + }, + promise.get_event_data() + }; + } + + template< + typename Awaiter + > decltype(auto) await_transform( + this auto& promise, + const suppress_trace& noTraceAwaiter + ) + { + return promise.traced_promise_base::await_transform( + noTraceAwaiter.value + ); + } + + suspend_never await_transform( + this auto& promise, + std::derived_from auto& traceEvent + ) + { + promise.m_traceSink( + traceEvent); + return suspend_never{}; + } + + template< + typename This + > + auto& get_underlying_promise( + this This& self) + { + if constexpr (std::is_const_v) + { + return static_cast(self); + } + else + { + return static_cast(self); + } + } +}; + +PHANTOM_COROUTINES_MODULE_EXPORT +namespace filters +{ + +struct filter; + +template< + typename T +> +concept is_filter = std::derived_from; + +template< + is_filter Left, + is_filter Right +> +struct and_filter; + +template< + is_filter Left, + is_filter Right +> +struct or_filter; + +template< + is_filter Filter +> +struct not_filter; + +struct filter +{ + constexpr std::false_type operator()(const auto&) const noexcept { + return {}; + } + + template< + is_filter Left, + is_filter Right + > + friend constexpr auto operator&&( + Left left, + Right right + ) noexcept + { + return and_filter{ left, right }; + } + + template< + is_filter Left, + is_filter Right + > + friend constexpr auto operator||( + Left left, + Right right + ) noexcept + { + return or_filter{ left, right }; + } + + template< + is_filter Filter + > + friend constexpr auto operator!( + Filter filter + ) noexcept + { + return not_filter{ filter }; + } +}; + +template< + is_filter Left, + is_filter Right +> +struct and_filter : filter +{ + Left left; + Right right; + + constexpr bool operator()( + const auto& event + ) const noexcept + { + using leftType = decltype(left(event)); + using rightType = decltype(right(event)); + + if constexpr ( + std::same_as + || + std::same_as) + { + return std::false_type{}; + } + else if constexpr ( + std::same_as && + std::same_as) + { + return std::true_type{}; + } + else + { + return left(event) && right(event); + } + } +}; + +template< + is_filter Left, + is_filter Right +> +struct or_filter : filter +{ + Left left; + Right right; + + constexpr bool operator()( + const auto& event + ) const noexcept + { + using leftType = decltype(left(event)); + using rightType = decltype(right(event)); + + if constexpr ( + std::same_as + || + std::same_as) + { + return std::true_type{}; + } + else if constexpr ( + std::same_as && + std::same_as) + { + return std::false_type{}; + } + else + { + return left(event) && right(event); + } + } +}; + +template< + is_filter Filter +> +struct not_filter : filter +{ + Filter filter; + + constexpr auto operator()( + const auto& event) + { + using type = decltype(filter(event)); + if constexpr (std::same_as) + { + return std::false_type{}; + } + else if constexpr (std::same_as) + { + return std::true_type{}; + } + else + { + return !filter(event); + } + }; +}; + +struct any_event_fn : filter +{ + constexpr std::true_type operator()(const auto&) const noexcept { + return {}; + } +}; +constexpr any_event_fn any_event{}; + +template< + typename ... EventType +> +struct event_type_fn : filter +{ + using filter::operator(); + constexpr std::true_type operator()( + const auto& event + ) const noexcept + requires ( + events::is_event_type, EventType> || ... + ) + { + return {}; + } +}; + +template< + typename ... EventType +> +event_type_fn event_type; + +template< + typename AwaiterType +> +struct awaiter_type_fn : filter +{ + using filter::operator(); + constexpr std::true_type operator()( + const auto& event + ) const noexcept + requires std::same_as< + const decltype(event.EventData.AwaiterType)&, + const AwaiterType& + > + { + return {}; + } +}; + +template< + typename AwaiterType +> +constexpr awaiter_type_fn awaiter_type; + +template< + typename Event +> +struct check_constexpr_fn +{ + template< + is_filter Filter + > constexpr auto operator()( + Filter filter + ) const noexcept + { + return decltype(filter(std::declval())){}; + } +}; + +template< + typename Event +> constexpr check_constexpr_fn check_constexpr{}; + +constexpr auto constant_filtered_trace_sink( + auto filter, + auto&& traceSink +) +{ + return [traceSink = std::forward(traceSink), filter](const auto& event) + { + if constexpr (filter(event)) + { + traceSink(event); + } + }; +} + +constexpr auto runtime_filtered_trace_sink( + auto filter, + auto&& traceSink +) +{ + return [traceSink = std::forward(traceSink), filter](const auto& event) + { + if (filter(event)) + { + traceSink(event); + } + }; +} + +// namespace filters +} + +// namespace tracing +} + + +// namespace Phantom::Coroutines +} + +#endif From fba5594fd1e011702a42c70d24e1955de09e341a Mon Sep 17 00:00:00 2001 From: Joshua Rowe Date: Tue, 7 Apr 2026 10:07:21 -0700 Subject: [PATCH 37/46] Fix tracing / functional build errors --- Phantom.Coroutines.Modules/CMakeLists.txt | 3 +- .../Phantom.Coroutines.ixx | 1 + Phantom.Coroutines.Modules/functional.ixx | 5 + Phantom.Coroutines.Modules/tracing.ixx | 1 + Phantom.Coroutines.Test/tracing_test.cpp | 2 +- .../include/Phantom.Coroutines/functional.h | 58 + .../include/Phantom.Coroutines/tracing.h | 3710 +++++++++-------- 7 files changed, 2051 insertions(+), 1729 deletions(-) create mode 100644 Phantom.Coroutines.Modules/functional.ixx create mode 100644 Phantom.Coroutines/include/Phantom.Coroutines/functional.h diff --git a/Phantom.Coroutines.Modules/CMakeLists.txt b/Phantom.Coroutines.Modules/CMakeLists.txt index 7842f24..27f5de4 100644 --- a/Phantom.Coroutines.Modules/CMakeLists.txt +++ b/Phantom.Coroutines.Modules/CMakeLists.txt @@ -51,6 +51,7 @@ target_sources( "extensible_promise.ixx" "fibonacci_heap.ixx" "final_suspend_transfer.ixx" + "functional.ixx" "function_traits.ixx" "generator.ixx" "immovable_object.ixx" @@ -85,7 +86,7 @@ target_sources( "type_traits.ixx" "value_awaiter.ixx" "variant_result_storage.ixx" -) + ) target_link_libraries( Phantom.Coroutines.Modules diff --git a/Phantom.Coroutines.Modules/Phantom.Coroutines.ixx b/Phantom.Coroutines.Modules/Phantom.Coroutines.ixx index 0400313..d964aa5 100644 --- a/Phantom.Coroutines.Modules/Phantom.Coroutines.ixx +++ b/Phantom.Coroutines.Modules/Phantom.Coroutines.ixx @@ -32,6 +32,7 @@ export import Phantom.Coroutines.extensible_promise; export import Phantom.Coroutines.fibonacci_heap; export import Phantom.Coroutines.final_suspend_transfer; export import Phantom.Coroutines.function_traits; +export import Phantom.Coroutines.functional; export import Phantom.Coroutines.generator; export import Phantom.Coroutines.immovable_object; export import Phantom.Coroutines.inline_scheduler; diff --git a/Phantom.Coroutines.Modules/functional.ixx b/Phantom.Coroutines.Modules/functional.ixx new file mode 100644 index 0000000..d1d587e --- /dev/null +++ b/Phantom.Coroutines.Modules/functional.ixx @@ -0,0 +1,5 @@ +module; +#include "Phantom.Coroutines/detail/config_macros.h" +#include +export module Phantom.Coroutines.functional; +#include "Phantom.Coroutines/functional.h" diff --git a/Phantom.Coroutines.Modules/tracing.ixx b/Phantom.Coroutines.Modules/tracing.ixx index 2d08a1e..f9a779f 100644 --- a/Phantom.Coroutines.Modules/tracing.ixx +++ b/Phantom.Coroutines.Modules/tracing.ixx @@ -11,6 +11,7 @@ module; export module Phantom.Coroutines.tracing; import Phantom.Coroutines.awaiter_wrapper; import Phantom.Coroutines.coroutine; +import Phantom.Coroutines.functional; import Phantom.Coroutines.extensible_promise; import Phantom.Coroutines.scope_guard; import Phantom.Coroutines.type_traits; diff --git a/Phantom.Coroutines.Test/tracing_test.cpp b/Phantom.Coroutines.Test/tracing_test.cpp index 01b0fec..05a2010 100644 --- a/Phantom.Coroutines.Test/tracing_test.cpp +++ b/Phantom.Coroutines.Test/tracing_test.cpp @@ -1445,7 +1445,7 @@ ASYNC_TEST_F(tracing_tests, traces_await_resume_exception) .Awaiter = expectedAwaiter, .TracedAwaiter = expectedTracedAwaiter, .AwaiterType = events::co_await_awaiter_type{}, - .MethodResult = events::value { false }, + .MethodResult = events::value { true }, }; auto expectedEvent = basic_event( diff --git a/Phantom.Coroutines/include/Phantom.Coroutines/functional.h b/Phantom.Coroutines/include/Phantom.Coroutines/functional.h new file mode 100644 index 0000000..17d481d --- /dev/null +++ b/Phantom.Coroutines/include/Phantom.Coroutines/functional.h @@ -0,0 +1,58 @@ +#ifndef PHANTOM_COROUTINES_INCLUDE_FUNCTIONAL_H +#define PHANTOM_COROUTINES_INCLUDE_FUNCTIONAL_H +#ifndef PHANTOM_COROUTINES_COMPILING_MODULES +#include "detail/config_macros.h" +#include +#endif + +static_assert(PHANTOM_COROUTINES_IS_CONFIGURED); +PHANTOM_COROUTINES_ASSERT_IS_MODULE; + +namespace Phantom::Coroutines +{ + +namespace detail +{ +template< + typename Function +> +struct composed_function_wrapper +{ + Function function; + + decltype(auto) operator()(auto&& ... args) + { + return std::invoke( + function, + std::forward(args)...); + } +}; +} + +PHANTOM_COROUTINES_MODULE_EXPORT +template< + typename ... Functions +> +struct composed_function + : + detail::composed_function_wrapper... +{ + composed_function( + auto&& ... functions + ) : + detail::composed_function_wrapper{ std::forward(functions) }... + {} + + using detail::composed_function_wrapper::operator()...; +}; + +PHANTOM_COROUTINES_MODULE_EXPORT +template< + typename ... Functions +> +composed_function( + Functions&& ... functions +) -> composed_function...>; + +} +#endif // PHANTOM_COROUTINES_INCLUDE_FUNCTIONAL_H diff --git a/Phantom.Coroutines/include/Phantom.Coroutines/tracing.h b/Phantom.Coroutines/include/Phantom.Coroutines/tracing.h index 9b9ea86..68659df 100644 --- a/Phantom.Coroutines/include/Phantom.Coroutines/tracing.h +++ b/Phantom.Coroutines/include/Phantom.Coroutines/tracing.h @@ -1,1727 +1,1983 @@ -#ifndef PHANTOM_COROUTINES_INCLUDE_TRACING_H -#define PHANTOM_COROUTINES_INCLUDE_TRACING_H -#ifndef PHANTOM_COROUTINES_COMPILING_MODULES -#include -#include -#include -#include -#include -#include -#include -#include -#include "detail/config_macros.h" -#include "detail/coroutine.h" -#include "detail/scope_guard.h" -#include "awaiter_wrapper.h" -#include "extensible_promise.h" -#include "type_traits.h" -#endif - -static_assert(PHANTOM_COROUTINES_IS_CONFIGURED); -PHANTOM_COROUTINES_ASSERT_IS_MODULE; - -namespace Phantom::Coroutines -{ - -namespace tracing -{ - -PHANTOM_COROUTINES_MODULE_EXPORT -template< - typename TraceSink -> concept is_trace_sink = true; - -namespace events -{ -PHANTOM_COROUTINES_MODULE_EXPORT -template< - typename Awaiter -> concept is_traced_promise_initial_suspend_awaiter = std::remove_cvref_t::is_traced_promise_initial_suspend_awaiter; - -PHANTOM_COROUTINES_MODULE_EXPORT -template< - typename Awaiter -> concept is_traced_promise_final_suspend_awaiter = std::remove_cvref_t::is_traced_promise_final_suspend_awaiter; - -PHANTOM_COROUTINES_MODULE_EXPORT -template< - typename Awaiter -> concept is_traced_promise_co_yield_awaiter = std::remove_cvref_t::is_traced_promise_co_yield_awaiter; - -PHANTOM_COROUTINES_MODULE_EXPORT -template< - typename Awaiter -> concept is_traced_promise_co_await_awaiter = std::remove_cvref_t::is_traced_promise_co_await_awaiter; - -PHANTOM_COROUTINES_MODULE_EXPORT -template< - typename TValue -> -struct value -{ - using value_type = TValue; - const value_type& Value; - - friend auto operator<=>(const value&, const value&) = default; -}; - -PHANTOM_COROUTINES_MODULE_EXPORT -template< - typename ... Args -> -using arguments = value>; - -PHANTOM_COROUTINES_MODULE_EXPORT -constexpr auto make_arguments( - const auto&... args -) -{ - return value{ std::tie(std::as_const(args)...) }; -} - -PHANTOM_COROUTINES_MODULE_EXPORT -template< - typename Label -> -struct event_type -{ - event_type() = default; - event_type( - Label - ) - { - } - - using label_type = Label; - - template< - typename Label1, - typename Label2 - > - friend consteval bool operator==( - const event_type&, - const event_type&); - - template< - typename Label1, - typename Label2 - > - friend consteval bool operator!=( - const event_type&, - const event_type&); -}; - -PHANTOM_COROUTINES_MODULE_EXPORT -template< - typename Label1, - typename Label2 -> -consteval bool operator==( - const event_type&, - const event_type&) -{ - return std::same_as; -} - -PHANTOM_COROUTINES_MODULE_EXPORT -template< - typename Label1, - typename Label2 -> -consteval bool operator!=( - const event_type&, - const event_type&) -{ - return !std::same_as; -} - -PHANTOM_COROUTINES_MODULE_EXPORT -template< - typename TEventType, - typename TEventData, - template typename ... Accessors -> -struct event : - Accessors>... -{ - using event_type = event_type; - using event_data_type = TEventData; - - std::source_location SourceLocation; - event_type EventType; - event_data_type EventData; -}; - -PHANTOM_COROUTINES_MODULE_EXPORT -struct empty_value -{ - using value_type = void; - static constexpr void* Value = nullptr; - - friend auto operator<=>(const empty_value&, const empty_value&) = default; -}; - -PHANTOM_COROUTINES_MODULE_EXPORT -struct empty_arguments_value -{ - using value_type = std::tuple<>; - static constexpr value_type Value = {}; - - friend auto operator<=>(const empty_arguments_value&, const empty_arguments_value&) = default; -}; - -PHANTOM_COROUTINES_MODULE_EXPORT -constexpr empty_value empty{}; -PHANTOM_COROUTINES_MODULE_EXPORT -constexpr empty_arguments_value empty_arguments{ }; - -PHANTOM_COROUTINES_MODULE_EXPORT -struct no_awaiter_type -{ - static constexpr bool is_traced_promise_initial_suspend_awaiter = false; - static constexpr bool is_traced_promise_final_suspend_awaiter = false; - static constexpr bool is_traced_promise_co_yield_awaiter = false; - static constexpr bool is_traced_promise_co_await_awaiter = false; -}; - -PHANTOM_COROUTINES_MODULE_EXPORT -struct initial_suspend_awaiter_type -{ - static constexpr bool is_traced_promise_initial_suspend_awaiter = true; - static constexpr bool is_traced_promise_final_suspend_awaiter = false; - static constexpr bool is_traced_promise_co_yield_awaiter = false; - static constexpr bool is_traced_promise_co_await_awaiter = false; -}; - -PHANTOM_COROUTINES_MODULE_EXPORT -struct final_suspend_awaiter_type -{ - static constexpr bool is_traced_promise_initial_suspend_awaiter = false; - static constexpr bool is_traced_promise_final_suspend_awaiter = true; - static constexpr bool is_traced_promise_co_yield_awaiter = false; - static constexpr bool is_traced_promise_co_await_awaiter = false; -}; - -PHANTOM_COROUTINES_MODULE_EXPORT -struct co_yield_awaiter_type -{ - static constexpr bool is_traced_promise_initial_suspend_awaiter = false; - static constexpr bool is_traced_promise_final_suspend_awaiter = false; - static constexpr bool is_traced_promise_co_yield_awaiter = true; - static constexpr bool is_traced_promise_co_await_awaiter = false; -}; - -PHANTOM_COROUTINES_MODULE_EXPORT -struct co_await_awaiter_type -{ - static constexpr bool is_traced_promise_initial_suspend_awaiter = false; - static constexpr bool is_traced_promise_final_suspend_awaiter = false; - static constexpr bool is_traced_promise_co_yield_awaiter = false; - static constexpr bool is_traced_promise_co_await_awaiter = true; -}; - -template< - typename T -> concept is_empty = -std::same_as -|| -std::same_as -|| -std::same_as -|| -std::same_as; - -template< - typename T -> concept is_not_empty = !is_empty; - -template< - typename Event -> -struct PromiseAccessor -{ - static constexpr bool has_promise = requires(Event self) - { - { self.Data.Promise } -> is_not_empty; - }; - - auto* promise(this auto& self) - { - if constexpr (has_promise) - { - return self.Data.Promise; - } - else - { - return ∅ - } - } - - static constexpr bool has_traced_promise = requires(Event self) - { - { self.Data.TracedPromise } -> is_not_empty; - }; - - auto* traced_promise(this auto& self) - { - if constexpr (has_traced_promise) - { - return self.Data.TracedPromise; - } - else - { - return ∅ - } - } - - static constexpr bool has_promise_creation_arguments = requires(Event self) - { - { self.Data.PromiseCreationArguments } -> is_not_empty; - }; - - auto* promise_creation_arguments( - this auto& self - ) - { - if constexpr (has_promise_creation_arguments) - { - return self.Data.PromiseCreationArguments; - } - else - { - return &empty_arguments; - } - } -}; - -template< - typename Event -> -struct AwaiterAccessor -{ - static constexpr bool has_awaiter = requires(Event self) - { - { self.Data.Awaiter } -> is_not_empty; - }; - - auto* awaiter(this auto& self) - { - if constexpr (has_awaiter) - { - return self.Data.Awaiter; - } - else - { - return ∅ - } - } - - static constexpr bool has_awaiter_type = requires(Event self) - { - { self.Data.AwaiterType } -> is_not_empty; - }; - - auto* awaiter_type(this auto& self) - { - if constexpr (has_awaiter_type) - { - return self.Data.AwaiterType; - } - else - { - return ∅ - } - } - - static constexpr bool has_traced_awaiter = requires(Event self) - { - { self.Data.TracedAwaiter } -> is_not_empty; - }; - - auto* traced_awaiter(this auto& self) - { - if constexpr (has_awaiter) - { - return self.Data.TracedAwaiter; - } - else - { - return ∅ - } - } - -}; - -template< - typename Event -> -struct MethodArgumentsAccessor -{ - static constexpr bool has_method_arguments = requires(Event self) - { - { self.Data.MethodArguments } -> is_not_empty; - }; - - auto* method_arguments(this auto& self) - { - if constexpr (has_method_arguments) - { - return self.Data.MethodArguments; - } - else - { - return &empty_arguments; - } - } -}; - -template< - typename Event -> -struct MethodResultAccessor -{ - static constexpr bool has_method_result = requires(Event self) - { - { self.Data.MethodResult } -> is_not_empty; - }; - - auto* method_result(this auto& self) - { - if constexpr (has_method_result) - { - return self.Data.MethodResult; - } - else - { - return ∅ - } - } -}; - -template< - typename Event -> -struct MethodExceptionAccessor -{ - static constexpr bool has_method_exception = requires(Event self) - { - { self.Data.MethodException } -> is_not_empty; - }; - - auto* method_exception(this auto& self) - { - if constexpr (has_method_exception) - { - return self.Data.MethodException; - } - else - { - return ∅ - } - } -}; - -template< - typename Event -> -struct UnhandledExceptionAccessor -{ - static constexpr bool has_unhandled_exception = requires(Event self) - { - { self.Data.UnhandledException } -> is_not_empty; - }; - - auto* unhandled_exception(this auto& self) - { - if constexpr (has_unhandled_exception) - { - return self.Data.UnhandledException; - } - else - { - return ∅ - } - } -}; - -template< - typename Event -> -struct YieldValueAccessor -{ - static constexpr bool has_yield_value = requires(Event self) - { - { self.Data.YieldValue } -> is_not_empty; - }; - - auto* yield_value(this auto& self) - { - if constexpr (has_yield_value) - { - return self.Data.YieldValue; - } - else - { - return ∅ - } - } -}; - -template< - typename Event -> -struct ReturnValueAccessor -{ - static constexpr bool has_return_value = requires(Event self) - { - { self.Data.ReturnValue } -> is_not_empty; - }; - - auto* return_value(this auto& self) - { - if constexpr (has_return_value) - { - return self.Data.ReturnValue; - } - else - { - return ∅ - } - } -}; - -PHANTOM_COROUTINES_MODULE_EXPORT -struct create_promise {}; -PHANTOM_COROUTINES_MODULE_EXPORT -struct destroy_promise {}; -PHANTOM_COROUTINES_MODULE_EXPORT -struct await_ready_begin {}; -PHANTOM_COROUTINES_MODULE_EXPORT -struct await_ready_result {}; -PHANTOM_COROUTINES_MODULE_EXPORT -struct await_ready_exception {}; -PHANTOM_COROUTINES_MODULE_EXPORT -struct await_suspend_begin {}; -PHANTOM_COROUTINES_MODULE_EXPORT -struct await_suspend_result {}; -PHANTOM_COROUTINES_MODULE_EXPORT -struct await_suspend_exception {}; -PHANTOM_COROUTINES_MODULE_EXPORT -struct await_resume_begin {}; -PHANTOM_COROUTINES_MODULE_EXPORT -struct await_resume_result {}; -PHANTOM_COROUTINES_MODULE_EXPORT -struct await_resume_exception {}; -PHANTOM_COROUTINES_MODULE_EXPORT -struct await_transform_begin {}; -PHANTOM_COROUTINES_MODULE_EXPORT -struct await_transform_result {}; -PHANTOM_COROUTINES_MODULE_EXPORT -struct await_transform_exception {}; -PHANTOM_COROUTINES_MODULE_EXPORT -struct unhandled_exception_begin {}; -PHANTOM_COROUTINES_MODULE_EXPORT -struct unhandled_exception_result {}; -PHANTOM_COROUTINES_MODULE_EXPORT -struct unhandled_exception_exception {}; -PHANTOM_COROUTINES_MODULE_EXPORT -struct get_return_object_begin {}; -PHANTOM_COROUTINES_MODULE_EXPORT -struct get_return_object_result {}; -PHANTOM_COROUTINES_MODULE_EXPORT -struct get_return_object_exception {}; -PHANTOM_COROUTINES_MODULE_EXPORT -struct yield_value_begin {}; -PHANTOM_COROUTINES_MODULE_EXPORT -struct yield_value_result {}; -PHANTOM_COROUTINES_MODULE_EXPORT -struct yield_value_exception {}; -PHANTOM_COROUTINES_MODULE_EXPORT -struct return_value_begin {}; -PHANTOM_COROUTINES_MODULE_EXPORT -struct return_value_result {}; -PHANTOM_COROUTINES_MODULE_EXPORT -struct return_value_exception {}; -PHANTOM_COROUTINES_MODULE_EXPORT -struct return_void_begin {}; -PHANTOM_COROUTINES_MODULE_EXPORT -struct return_void_result {}; -PHANTOM_COROUTINES_MODULE_EXPORT -struct return_void_exception {}; - -PHANTOM_COROUTINES_MODULE_EXPORT -template< - typename Event, - typename EventType -> concept is_event_type = std::same_as; - -PHANTOM_COROUTINES_MODULE_EXPORT -template< - typename TPromise = empty_value, - typename TTracedPromise = empty_value, - typename TPromiseCreationArguments = empty_arguments_value, - typename TAwaiter = empty_value, - typename TTracedAwaiter = empty_value, - typename TAwaiterType = no_awaiter_type, - typename TMethodArguments = empty_arguments_value, - typename TMethodResult = empty_value, - typename TMethodException = empty_value, - typename TUnhandledException = empty_value, - typename TYieldValue = empty_value, - typename TReturnValue = empty_value -> -struct event_data -{ - PHANTOM_COROUTINES_NO_UNIQUE_ADDRESS TPromise Promise = empty; - PHANTOM_COROUTINES_NO_UNIQUE_ADDRESS TTracedPromise TracedPromise = empty; - PHANTOM_COROUTINES_NO_UNIQUE_ADDRESS TPromiseCreationArguments PromiseCreationArguments = empty_arguments; - PHANTOM_COROUTINES_NO_UNIQUE_ADDRESS TAwaiter Awaiter = empty; - PHANTOM_COROUTINES_NO_UNIQUE_ADDRESS TTracedAwaiter TracedAwaiter = empty; - PHANTOM_COROUTINES_NO_UNIQUE_ADDRESS TAwaiterType AwaiterType = no_awaiter_type{}; - PHANTOM_COROUTINES_NO_UNIQUE_ADDRESS TMethodArguments MethodArguments = empty_arguments; - PHANTOM_COROUTINES_NO_UNIQUE_ADDRESS TMethodResult MethodResult = empty; - PHANTOM_COROUTINES_NO_UNIQUE_ADDRESS TMethodException MethodException = empty; - PHANTOM_COROUTINES_NO_UNIQUE_ADDRESS TUnhandledException UnhandledException = empty; - PHANTOM_COROUTINES_NO_UNIQUE_ADDRESS TYieldValue YieldValue = empty; - PHANTOM_COROUTINES_NO_UNIQUE_ADDRESS TReturnValue ReturnValue = empty; - - friend auto operator<=>(const event_data&, const event_data&) = default; - - static constexpr auto combine( - const auto& lhs, - const auto& rhs - ) - { - if constexpr (is_empty>) - { - return lhs; - } - else - { - return rhs; - } - } - - auto with( - const auto& other - ) const - { - return events::event_data - { - .Promise = combine( - this->Promise, - other.Promise), - .TracedPromise = combine( - this->TracedPromise, - other.TracedPromise), - .PromiseCreationArguments = combine( - this->PromiseCreationArguments, - other.PromiseCreationArguments), - .Awaiter = combine( - this->Awaiter, - other.Awaiter), - .TracedAwaiter = combine( - this->TracedAwaiter, - other.TracedAwaiter), - .AwaiterType = combine( - this->AwaiterType, - other.AwaiterType), - .MethodArguments = combine( - this->MethodArguments, - other.MethodArguments), - .MethodResult = combine( - this->MethodResult, - other.MethodResult), - .MethodException = combine( - this->MethodException, - other.MethodException), - .UnhandledException = combine( - this->UnhandledException, - other.UnhandledException), - .YieldValue = combine( - this->YieldValue, - other.YieldValue), - .ReturnValue = combine( - this->ReturnValue, - other.ReturnValue), - }; - } -}; - -template< - typename EventType, - typename EventData -> -using basic_event_type = event< - EventType, - EventData, - PromiseAccessor, - AwaiterAccessor, - MethodArgumentsAccessor, - MethodResultAccessor, - MethodExceptionAccessor, - UnhandledExceptionAccessor, - YieldValueAccessor, - ReturnValueAccessor ->; - -PHANTOM_COROUTINES_MODULE_EXPORT -template< - typename EventType, - typename EventData -> -auto basic_event( - const std::source_location& sourceLocation, - const EventType& eventType, - const EventData& eventData -) -{ - return basic_event_type< - EventType, - EventData - > - { - .SourceLocation = sourceLocation, - .EventType = eventType, - .EventData = eventData, - }; -} - -// namespace events -} - -namespace detail -{ - -template< - is_trace_sink TraceSink -> -struct trace_sink_accessor -{ - TraceSink m_traceSink; - - auto& trace_sink() - { - return m_traceSink; - } -}; - -template< - typename BeginEventType, - typename ResultEventType, - typename ExceptionEventType -> -struct method_events_group -{ - method_events_group( - BeginEventType, - ResultEventType, - ExceptionEventType) - { } - - using begin_event_type = BeginEventType; - using result_event_type = ResultEventType; - using exception_event_type = ExceptionEventType; - - BeginEventType BeginEvent; - ResultEventType ResultEvent; - ExceptionEventType ExceptionEvent; -}; - -} // namespace detail - -auto call_traced_method( - const auto& traceSink, - auto events, - std::source_location sourceLocation, - const auto& baseEventData, - std::invocable<> auto call -) -> std::invoke_result_t -{ - using result_type = std::invoke_result_t; - constexpr bool is_move_constructible = std::is_move_constructible_v; - constexpr bool is_copy_constructible = std::is_copy_constructible_v; - constexpr bool can_trace_return_value = - ( - is_move_constructible - || - is_copy_constructible - ); - - try - { - traceSink( - events::basic_event( - sourceLocation, - events.BeginEvent, - baseEventData)); - - if (std::same_as) - { - call(); - traceSink( - events::basic_event( - sourceLocation, - events.ResultEvent, - baseEventData)); - } - else if constexpr (!can_trace_return_value) - { - auto uncaughtExceptions = std::uncaught_exceptions(); - - Coroutines::detail::scope_guard guard = [&]() - { - if (std::uncaught_exceptions() != uncaughtExceptions) - { - // If the number of uncaught exceptions has changed in between - // the call being performed and this guard being deactivated, - // it means an exception was thrown by the call, - // and we should not report a successful result. - return; - } - - traceSink( - events::basic_event( - sourceLocation, - events.ExceptionEvent, - baseEventData)); - }; - - call(); - } - else - { - decltype(auto) result = call(); - traceSink( - events::basic_event( - sourceLocation, - events.ResultEvent, - baseEventData.with( - events::event_data - { - .MethodResult = events::value{ result } - }))); - - if constexpr (is_move_constructible) - { - return std::forward(result); - } - else - { - return result; - } - } - } - catch (...) - { - auto exception = std::current_exception(); - traceSink( - events::basic_event( - sourceLocation, - events.ExceptionEvent, - baseEventData.with( - events::event_data - { - .MethodException = events::value{ exception } - }))); - throw; - } -} - -PHANTOM_COROUTINES_MODULE_EXPORT -template< - is_trace_sink TraceSink, - typename Awaitable, - typename AwaiterType, - typename BaseEventData -> -struct traced_awaiter : - detail::trace_sink_accessor, - awaiter_wrapper, - AwaiterType -{ - using awaiter_wrapper = awaiter_wrapper; - using trace_sink_accessor = traced_awaiter::trace_sink_accessor; - using traced_awaiter::trace_sink_accessor::trace_sink; - using wrapped_awaiter_type = std::remove_reference_t; - - std::source_location m_sourceLocation; - BaseEventData m_baseEventData; - - auto call_awaiter( - this auto& awaiter, - auto events, - const auto& baseEventData, - std::invocable<> auto call - - ) -> std::invoke_result_t - { - return call_traced_method( - awaiter.traced_awaiter::trace_sink(), - events, - awaiter.m_sourceLocation, - baseEventData.with( - events::event_data - { - .Awaiter = static_cast(std::addressof(awaiter.awaiter())), - .TracedAwaiter = std::addressof(awaiter), - .AwaiterType = AwaiterType{}, - }), - call); - } - - decltype(auto) await_ready( - this auto& self - ) noexcept(noexcept(self.awaiter_wrapper::await_ready())) - { - return self.traced_awaiter::call_awaiter( - detail::method_events_group - { - events::await_ready_begin{}, - events::await_ready_result{}, - events::await_ready_exception{}, - }, - self.get_event_data( - events::empty_arguments), - [&]() -> decltype(auto) - { - return self.awaiter_wrapper::await_ready(); - } - ); - } - - template< - typename Arg - > - decltype(auto) await_suspend( - this auto& self, - Arg&& arg - ) noexcept(noexcept(self.awaiter_wrapper::await_suspend(std::forward(arg)))) - { - return self.traced_awaiter::call_awaiter( - detail::method_events_group - { - events::await_suspend_begin{}, - events::await_suspend_result{}, - events::await_suspend_exception{}, - }, - self.get_event_data( - make_arguments(arg)), - [&]() -> decltype(auto) - { - return self.awaiter_wrapper::await_suspend( - std::forward(arg)); - } - ); - } - - decltype(auto) await_resume( - this auto& self - ) noexcept(noexcept(self.awaiter_wrapper::await_resume())) - { - return self.traced_awaiter::call_awaiter( - detail::method_events_group - { - events::await_resume_begin{}, - events::await_resume_result{}, - events::await_resume_exception{}, - }, - self.get_event_data( - events::empty_arguments), - [&]() -> decltype(auto) - { - return self.awaiter_wrapper::await_resume(); - } - ); - } - - traced_awaiter( - std::source_location sourceLocation, - std::invocable auto awaiterFunction, - AwaiterType, - trace_sink_accessor traceSinkAccessor, - BaseEventData baseEventData - ) - : - trace_sink_accessor{ traceSinkAccessor }, - m_sourceLocation{ sourceLocation }, - awaiter_wrapper{ std::move(awaiterFunction) }, - m_baseEventData{ std::move(baseEventData) } - { - } - - template< - typename ... Args - > - auto get_event_data( - this auto& awaiter, - const auto& methodArguments - ) - { - return awaiter.m_baseEventData.with( - events::event_data - { - .Awaiter = &awaiter, - .MethodArguments = methodArguments, - }); - } -}; - -template< - is_trace_sink TraceSink, - std::invocable<> AwaiterFunction, - typename AwaiterType, - typename BaseEventData -> -traced_awaiter( - std::source_location, - AwaiterFunction, - AwaiterType, - detail::trace_sink_accessor, - BaseEventData -) -> traced_awaiter< - TraceSink, - std::invoke_result_t, - AwaiterType, - BaseEventData ->; - - -namespace detail -{ - -// This class stored the trace sink and produces non-result-specific tracing events. -PHANTOM_COROUTINES_MODULE_EXPORT -template< - is_trace_sink TraceSink -> -class traced_promise_trace_sink_storage -{ - template< - typename Declaration - > - struct traced_promise_trace_sink_accessor; - -public: - using trace_sink_type = TraceSink; - -public: - trace_sink_type m_traceSink; - - template< - typename ... Args - > - requires - is_constructible_from_promise_arguments - traced_promise_trace_sink_storage( - Args&& ... args - ) : - m_traceSink(std::forward(args)...) - { - } - - // Allow default construction of the trace sink when arguments are provided. - template< - typename ... Args - > - requires - is_default_constructible_from_promise_arguments - traced_promise_trace_sink_storage( - Args&& ... args - ) : - m_traceSink() - { - } - - auto get_event_data( - this auto& promise - ) - { - return events::event_data - { - .Promise = &promise.get_underlying_promise(), - .TracedPromise = &promise, - }; - } - - decltype(auto) call_promise_method( - this auto& promise, - auto events, - std::source_location sourceLocation, - const auto& baseEventData, - std::invocable auto call - ) - { - return call_traced_method( - promise.traced_promise_trace_sink_storage::m_traceSink, - events, - sourceLocation, - baseEventData.with( - promise.get_event_data()), - call); - } -}; - -// This class embodies constructors for traced promises. -template< - is_trace_sink TraceSink, - typename BasePromise -> -class traced_promise_construction - : - public traced_promise_trace_sink_storage, - public derived_promise -{ -public: - template< - typename ... Args - > - requires - std::constructible_from, Args&&...> - && - std::constructible_from, Args&&...> - traced_promise_construction( - Args&& ... args - ) - : - traced_promise_construction::traced_promise_trace_sink_storage(std::forward(args)...), - traced_promise_construction::derived_promise(std::forward(args)... ) - { - } -}; - -// This class provides an implementation of yield_value for a promise -// if it has a yield_value implementation. -// The main template does not provide yield_value. -template< - is_trace_sink TraceSink, - typename BasePromise -> -class traced_promise_yield_value - : - public traced_promise_construction -{ -public: - using traced_promise_yield_value::traced_promise_construction::traced_promise_construction; -}; - -// The specialization for promises that have yield_value. -template< - is_trace_sink TraceSink, - has_yield_value BasePromise -> -class traced_promise_yield_value< - TraceSink, - BasePromise -> - : - public traced_promise_construction -{ -public: - using traced_promise_yield_value::traced_promise_construction::traced_promise_construction; - - // Trace an delegate the base class's yield_value implementation. - template< - typename TPromise - > - decltype(auto) yield_value( - this TPromise&& promise, - auto&& value, - std::source_location sourceLocation = std::source_location::current() - ) - { - return promise.traced_promise_yield_value::call_promise_method( - promise, - detail::method_events_group - { - events::yield_value_begin{}, - events::yield_value_result{}, - events::yield_value_exception{}, - }, - sourceLocation, - events::event_data - { - .MethodArguments = events::make_arguments(value), - }, - [&]() - { - return std::forward(promise).traced_promise_yield_value::yield_value( - std::forward(value)); - } - ); - } -}; - -template< - is_trace_sink TraceSink, - typename BasePromise -> -class traced_promise_return_value_or_void - : - public traced_promise_yield_value -{ - using traced_promise_return_value_or_void::traced_promise_yield_value::traced_promise_yield_value; - -public: - using traced_promise_return_value_or_void::traced_promise_yield_value::call_promise_method; - - template< - typename TPromise, - typename Value - > - void return_value( - this TPromise&& promise, - Value&& value, - std::source_location sourceLocation = std::source_location::current() - ) - { - std::forward(promise).call_promise_method( - detail::method_events_group - { - events::return_value_begin{}, - events::return_value_result{}, - events::return_value_exception{}, - }, - sourceLocation, - events::event_data - { - .MethodArguments = events::make_arguments(value), - }, - [&]() - { - std::forward(promise).traced_promise_return_value_or_void::traced_promise_yield_value::return_value( - std::forward(value)); - } - ); - } -}; - -template< - is_trace_sink TraceSink, - has_return_void BasePromise -> -class traced_promise_return_value_or_void< - TraceSink, - BasePromise -> - : - public traced_promise_yield_value -{ - using traced_promise_return_value_or_void::traced_promise_yield_value::traced_promise_yield_value; - -public: - using traced_promise_return_value_or_void::traced_promise_yield_value::call_promise_method; - - template< - typename TPromise - > - void return_void( - this TPromise&& promise, - std::source_location sourceLocation = std::source_location::current() - ) - { - std::forward(promise).call_promise_method( - detail::method_events_group - { - events::return_void_begin{}, - events::return_void_result{}, - events::return_void_exception{}, - }, - sourceLocation, - events::event_data - { - }, - [&]() - { - std::forward(promise).traced_promise_return_value_or_void::traced_promise_yield_value::return_void(); - } - ); - } -}; - -template< - is_trace_sink TraceSink, - typename BasePromise -> -using traced_promise_base = traced_promise_return_value_or_void; - -} // namespace detail - -// Use suppress_trace to suppress tracing of an awaitable. -// Example: -// co_await suppress_trace{ m_event.Wait() }; -PHANTOM_COROUTINES_MODULE_EXPORT -template< - typename Awaiter -> -struct suppress_trace -{ - Awaiter&& value; -}; - -// Use trace to send a value to the trace sink. -// Example: -// co_await trace{ my_trace_event_information{} }; -PHANTOM_COROUTINES_MODULE_EXPORT -struct trace -{ -}; - -PHANTOM_COROUTINES_MODULE_EXPORT -template< - is_trace_sink TraceSink, - is_extensible_promise BasePromise -> -class traced_promise - : - public detail::traced_promise_base -{ -public: - using traced_promise_base = detail::traced_promise_base; - using traced_promise_base::call_promise_method; - using traced_promise_base::get_event_data; - using traced_promise_base::m_traceSink; - - template< - typename Declaration - > - struct traced_promise_trace_sink_accessor; - -public: - template< - typename ... Args - > - traced_promise( - Args&& ... args - ) - : - traced_promise_base{ std::forward(args)... } - { - m_traceSink( - events::basic_event( - std::source_location::current(), - events::create_promise{}, - events::event_data - { - .Promise = &this->get_underlying_promise(), - .TracedPromise = this, - .PromiseCreationArguments = events::make_arguments(args...), - })); - } - - ~traced_promise() - { - m_traceSink( - basic_event( - std::source_location::current(), - events::destroy_promise{}, - get_event_data() - )); - } - - template< - typename TPromise - > - auto initial_suspend( - this TPromise& promise, - std::source_location sourceLocation = std::source_location::current() - ) - { - return traced_awaiter - { - sourceLocation, - [&]() { return promise.traced_promise_base::initial_suspend(); }, - events::initial_suspend_awaiter_type{}, - detail::trace_sink_accessor - { - promise.traced_promise::m_traceSink - }, - promise.get_event_data() - }; - } - - template< - typename TPromise - > - auto final_suspend( - this TPromise& promise, - std::source_location sourceLocation = std::source_location::current() - ) noexcept - { - return traced_awaiter - { - sourceLocation, - [&]() { return promise.traced_promise_base::final_suspend(); }, - events::final_suspend_awaiter_type{}, - detail::trace_sink_accessor - { - promise.traced_promise::m_traceSink - }, - promise.get_event_data() - }; - } - - template< - typename TPromise - > - void unhandled_exception( - this TPromise& promise, - std::source_location sourceLocation = std::source_location::current() - ) - { - promise.call_promise_method( - detail::method_events_group - { - events::unhandled_exception_begin{}, - events::unhandled_exception_result{}, - events::unhandled_exception_exception{}, - }, - sourceLocation, - events::event_data{}, - [&]() - { - return promise.traced_promise_base::unhandled_exception(); - } - ); - } - - auto await_transform( - this auto& promise, - auto&& awaiter, - std::source_location sourceLocation = std::source_location::current() - ) - { - return traced_awaiter - { - sourceLocation, - [&]() -> decltype(auto) - { - return promise.traced_promise_base::await_transform( - std::forward(awaiter)); - }, - events::co_await_awaiter_type{}, - detail::trace_sink_accessor - { - promise.traced_promise::m_traceSink - }, - promise.get_event_data() - }; - } - - template< - typename Awaiter - > decltype(auto) await_transform( - this auto& promise, - const suppress_trace& noTraceAwaiter - ) - { - return promise.traced_promise_base::await_transform( - noTraceAwaiter.value - ); - } - - suspend_never await_transform( - this auto& promise, - std::derived_from auto& traceEvent - ) - { - promise.m_traceSink( - traceEvent); - return suspend_never{}; - } - - template< - typename This - > - auto& get_underlying_promise( - this This& self) - { - if constexpr (std::is_const_v) - { - return static_cast(self); - } - else - { - return static_cast(self); - } - } -}; - -PHANTOM_COROUTINES_MODULE_EXPORT -namespace filters -{ - -struct filter; - -template< - typename T -> -concept is_filter = std::derived_from; - -template< - is_filter Left, - is_filter Right -> -struct and_filter; - -template< - is_filter Left, - is_filter Right -> -struct or_filter; - -template< - is_filter Filter -> -struct not_filter; - -struct filter -{ - constexpr std::false_type operator()(const auto&) const noexcept { - return {}; - } - - template< - is_filter Left, - is_filter Right - > - friend constexpr auto operator&&( - Left left, - Right right - ) noexcept - { - return and_filter{ left, right }; - } - - template< - is_filter Left, - is_filter Right - > - friend constexpr auto operator||( - Left left, - Right right - ) noexcept - { - return or_filter{ left, right }; - } - - template< - is_filter Filter - > - friend constexpr auto operator!( - Filter filter - ) noexcept - { - return not_filter{ filter }; - } -}; - -template< - is_filter Left, - is_filter Right -> -struct and_filter : filter -{ - Left left; - Right right; - - constexpr bool operator()( - const auto& event - ) const noexcept - { - using leftType = decltype(left(event)); - using rightType = decltype(right(event)); - - if constexpr ( - std::same_as - || - std::same_as) - { - return std::false_type{}; - } - else if constexpr ( - std::same_as && - std::same_as) - { - return std::true_type{}; - } - else - { - return left(event) && right(event); - } - } -}; - -template< - is_filter Left, - is_filter Right -> -struct or_filter : filter -{ - Left left; - Right right; - - constexpr bool operator()( - const auto& event - ) const noexcept - { - using leftType = decltype(left(event)); - using rightType = decltype(right(event)); - - if constexpr ( - std::same_as - || - std::same_as) - { - return std::true_type{}; - } - else if constexpr ( - std::same_as && - std::same_as) - { - return std::false_type{}; - } - else - { - return left(event) && right(event); - } - } -}; - -template< - is_filter Filter -> -struct not_filter : filter -{ - Filter filter; - - constexpr auto operator()( - const auto& event) - { - using type = decltype(filter(event)); - if constexpr (std::same_as) - { - return std::false_type{}; - } - else if constexpr (std::same_as) - { - return std::true_type{}; - } - else - { - return !filter(event); - } - }; -}; - -struct any_event_fn : filter -{ - constexpr std::true_type operator()(const auto&) const noexcept { - return {}; - } -}; -constexpr any_event_fn any_event{}; - -template< - typename ... EventType -> -struct event_type_fn : filter -{ - using filter::operator(); - constexpr std::true_type operator()( - const auto& event - ) const noexcept - requires ( - events::is_event_type, EventType> || ... - ) - { - return {}; - } -}; - -template< - typename ... EventType -> -event_type_fn event_type; - -template< - typename AwaiterType -> -struct awaiter_type_fn : filter -{ - using filter::operator(); - constexpr std::true_type operator()( - const auto& event - ) const noexcept - requires std::same_as< - const decltype(event.EventData.AwaiterType)&, - const AwaiterType& - > - { - return {}; - } -}; - -template< - typename AwaiterType -> -constexpr awaiter_type_fn awaiter_type; - -template< - typename Event -> -struct check_constexpr_fn -{ - template< - is_filter Filter - > constexpr auto operator()( - Filter filter - ) const noexcept - { - return decltype(filter(std::declval())){}; - } -}; - -template< - typename Event -> constexpr check_constexpr_fn check_constexpr{}; - -constexpr auto constant_filtered_trace_sink( - auto filter, - auto&& traceSink -) -{ - return [traceSink = std::forward(traceSink), filter](const auto& event) - { - if constexpr (filter(event)) - { - traceSink(event); - } - }; -} - -constexpr auto runtime_filtered_trace_sink( - auto filter, - auto&& traceSink -) -{ - return [traceSink = std::forward(traceSink), filter](const auto& event) - { - if (filter(event)) - { - traceSink(event); - } - }; -} - -// namespace filters -} - -// namespace tracing -} - - -// namespace Phantom::Coroutines -} - -#endif +#ifndef PHANTOM_COROUTINES_INCLUDE_TRACING_H +#define PHANTOM_COROUTINES_INCLUDE_TRACING_H +#ifndef PHANTOM_COROUTINES_COMPILING_MODULES +#include +#include +#include +#include +#include +#include +#include +#include +#include "detail/config_macros.h" +#include "detail/coroutine.h" +#include "detail/scope_guard.h" +#include "awaiter_wrapper.h" +#include "functional.h" +#include "extensible_promise.h" +#include "type_traits.h" +#endif + +static_assert(PHANTOM_COROUTINES_IS_CONFIGURED); +PHANTOM_COROUTINES_ASSERT_IS_MODULE; + +namespace Phantom::Coroutines +{ + +namespace tracing +{ + +PHANTOM_COROUTINES_MODULE_EXPORT +template< + typename TraceSink +> concept is_trace_sink = true; +//requires +//{ +// trace(); +// should_trace(); +// should_trace_return_value; +//}; + +namespace events +{ +PHANTOM_COROUTINES_MODULE_EXPORT +template< + typename Awaiter +> concept is_traced_promise_initial_suspend_awaiter = std::remove_cvref_t::is_traced_promise_initial_suspend_awaiter; + +PHANTOM_COROUTINES_MODULE_EXPORT +template< + typename Awaiter +> concept is_traced_promise_final_suspend_awaiter = std::remove_cvref_t::is_traced_promise_final_suspend_awaiter; + +PHANTOM_COROUTINES_MODULE_EXPORT +template< + typename Awaiter +> concept is_traced_promise_co_yield_awaiter = std::remove_cvref_t::is_traced_promise_co_yield_awaiter; + +PHANTOM_COROUTINES_MODULE_EXPORT +template< + typename Awaiter +> concept is_traced_promise_co_await_awaiter = std::remove_cvref_t::is_traced_promise_co_await_awaiter; + +PHANTOM_COROUTINES_MODULE_EXPORT +template< + typename TValue +> +struct value +{ + using value_type = TValue; + const value_type& Value; + + friend auto operator<=>(const value&, const value&) = default; +}; + +PHANTOM_COROUTINES_MODULE_EXPORT +template< + typename ... Args +> +using arguments = value>; + +PHANTOM_COROUTINES_MODULE_EXPORT +constexpr auto make_arguments( + const auto&... args +) +{ + return value{ std::tie(std::as_const(args)...) }; +} + +PHANTOM_COROUTINES_MODULE_EXPORT +template< + typename Label +> +struct event_type +{ + event_type() = default; + event_type( + Label + ) + { + } + + using label_type = Label; + + template< + typename Label1, + typename Label2 + > + friend consteval bool operator==( + const event_type&, + const event_type&); + + template< + typename Label1, + typename Label2 + > + friend consteval bool operator!=( + const event_type&, + const event_type&); +}; + +PHANTOM_COROUTINES_MODULE_EXPORT +template< + typename Label1, + typename Label2 +> +consteval bool operator==( + const event_type&, + const event_type&) +{ + return std::same_as; +} + +PHANTOM_COROUTINES_MODULE_EXPORT +template< + typename Label1, + typename Label2 +> +consteval bool operator!=( + const event_type&, + const event_type&) +{ + return !std::same_as; +} + +PHANTOM_COROUTINES_MODULE_EXPORT +template< + typename TEventType, + typename TEventData, + template typename ... Accessors +> +struct event : + Accessors>... +{ + using event_type = event_type; + using event_data_type = TEventData; + + std::source_location SourceLocation; + event_type EventType; + event_data_type EventData; +}; + +PHANTOM_COROUTINES_MODULE_EXPORT +struct empty_value +{ + using value_type = void; + static constexpr void* Value = nullptr; + + friend auto operator<=>(const empty_value&, const empty_value&) = default; +}; + +PHANTOM_COROUTINES_MODULE_EXPORT +struct empty_arguments_value +{ + using value_type = std::tuple<>; + static constexpr value_type Value = {}; + + friend auto operator<=>(const empty_arguments_value&, const empty_arguments_value&) = default; +}; + +PHANTOM_COROUTINES_MODULE_EXPORT +constexpr empty_value empty{}; +PHANTOM_COROUTINES_MODULE_EXPORT +constexpr empty_arguments_value empty_arguments{ }; + +PHANTOM_COROUTINES_MODULE_EXPORT +struct no_awaiter_type +{ + static constexpr bool is_traced_promise_initial_suspend_awaiter = false; + static constexpr bool is_traced_promise_final_suspend_awaiter = false; + static constexpr bool is_traced_promise_co_yield_awaiter = false; + static constexpr bool is_traced_promise_co_await_awaiter = false; +}; + +PHANTOM_COROUTINES_MODULE_EXPORT +struct initial_suspend_awaiter_type +{ + static constexpr bool is_traced_promise_initial_suspend_awaiter = true; + static constexpr bool is_traced_promise_final_suspend_awaiter = false; + static constexpr bool is_traced_promise_co_yield_awaiter = false; + static constexpr bool is_traced_promise_co_await_awaiter = false; +}; + +PHANTOM_COROUTINES_MODULE_EXPORT +struct final_suspend_awaiter_type +{ + static constexpr bool is_traced_promise_initial_suspend_awaiter = false; + static constexpr bool is_traced_promise_final_suspend_awaiter = true; + static constexpr bool is_traced_promise_co_yield_awaiter = false; + static constexpr bool is_traced_promise_co_await_awaiter = false; +}; + +PHANTOM_COROUTINES_MODULE_EXPORT +struct co_yield_awaiter_type +{ + static constexpr bool is_traced_promise_initial_suspend_awaiter = false; + static constexpr bool is_traced_promise_final_suspend_awaiter = false; + static constexpr bool is_traced_promise_co_yield_awaiter = true; + static constexpr bool is_traced_promise_co_await_awaiter = false; +}; + +PHANTOM_COROUTINES_MODULE_EXPORT +struct co_await_awaiter_type +{ + static constexpr bool is_traced_promise_initial_suspend_awaiter = false; + static constexpr bool is_traced_promise_final_suspend_awaiter = false; + static constexpr bool is_traced_promise_co_yield_awaiter = false; + static constexpr bool is_traced_promise_co_await_awaiter = true; +}; + +template< + typename T +> concept is_empty = +std::same_as +|| +std::same_as +|| +std::same_as +|| +std::same_as; + +template< + typename T +> concept is_not_empty = !is_empty; + +template< + typename Event +> +struct PromiseAccessor +{ + static constexpr bool has_promise = requires(Event self) + { + { self.Data.Promise } -> is_not_empty; + }; + + auto* promise(this auto& self) + { + if constexpr (has_promise) + { + return self.Data.Promise; + } + else + { + return ∅ + } + } + + static constexpr bool has_traced_promise = requires(Event self) + { + { self.Data.TracedPromise } -> is_not_empty; + }; + + auto* traced_promise(this auto& self) + { + if constexpr (has_traced_promise) + { + return self.Data.TracedPromise; + } + else + { + return ∅ + } + } + + static constexpr bool has_promise_creation_arguments = requires(Event self) + { + { self.Data.PromiseCreationArguments } -> is_not_empty; + }; + + auto* promise_creation_arguments( + this auto& self + ) + { + if constexpr (has_promise_creation_arguments) + { + return self.Data.PromiseCreationArguments; + } + else + { + return &empty_arguments; + } + } +}; + +template< + typename Event +> +struct AwaiterAccessor +{ + static constexpr bool has_awaiter = requires(Event self) + { + { self.Data.Awaiter } -> is_not_empty; + }; + + auto* awaiter(this auto& self) + { + if constexpr (has_awaiter) + { + return self.Data.Awaiter; + } + else + { + return ∅ + } + } + + static constexpr bool has_awaiter_type = requires(Event self) + { + { self.Data.AwaiterType } -> is_not_empty; + }; + + auto* awaiter_type(this auto& self) + { + if constexpr (has_awaiter_type) + { + return self.Data.AwaiterType; + } + else + { + return ∅ + } + } + + static constexpr bool has_traced_awaiter = requires(Event self) + { + { self.Data.TracedAwaiter } -> is_not_empty; + }; + + auto* traced_awaiter(this auto& self) + { + if constexpr (has_awaiter) + { + return self.Data.TracedAwaiter; + } + else + { + return ∅ + } + } + +}; + +template< + typename Event +> +struct MethodArgumentsAccessor +{ + static constexpr bool has_method_arguments = requires(Event self) + { + { self.Data.MethodArguments } -> is_not_empty; + }; + + auto* method_arguments(this auto& self) + { + if constexpr (has_method_arguments) + { + return self.Data.MethodArguments; + } + else + { + return &empty_arguments; + } + } +}; + +template< + typename Event +> +struct MethodResultAccessor +{ + static constexpr bool has_method_result = requires(Event self) + { + { self.Data.MethodResult } -> is_not_empty; + }; + + auto* method_result(this auto& self) + { + if constexpr (has_method_result) + { + return self.Data.MethodResult; + } + else + { + return ∅ + } + } +}; + +template< + typename Event +> +struct MethodExceptionAccessor +{ + static constexpr bool has_method_exception = requires(Event self) + { + { self.Data.MethodException } -> is_not_empty; + }; + + auto* method_exception(this auto& self) + { + if constexpr (has_method_exception) + { + return self.Data.MethodException; + } + else + { + return ∅ + } + } +}; + +template< + typename Event +> +struct UnhandledExceptionAccessor +{ + static constexpr bool has_unhandled_exception = requires(Event self) + { + { self.Data.UnhandledException } -> is_not_empty; + }; + + auto* unhandled_exception(this auto& self) + { + if constexpr (has_unhandled_exception) + { + return self.Data.UnhandledException; + } + else + { + return ∅ + } + } +}; + +template< + typename Event +> +struct YieldValueAccessor +{ + static constexpr bool has_yield_value = requires(Event self) + { + { self.Data.YieldValue } -> is_not_empty; + }; + + auto* yield_value(this auto& self) + { + if constexpr (has_yield_value) + { + return self.Data.YieldValue; + } + else + { + return ∅ + } + } +}; + +template< + typename Event +> +struct ReturnValueAccessor +{ + static constexpr bool has_return_value = requires(Event self) + { + { self.Data.ReturnValue } -> is_not_empty; + }; + + auto* return_value(this auto& self) + { + if constexpr (has_return_value) + { + return self.Data.ReturnValue; + } + else + { + return ∅ + } + } +}; + +// The trace sink is _queried_ for should_capture_promise_creation_arguments +// to determine whether to capture the promise creation arguments. The query is +// in the form of: +// * Check to see if traceSink(const should_capture_promise_creation_arguments&) is accepted. +// * If it is not, then do not capture and trace the promise creation arguments. +// * If it is, check the return value: +// * If it is std::true_type, then capture and trace the promise creation arguments. +// * If it is std::false_type, then do not capture and trace the promise creation arguments +// * If it is bool, then check the value at runtime to determine +// whether to capture and trace the promise creation arguments. +PHANTOM_COROUTINES_MODULE_EXPORT +template< + typename PromiseCreationEvent +> +struct should_capture_promise_creation_arguments +{ + using event_type = PromiseCreationEvent; +}; + +// The trace sink is _queried_ for should_trace_return_value +// to determine whether to trace the return value. The query is +// in the form of: +// * Check to see if traceSink(const should_trace_return_value&) is accepted. +// * If it is not, then do not trace the return value. +// * If it is, check the return value: +// * If it is std::true_type, then trace the return value. +// * If it is std::false_type, then do not trace the return +// * If it is bool, then check the value at runtime to determine +// whether to trace the return value. +PHANTOM_COROUTINES_MODULE_EXPORT +template< + typename Event +> +struct should_trace_return_value +{ + using event_type = Event; +}; + +PHANTOM_COROUTINES_MODULE_EXPORT +struct event_is_begin +{ + static constexpr bool is_begin = true; +}; + +PHANTOM_COROUTINES_MODULE_EXPORT +struct event_is_result +{ + static constexpr bool is_result = true; +}; + +PHANTOM_COROUTINES_MODULE_EXPORT +struct event_is_exception +{ + static constexpr bool is_exception = true; +}; + +PHANTOM_COROUTINES_MODULE_EXPORT +struct event_is_await_ready +{ + static constexpr bool is_await_ready = true; +}; + +PHANTOM_COROUTINES_MODULE_EXPORT +struct event_is_await_suspend +{ + static constexpr bool is_await_suspend = true; +}; + +PHANTOM_COROUTINES_MODULE_EXPORT +struct event_is_await_resume +{ + static constexpr bool is_await_resume = true; +}; + +PHANTOM_COROUTINES_MODULE_EXPORT +struct event_is_await_transform +{ + static constexpr bool is_await_transform = true; +}; + +PHANTOM_COROUTINES_MODULE_EXPORT +struct event_is_unhandled_exception +{ + static constexpr bool is_unhandled_exception = true; +}; + +PHANTOM_COROUTINES_MODULE_EXPORT +struct event_is_get_return_object +{ + static constexpr bool is_get_return_object = true; +}; + +PHANTOM_COROUTINES_MODULE_EXPORT +struct event_is_yield_value +{ + static constexpr bool is_yield_value = true; +}; + +PHANTOM_COROUTINES_MODULE_EXPORT +struct event_is_return_value +{ + static constexpr bool is_return_value = true; +}; + +PHANTOM_COROUTINES_MODULE_EXPORT +struct event_is_return_void +{ + static constexpr bool is_return_void = true; +}; + +PHANTOM_COROUTINES_MODULE_EXPORT +struct create_promise {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct destroy_promise {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct await_ready_begin : event_is_await_ready, event_is_begin {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct await_ready_result : event_is_await_ready, event_is_result {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct await_ready_exception : event_is_await_ready, event_is_exception {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct await_suspend_begin : event_is_await_suspend, event_is_begin {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct await_suspend_result : event_is_await_suspend, event_is_result {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct await_suspend_exception : event_is_await_suspend, event_is_exception {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct await_resume_begin : event_is_await_resume, event_is_begin {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct await_resume_result : event_is_await_resume, event_is_result {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct await_resume_exception : event_is_await_resume, event_is_exception {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct await_transform_begin : event_is_await_transform, event_is_begin {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct await_transform_result : event_is_await_transform, event_is_result {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct await_transform_exception : event_is_await_transform, event_is_exception {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct unhandled_exception_begin : event_is_unhandled_exception, event_is_begin {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct unhandled_exception_result : event_is_unhandled_exception, event_is_result {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct unhandled_exception_exception : event_is_unhandled_exception, event_is_exception {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct get_return_object_begin : event_is_get_return_object, event_is_begin {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct get_return_object_result : event_is_get_return_object, event_is_result {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct get_return_object_exception : event_is_get_return_object, event_is_exception {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct yield_value_begin : event_is_yield_value, event_is_begin {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct yield_value_result : event_is_yield_value, event_is_result {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct yield_value_exception : event_is_yield_value, event_is_exception {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct return_value_begin : event_is_return_value, event_is_begin {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct return_value_result : event_is_return_value, event_is_result {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct return_value_exception : event_is_return_value, event_is_exception {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct return_void_begin : event_is_return_void, event_is_begin {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct return_void_result : event_is_return_void, event_is_result {}; +PHANTOM_COROUTINES_MODULE_EXPORT +struct return_void_exception : event_is_return_void, event_is_exception {}; + +PHANTOM_COROUTINES_MODULE_EXPORT +template< + typename Event, + typename EventType +> concept is_event_type = std::same_as; + +PHANTOM_COROUTINES_MODULE_EXPORT +template< + typename TPromise = empty_value, + typename TTracedPromise = empty_value, + typename TPromiseCreationArguments = empty_arguments_value, + typename TAwaiter = empty_value, + typename TTracedAwaiter = empty_value, + typename TAwaiterType = no_awaiter_type, + typename TMethodArguments = empty_arguments_value, + typename TMethodResult = empty_value, + typename TMethodException = empty_value, + typename TUnhandledException = empty_value, + typename TYieldValue = empty_value, + typename TReturnValue = empty_value +> +struct event_data +{ + PHANTOM_COROUTINES_NO_UNIQUE_ADDRESS TPromise Promise = empty; + PHANTOM_COROUTINES_NO_UNIQUE_ADDRESS TTracedPromise TracedPromise = empty; + PHANTOM_COROUTINES_NO_UNIQUE_ADDRESS TPromiseCreationArguments PromiseCreationArguments = empty_arguments; + PHANTOM_COROUTINES_NO_UNIQUE_ADDRESS TAwaiter Awaiter = empty; + PHANTOM_COROUTINES_NO_UNIQUE_ADDRESS TTracedAwaiter TracedAwaiter = empty; + PHANTOM_COROUTINES_NO_UNIQUE_ADDRESS TAwaiterType AwaiterType = no_awaiter_type{}; + PHANTOM_COROUTINES_NO_UNIQUE_ADDRESS TMethodArguments MethodArguments = empty_arguments; + PHANTOM_COROUTINES_NO_UNIQUE_ADDRESS TMethodResult MethodResult = empty; + PHANTOM_COROUTINES_NO_UNIQUE_ADDRESS TMethodException MethodException = empty; + PHANTOM_COROUTINES_NO_UNIQUE_ADDRESS TUnhandledException UnhandledException = empty; + PHANTOM_COROUTINES_NO_UNIQUE_ADDRESS TYieldValue YieldValue = empty; + PHANTOM_COROUTINES_NO_UNIQUE_ADDRESS TReturnValue ReturnValue = empty; + + friend auto operator<=>(const event_data&, const event_data&) = default; + + static constexpr auto combine( + const auto& lhs, + const auto& rhs + ) + { + if constexpr (is_empty>) + { + return lhs; + } + else + { + return rhs; + } + } + + auto with( + const auto& other + ) const + { + return events::event_data + { + .Promise = combine( + this->Promise, + other.Promise), + .TracedPromise = combine( + this->TracedPromise, + other.TracedPromise), + .PromiseCreationArguments = combine( + this->PromiseCreationArguments, + other.PromiseCreationArguments), + .Awaiter = combine( + this->Awaiter, + other.Awaiter), + .TracedAwaiter = combine( + this->TracedAwaiter, + other.TracedAwaiter), + .AwaiterType = combine( + this->AwaiterType, + other.AwaiterType), + .MethodArguments = combine( + this->MethodArguments, + other.MethodArguments), + .MethodResult = combine( + this->MethodResult, + other.MethodResult), + .MethodException = combine( + this->MethodException, + other.MethodException), + .UnhandledException = combine( + this->UnhandledException, + other.UnhandledException), + .YieldValue = combine( + this->YieldValue, + other.YieldValue), + .ReturnValue = combine( + this->ReturnValue, + other.ReturnValue), + }; + } +}; + +template< + typename EventType, + typename EventData +> +using basic_event_type = event< + EventType, + EventData, + PromiseAccessor, + AwaiterAccessor, + MethodArgumentsAccessor, + MethodResultAccessor, + MethodExceptionAccessor, + UnhandledExceptionAccessor, + YieldValueAccessor, + ReturnValueAccessor +>; + +PHANTOM_COROUTINES_MODULE_EXPORT +template< + typename EventType, + typename EventData +> +auto basic_event( + const std::source_location& sourceLocation, + const EventType& eventType, + const EventData& eventData +) +{ + return basic_event_type< + EventType, + EventData + > + { + .SourceLocation = sourceLocation, + .EventType = eventType, + .EventData = eventData, + }; +} + +// namespace events +} + +namespace detail +{ + +template< + is_trace_sink TraceSink +> +struct trace_sink_accessor +{ + TraceSink m_traceSink; + + auto& trace_sink() + { + return m_traceSink; + } +}; + +template< + typename BeginEventType, + typename ResultEventType, + typename ExceptionEventType +> +struct method_events_group +{ + method_events_group( + BeginEventType, + ResultEventType, + ExceptionEventType) + { } + + using begin_event_type = BeginEventType; + using result_event_type = ResultEventType; + using exception_event_type = ExceptionEventType; + + BeginEventType BeginEvent; + ResultEventType ResultEvent; + ExceptionEventType ExceptionEvent; +}; + +template< + typename TraceSink, + typename EventType, + typename BaseEventData, + typename MethodResult +> +struct trace_sink_should_trace_return_value_result_type +{ + static constexpr bool can_trace_return_value = false; + static constexpr bool should_query_for_trace_return_value = false; +}; + +template< + typename EventType, + typename BaseEventData, + typename MethodResult +> +using should_trace_return_value_query_type = events::should_trace_return_value< + decltype( + std::declval().with( + events::event_data + { + .MethodResult = events::value{ std::declval() } + }) + ) > ; + +template< + typename TraceSink, + typename EventType, + typename BaseEventData, + typename MethodResult +> +requires +( + ( + std::is_move_constructible_v + || + std::is_copy_constructible_v + ) + && + std::invocable< + TraceSink, + should_trace_return_value_query_type< + EventType, + BaseEventData, + MethodResult + > + > +) +struct trace_sink_should_trace_return_value_result_type< + TraceSink, + EventType, + BaseEventData, + MethodResult +> +{ + using query_type = should_trace_return_value_query_type< + EventType, + BaseEventData, + MethodResult + >; + + using type = std::invoke_result_t< + TraceSink, + query_type>; + + static constexpr bool can_trace_return_value = + std::same_as + || std::same_as; + + static constexpr bool should_query_for_trace_return_value = std::same_as; +}; + +} // namespace detail + +auto call_traced_method( + const auto& traceSink, + auto events, + std::source_location sourceLocation, + const auto& baseEventData, + std::invocable<> auto call +) -> std::invoke_result_t +{ + using result_type = std::invoke_result_t; + constexpr bool is_move_constructible = std::is_move_constructible_v; + constexpr bool is_copy_constructible = std::is_copy_constructible_v; + using trace_sink_should_trace_return_value_result_type = detail::trace_sink_should_trace_return_value_result_type< + decltype(traceSink), + decltype(events.ResultEvent), + decltype(baseEventData), + result_type>; + + constexpr bool can_trace_return_value = + trace_sink_should_trace_return_value_result_type::can_trace_return_value + && + ( + is_move_constructible + || + is_copy_constructible + ); + + constexpr bool should_query_for_trace_return_value = can_trace_return_value + && trace_sink_should_trace_return_value_result_type::should_query_for_trace_return_value; + + auto invoke_without_tracing_return_value = [&]() -> result_type + { + if constexpr (std::same_as) + { + call(); + traceSink( + events::basic_event( + sourceLocation, + events.ResultEvent, + baseEventData)); + } + else + { + auto uncaughtExceptions = std::uncaught_exceptions(); + + Coroutines::detail::scope_guard guard = [&]() + { + if (std::uncaught_exceptions() != uncaughtExceptions) + { + // If the number of uncaught exceptions has changed in between + // the call being performed and this guard being deactivated, + // it means an exception was thrown by the call, + // and we should not report a "result" type but an "exception" type. + return; + }; + + traceSink( + events::basic_event( + sourceLocation, + events.ResultEvent, + baseEventData)); + }; + + return call(); + } + }; + + try + { + traceSink( + events::basic_event( + sourceLocation, + events.BeginEvent, + baseEventData)); + + if constexpr (!can_trace_return_value) + { + return invoke_without_tracing_return_value(); + } + else + { + auto invoke_with_tracing_return_value = [&]() -> result_type + { + decltype(auto) result = call(); + traceSink( + events::basic_event( + sourceLocation, + events.ResultEvent, + baseEventData.with( + events::event_data + { + .MethodResult = events::value{ result } + }))); + + if constexpr (is_move_constructible) + { + return auto(result); + } + else + { + return result; + } + }; + + if constexpr (!should_query_for_trace_return_value) + { + return invoke_with_tracing_return_value(); + } + else + { + bool shouldTraceReturnValue = traceSink( + events::should_trace_return_value< + decltype(events::basic_event( + sourceLocation, + events.ResultEvent, + baseEventData.with( + events::event_data + { + .MethodResult = events::value{ std::declval() } + })))>{}); + if (shouldTraceReturnValue) + { + return invoke_with_tracing_return_value(); + } + else + { + return invoke_without_tracing_return_value(); + } + } + } + } + catch (...) + { + auto exception = std::current_exception(); + traceSink( + events::basic_event( + sourceLocation, + events.ExceptionEvent, + baseEventData.with( + events::event_data + { + .MethodException = events::value{ exception } + }))); + throw; + } +} + +PHANTOM_COROUTINES_MODULE_EXPORT +template< + is_trace_sink TraceSink, + typename Awaitable, + typename AwaiterType, + typename BaseEventData +> +struct traced_awaiter : + detail::trace_sink_accessor, + awaiter_wrapper, + AwaiterType +{ + using awaiter_wrapper = awaiter_wrapper; + using trace_sink_accessor = traced_awaiter::trace_sink_accessor; + using traced_awaiter::trace_sink_accessor::trace_sink; + using wrapped_awaiter_type = std::remove_reference_t; + + std::source_location m_sourceLocation; + BaseEventData m_baseEventData; + + auto call_awaiter( + this auto& awaiter, + auto events, + const auto& baseEventData, + std::invocable<> auto call + + ) -> std::invoke_result_t + { + return call_traced_method( + awaiter.traced_awaiter::trace_sink(), + events, + awaiter.m_sourceLocation, + baseEventData.with( + events::event_data + { + .Awaiter = static_cast(std::addressof(awaiter.awaiter())), + .TracedAwaiter = std::addressof(awaiter), + .AwaiterType = AwaiterType{}, + }), + call); + } + + decltype(auto) await_ready( + this auto& self + ) noexcept(noexcept(self.awaiter_wrapper::await_ready())) + { + return self.traced_awaiter::call_awaiter( + detail::method_events_group + { + events::await_ready_begin{}, + events::await_ready_result{}, + events::await_ready_exception{}, + }, + self.get_event_data( + events::empty_arguments), + [&]() -> decltype(auto) + { + return self.awaiter_wrapper::await_ready(); + } + ); + } + + template< + typename Arg + > + decltype(auto) await_suspend( + this auto& self, + Arg&& arg + ) noexcept(noexcept(self.awaiter_wrapper::await_suspend(std::forward(arg)))) + { + return self.traced_awaiter::call_awaiter( + detail::method_events_group + { + events::await_suspend_begin{}, + events::await_suspend_result{}, + events::await_suspend_exception{}, + }, + self.get_event_data( + make_arguments(arg)), + [&]() -> decltype(auto) + { + return self.awaiter_wrapper::await_suspend( + std::forward(arg)); + } + ); + } + + decltype(auto) await_resume( + this auto& self + ) noexcept(noexcept(self.awaiter_wrapper::await_resume())) + { + return self.traced_awaiter::call_awaiter( + detail::method_events_group + { + events::await_resume_begin{}, + events::await_resume_result{}, + events::await_resume_exception{}, + }, + self.get_event_data( + events::empty_arguments), + [&]() -> decltype(auto) + { + return self.awaiter_wrapper::await_resume(); + } + ); + } + + traced_awaiter( + std::source_location sourceLocation, + std::invocable auto awaiterFunction, + AwaiterType, + trace_sink_accessor traceSinkAccessor, + BaseEventData baseEventData + ) + : + trace_sink_accessor{ traceSinkAccessor }, + m_sourceLocation{ sourceLocation }, + awaiter_wrapper{ std::move(awaiterFunction) }, + m_baseEventData{ std::move(baseEventData) } + { + } + + template< + typename ... Args + > + auto get_event_data( + this auto& awaiter, + const auto& methodArguments + ) + { + return awaiter.m_baseEventData.with( + events::event_data + { + .Awaiter = &awaiter, + .MethodArguments = methodArguments, + }); + } +}; + +template< + is_trace_sink TraceSink, + std::invocable<> AwaiterFunction, + typename AwaiterType, + typename BaseEventData +> +traced_awaiter( + std::source_location, + AwaiterFunction, + AwaiterType, + detail::trace_sink_accessor, + BaseEventData +) -> traced_awaiter< + TraceSink, + std::invoke_result_t, + AwaiterType, + BaseEventData +>; + + +namespace detail +{ + +// This class stored the trace sink and produces non-result-specific tracing events. +PHANTOM_COROUTINES_MODULE_EXPORT +template< + is_trace_sink TraceSink +> +class traced_promise_trace_sink_storage +{ + template< + typename Declaration + > + struct traced_promise_trace_sink_accessor; + +public: + using trace_sink_type = TraceSink; + +public: + trace_sink_type m_traceSink; + + template< + typename ... Args + > + requires + is_constructible_from_promise_arguments + traced_promise_trace_sink_storage( + Args&& ... args + ) : + m_traceSink(std::forward(args)...) + { + } + + // Allow default construction of the trace sink when arguments are provided. + template< + typename ... Args + > + requires + is_default_constructible_from_promise_arguments + traced_promise_trace_sink_storage( + Args&& ... args + ) : + m_traceSink() + { + } + + auto get_event_data( + this auto& promise + ) + { + return events::event_data + { + .Promise = &promise.get_underlying_promise(), + .TracedPromise = &promise, + }; + } + + decltype(auto) call_promise_method( + this auto& promise, + auto events, + std::source_location sourceLocation, + const auto& baseEventData, + std::invocable auto call + ) + { + return call_traced_method( + promise.traced_promise_trace_sink_storage::m_traceSink, + events, + sourceLocation, + baseEventData.with( + promise.get_event_data()), + call); + } +}; + +// This class embodies constructors for traced promises. +template< + is_trace_sink TraceSink, + typename BasePromise +> +class traced_promise_construction + : + public traced_promise_trace_sink_storage, + public derived_promise +{ +public: + template< + typename ... Args + > + requires + std::constructible_from, Args&&...> + && + std::constructible_from, Args&&...> + traced_promise_construction( + Args&& ... args + ) + : + traced_promise_construction::traced_promise_trace_sink_storage(std::forward(args)...), + traced_promise_construction::derived_promise(std::forward(args)... ) + { + } +}; + +// This class provides an implementation of yield_value for a promise +// if it has a yield_value implementation. +// The main template does not provide yield_value. +template< + is_trace_sink TraceSink, + typename BasePromise +> +class traced_promise_yield_value + : + public traced_promise_construction +{ +public: + using traced_promise_yield_value::traced_promise_construction::traced_promise_construction; +}; + +// The specialization for promises that have yield_value. +template< + is_trace_sink TraceSink, + has_yield_value BasePromise +> +class traced_promise_yield_value< + TraceSink, + BasePromise +> + : + public traced_promise_construction +{ +public: + using traced_promise_yield_value::traced_promise_construction::traced_promise_construction; + + // Trace an delegate the base class's yield_value implementation. + template< + typename TPromise + > + decltype(auto) yield_value( + this TPromise&& promise, + auto&& value, + std::source_location sourceLocation = std::source_location::current() + ) + { + return promise.traced_promise_yield_value::call_promise_method( + promise, + detail::method_events_group + { + events::yield_value_begin{}, + events::yield_value_result{}, + events::yield_value_exception{}, + }, + sourceLocation, + events::event_data + { + .MethodArguments = events::make_arguments(value), + }, + [&]() + { + return std::forward(promise).traced_promise_yield_value::yield_value( + std::forward(value)); + } + ); + } +}; + +template< + is_trace_sink TraceSink, + typename BasePromise +> +class traced_promise_return_value_or_void + : + public traced_promise_yield_value +{ + using traced_promise_return_value_or_void::traced_promise_yield_value::traced_promise_yield_value; + +public: + using traced_promise_return_value_or_void::traced_promise_yield_value::call_promise_method; + + template< + typename TPromise, + typename Value + > + void return_value( + this TPromise&& promise, + Value&& value, + std::source_location sourceLocation = std::source_location::current() + ) + { + std::forward(promise).call_promise_method( + detail::method_events_group + { + events::return_value_begin{}, + events::return_value_result{}, + events::return_value_exception{}, + }, + sourceLocation, + events::event_data + { + .MethodArguments = events::make_arguments(value), + }, + [&]() + { + std::forward(promise).traced_promise_return_value_or_void::traced_promise_yield_value::return_value( + std::forward(value)); + } + ); + } +}; + +template< + is_trace_sink TraceSink, + has_return_void BasePromise +> +class traced_promise_return_value_or_void< + TraceSink, + BasePromise +> + : + public traced_promise_yield_value +{ + using traced_promise_return_value_or_void::traced_promise_yield_value::traced_promise_yield_value; + +public: + using traced_promise_return_value_or_void::traced_promise_yield_value::call_promise_method; + + template< + typename TPromise + > + void return_void( + this TPromise&& promise, + std::source_location sourceLocation = std::source_location::current() + ) + { + std::forward(promise).call_promise_method( + detail::method_events_group + { + events::return_void_begin{}, + events::return_void_result{}, + events::return_void_exception{}, + }, + sourceLocation, + events::event_data + { + }, + [&]() + { + std::forward(promise).traced_promise_return_value_or_void::traced_promise_yield_value::return_void(); + } + ); + } +}; + +template< + is_trace_sink TraceSink, + typename BasePromise +> +using traced_promise_base = traced_promise_return_value_or_void; + +} // namespace detail + +// Use suppress_trace to suppress tracing of an awaitable. +// Example: +// co_await suppress_trace{ m_event.Wait() }; +PHANTOM_COROUTINES_MODULE_EXPORT +template< + typename Awaiter +> +struct suppress_trace +{ + Awaiter&& value; +}; + +// Use trace to send a value to the trace sink. +// Example: +// co_await trace{ my_trace_event_information{} }; +PHANTOM_COROUTINES_MODULE_EXPORT +struct trace +{ +}; + +PHANTOM_COROUTINES_MODULE_EXPORT +template< + is_trace_sink TraceSink, + is_extensible_promise BasePromise +> +class traced_promise + : + public detail::traced_promise_base +{ +public: + using traced_promise_base = detail::traced_promise_base; + using traced_promise_base::call_promise_method; + using traced_promise_base::get_event_data; + using traced_promise_base::m_traceSink; + + template< + typename Declaration + > + struct traced_promise_trace_sink_accessor; + +public: + template< + typename ... Args + > + traced_promise( + Args&& ... args + ) + : + traced_promise_base{ std::forward(args)... } + { + m_traceSink( + events::basic_event( + std::source_location::current(), + events::create_promise{}, + events::event_data + { + .Promise = &this->get_underlying_promise(), + .TracedPromise = this, + .PromiseCreationArguments = events::make_arguments(args...), + })); + } + + ~traced_promise() + { + m_traceSink( + basic_event( + std::source_location::current(), + events::destroy_promise{}, + get_event_data() + )); + } + + template< + typename TPromise + > + auto initial_suspend( + this TPromise& promise, + std::source_location sourceLocation = std::source_location::current() + ) + { + return traced_awaiter + { + sourceLocation, + [&]() { return promise.traced_promise_base::initial_suspend(); }, + events::initial_suspend_awaiter_type{}, + detail::trace_sink_accessor + { + promise.traced_promise::m_traceSink + }, + promise.get_event_data() + }; + } + + template< + typename TPromise + > + auto final_suspend( + this TPromise& promise, + std::source_location sourceLocation = std::source_location::current() + ) noexcept + { + return traced_awaiter + { + sourceLocation, + [&]() { return promise.traced_promise_base::final_suspend(); }, + events::final_suspend_awaiter_type{}, + detail::trace_sink_accessor + { + promise.traced_promise::m_traceSink + }, + promise.get_event_data() + }; + } + + template< + typename TPromise + > + void unhandled_exception( + this TPromise& promise, + std::source_location sourceLocation = std::source_location::current() + ) + { + promise.call_promise_method( + detail::method_events_group + { + events::unhandled_exception_begin{}, + events::unhandled_exception_result{}, + events::unhandled_exception_exception{}, + }, + sourceLocation, + events::event_data{}, + [&]() + { + return promise.traced_promise_base::unhandled_exception(); + } + ); + } + + auto await_transform( + this auto& promise, + auto&& awaiter, + std::source_location sourceLocation = std::source_location::current() + ) + { + return traced_awaiter + { + sourceLocation, + [&]() -> decltype(auto) + { + return promise.traced_promise_base::await_transform( + std::forward(awaiter)); + }, + events::co_await_awaiter_type{}, + detail::trace_sink_accessor + { + promise.traced_promise::m_traceSink + }, + promise.get_event_data() + }; + } + + template< + typename Awaiter + > decltype(auto) await_transform( + this auto& promise, + const suppress_trace& noTraceAwaiter + ) + { + return promise.traced_promise_base::await_transform( + noTraceAwaiter.value + ); + } + + suspend_never await_transform( + this auto& promise, + std::derived_from auto& traceEvent + ) + { + promise.m_traceSink( + traceEvent); + return suspend_never{}; + } + + template< + typename This + > + auto& get_underlying_promise( + this This& self) + { + if constexpr (std::is_const_v) + { + return static_cast(self); + } + else + { + return static_cast(self); + } + } +}; + +PHANTOM_COROUTINES_MODULE_EXPORT +namespace filters +{ + +struct filter; + +template< + typename T +> +concept is_filter = std::derived_from; + +template< + is_filter Left, + is_filter Right +> +struct and_filter; + +template< + is_filter Left, + is_filter Right +> +struct or_filter; + +template< + is_filter Filter +> +struct not_filter; + +struct filter +{ + constexpr std::false_type operator()(const auto&) const noexcept { + return {}; + } + + template< + is_filter Left, + is_filter Right + > + friend constexpr auto operator&&( + Left left, + Right right + ) noexcept + { + return and_filter{ left, right }; + } + + template< + is_filter Left, + is_filter Right + > + friend constexpr auto operator||( + Left left, + Right right + ) noexcept + { + return or_filter{ left, right }; + } + + template< + is_filter Filter + > + friend constexpr auto operator!( + Filter filter + ) noexcept + { + return not_filter{ filter }; + } +}; + +template< + is_filter Left, + is_filter Right +> +struct and_filter : filter +{ + Left left; + Right right; + + constexpr bool operator()( + const auto& event + ) const noexcept + { + using leftType = decltype(left(event)); + using rightType = decltype(right(event)); + + if constexpr ( + std::same_as + || + std::same_as) + { + return std::false_type{}; + } + else if constexpr ( + std::same_as && + std::same_as) + { + return std::true_type{}; + } + else + { + return left(event) && right(event); + } + } +}; + +template< + is_filter Left, + is_filter Right +> +struct or_filter : filter +{ + Left left; + Right right; + + constexpr bool operator()( + const auto& event + ) const noexcept + { + using leftType = decltype(left(event)); + using rightType = decltype(right(event)); + + if constexpr ( + std::same_as + || + std::same_as) + { + return std::true_type{}; + } + else if constexpr ( + std::same_as && + std::same_as) + { + return std::false_type{}; + } + else + { + return left(event) && right(event); + } + } +}; + +template< + is_filter Filter +> +struct not_filter : filter +{ + Filter filter; + + constexpr auto operator()( + const auto& event) + { + using type = decltype(filter(event)); + if constexpr (std::same_as) + { + return std::false_type{}; + } + else if constexpr (std::same_as) + { + return std::true_type{}; + } + else + { + return !filter(event); + } + }; +}; + +struct any_event_fn : filter +{ + constexpr std::true_type operator()(const auto&) const noexcept { + return {}; + } +}; +constexpr any_event_fn any_event{}; + +template< + typename ... EventType +> +struct event_type_fn : filter +{ + using filter::operator(); + constexpr std::true_type operator()( + const auto& event + ) const noexcept + requires ( + events::is_event_type, EventType> || ... + ) + { + return {}; + } +}; + +template< + typename ... EventType +> +event_type_fn event_type; + +template< + typename AwaiterType +> +struct awaiter_type_fn : filter +{ + using filter::operator(); + constexpr std::true_type operator()( + const auto& event + ) const noexcept + requires std::same_as< + const decltype(event.EventData.AwaiterType)&, + const AwaiterType& + > + { + return {}; + } +}; + +template< + typename AwaiterType +> +constexpr awaiter_type_fn awaiter_type; + +template< + typename Event +> +struct check_constexpr_fn +{ + template< + is_filter Filter + > constexpr auto operator()( + Filter filter + ) const noexcept + { + return decltype(filter(std::declval())){}; + } +}; + +// Return a trace sink that traces return values for events that are selected. +template< + typename TraceSink +> +constexpr auto trace_return_value( + TraceSink traceSink +) +{ + return composed_function + { + traceSink, + [](const events::should_trace_return_value&) + { + return std::true_type{}; + } + }; +} + +template< + typename Event +> constexpr check_constexpr_fn check_constexpr{}; + +constexpr auto constant_filtered_trace_sink( + auto filter, + auto&& traceSink +) +{ + return [traceSink = std::forward(traceSink), filter](const auto& event) + { + if constexpr (filter(event)) + { + traceSink(event); + } + }; +} + +constexpr auto runtime_filtered_trace_sink( + auto filter, + auto&& traceSink +) +{ + return [traceSink = std::forward(traceSink), filter](const auto& event) + { + if (filter(event)) + { + traceSink(event); + } + }; +} + +// namespace filters +} + +// namespace tracing +} + + +// namespace Phantom::Coroutines +} + +#endif From d573eb4e9c8abadbe689c54deeb7398f39b6559b Mon Sep 17 00:00:00 2001 From: Joshua Rowe Date: Tue, 7 Apr 2026 10:55:40 -0700 Subject: [PATCH 38/46] Add a BUILDING.md file and copilot instructions --- .github/copilot-instructions.md | 54 ++++++++++++++++++ BUILDING.md | 99 +++++++++++++++++++++++++++++++++ CMakePresets.json | 26 +++++++++ 3 files changed, 179 insertions(+) create mode 100644 .github/copilot-instructions.md create mode 100644 BUILDING.md diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..83403d9 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,54 @@ +# Copilot Instructions + +## Build Configuration + +**Always use debug builds when developing and testing.** Use the appropriate debug preset for your platform: + +- **MSVC on Windows:** `msvc-windows-x64-debug` +- **Clang on Windows:** `clang-windows-x64-debug` (or `clang-windows-x64-debug-local` if using `CMakeUserPresets.json`) +- **Clang on Linux:** `clang-linux-x64-debug` (or `clang-linux-x64-debug-local` if using `CMakeUserPresets.json`) + +Debug builds enable AddressSanitizer (`/fsanitize=address`) on MSVC, which helps catch memory errors during development. + +**All build and test commands must be run from a Visual Studio 2026 Developer PowerShell session.** Load it with: + +```powershell +Import-Module "C:\Program Files\Microsoft Visual Studio\18\Enterprise\Common7\Tools\Microsoft.VisualStudio.DevShell.dll" +Enter-VsDevShell -VsInstallPath "C:\Program Files\Microsoft Visual Studio\18\Enterprise" -Arch amd64 -SkipAutomaticLocation +``` + +See [BUILDING.md](../BUILDING.md) for full build instructions. + +## Project Overview + +Phantom.Coroutines is a C++23 coroutine library with two implementation options: + +- **Header-only:** include headers from `Phantom.Coroutines/include/` +- **C++ Modules:** build and link against the `Phantom.Coroutines.Modules` CMake target + +Tests are in `Phantom.Coroutines.Test/` and cppcoro-compatibility tests in `Phantom.Coroutines.cppcoro.Test/`. + +## Testing + +### During development (iterative) + +Build and run only the C++ modules implementation and its tests. This is faster and catches most issues: + +``` +cmake --build --preset msvc-windows-x64-debug --target Phantom.Coroutines.Modules.Test +.\out\build\msvc-windows-x64-debug\Phantom.Coroutines.Test\Phantom.Coroutines.Modules.Test.exe +``` + +### When a task is complete (full suite) + +Build everything and run all three test executables: + +``` +cmake --build --preset msvc-windows-x64-debug +.\out\build\msvc-windows-x64-debug\Phantom.Coroutines.Test\Phantom.Coroutines.Test.exe +.\out\build\msvc-windows-x64-debug\Phantom.Coroutines.Test\Phantom.Coroutines.Modules.Test.exe +.\out\build\msvc-windows-x64-debug\Phantom.Coroutines.Test\Phantom.Coroutines.SingleModule.Test.exe +.\out\build\msvc-windows-x64-debug\Phantom.Coroutines.cppcoro.Test\Phantom.Coroutines.cppcoro.Test.exe +``` + +All PRs must include unit tests. Follow the existing naming style: descriptive test names with single-purpose assertions. diff --git a/BUILDING.md b/BUILDING.md new file mode 100644 index 0000000..2def7bc --- /dev/null +++ b/BUILDING.md @@ -0,0 +1,99 @@ +# Building Phantom.Coroutines + +Phantom.Coroutines uses [CMake](https://cmake.org/) (3.30+) with [vcpkg](https://vcpkg.io/) for dependency management and [Ninja](https://ninja-build.org/) as the build generator. + +## Prerequisites + +- CMake 3.30 or later +- Ninja +- vcpkg (set `VCPKG_ROOT` environment variable to your vcpkg installation) +- A supported compiler: + - **Windows:** MSVC (Visual Studio 2026 or later) or Clang + - **Linux:** Clang 21 + +## Configure + +Use a CMake preset to configure. For development, prefer a **debug** preset: + +``` +cmake --preset msvc-windows-x64-debug +``` + +Available presets (defined in `CMakePresets.json`): + +| Preset | Platform | Compiler | Config | +|---|---|---|---| +| `msvc-windows-x64-debug` | Windows | MSVC | Debug | +| `msvc-windows-x64-release` | Windows | MSVC | RelWithDebInfo | +| `clang-windows-x64-debug` | Windows | Clang | Debug | +| `clang-windows-x64-release` | Windows | Clang | RelWithDebInfo | +| `clang-linux-x64-debug` | Linux | Clang 21 | Debug | +| `clang-linux-x64-release` | Linux | Clang 21 | RelWithDebInfo | + +> **Note:** The release configuration is `RelWithDebInfo` (not `Release`). It enables link-time code generation (`/GL`/`/LTCG`) on MSVC. + +> **Note:** Debug builds on MSVC enable AddressSanitizer (`/fsanitize=address`). + +### Local overrides + +`CMakeUserPresets.json` provides `*-local` variants of the Clang presets that hard-code local compiler paths. Copy and adapt as needed for your machine. + +## Build + +``` +cmake --build --preset msvc-windows-x64-debug +``` + +Replace the preset name to match your configured preset. + +## Run Tests + +``` +ctest --preset msvc-windows-x64-debug-unit +``` + +Test presets mirror the configure presets. Tests take roughly 5 seconds to run. + +## Visual Studio + +Open the repository folder in Visual Studio. It will detect `CMakePresets.json` automatically. Select a preset from the configuration drop-down and build/run tests from the IDE. + +## Building with VS2026 PowerShell + +From a **Visual Studio 2026 Developer PowerShell** session, cmake, ninja, cl, and the Windows SDK are all on `PATH` and the MSVC environment variables (`INCLUDE`, `LIB`) are set correctly. Use the VS Developer PowerShell shortcut, or load it yourself: + +```powershell +Import-Module "C:\Program Files\Microsoft Visual Studio\18\Enterprise\Common7\Tools\Microsoft.VisualStudio.DevShell.dll" +Enter-VsDevShell -VsInstallPath "C:\Program Files\Microsoft Visual Studio\18\Enterprise" -Arch amd64 -SkipAutomaticLocation +``` + +Then configure (only needed once, or after clearing the cache): + +```powershell +cmake --preset msvc-windows-x64-debug +``` + +Build the C++ modules test target (iterative development): + +```powershell +cmake --build --preset msvc-windows-x64-debug --target Phantom.Coroutines.Modules.Test +.\out\build\msvc-windows-x64-debug\Phantom.Coroutines.Test\Phantom.Coroutines.Modules.Test.exe +``` + +Full build and test suite (run when done with a task): + +```powershell +cmake --build --preset msvc-windows-x64-debug +.\out\build\msvc-windows-x64-debug\Phantom.Coroutines.Test\Phantom.Coroutines.Test.exe +.\out\build\msvc-windows-x64-debug\Phantom.Coroutines.Test\Phantom.Coroutines.Modules.Test.exe +.\out\build\msvc-windows-x64-debug\Phantom.Coroutines.Test\Phantom.Coroutines.SingleModule.Test.exe +.\out\build\msvc-windows-x64-debug\Phantom.Coroutines.cppcoro.Test\Phantom.Coroutines.cppcoro.Test.exe +``` + +> **Note:** Use the test executables directly rather than `ctest`, as ctest spawns each test case as a separate process which is slow with AddressSanitizer enabled. + +> **Note:** If configure fails with `Could NOT find GTest`, delete `out\build\msvc-windows-x64-debug\CMakeCache.txt` and re-run configure. This can happen when the build directory was previously configured with a different toolchain. + +## C++ Modules + +The `Phantom.Coroutines.Modules` target builds C++ module support. Module linking is disabled for Clang builds (`DISABLE_LINK_MODULES=ON`) and can be disabled entirely with `-DDISABLE_COMPILE_MODULES=ON`. diff --git a/CMakePresets.json b/CMakePresets.json index 573ead5..25e90c3 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -123,6 +123,32 @@ } } ], + "buildPresets": [ + { + "name": "msvc-windows-x64-debug", + "configurePreset": "msvc-windows-x64-debug" + }, + { + "name": "msvc-windows-x64-release", + "configurePreset": "msvc-windows-x64-release" + }, + { + "name": "clang-windows-x64-debug", + "configurePreset": "clang-windows-x64-debug" + }, + { + "name": "clang-windows-x64-release", + "configurePreset": "clang-windows-x64-release" + }, + { + "name": "clang-linux-x64-debug", + "configurePreset": "clang-linux-x64-debug" + }, + { + "name": "clang-linux-x64-release", + "configurePreset": "clang-linux-x64-release" + } + ], "testPresets": [ { "name": "msvc-windows-x64-debug-unit", From 5ad6617389cbd384c180382125fa55fe163fde67 Mon Sep 17 00:00:00 2001 From: Joshua Rowe Date: Tue, 7 Apr 2026 11:41:00 -0700 Subject: [PATCH 39/46] Fix filtered trace sink query propagation and correct test expectations constant_filtered_trace_sink and runtime_filtered_trace_sink were discarding query results (should_trace_return_value, should_capture_promise_creation_arguments) by using void-returning lambdas and applying the event filter to queries where it does not apply. Rewrote both wrappers using composed_function with explicit decltype(auto)-returning overloads that bypass the filter entirely. Five test expectations in tracing_test.cpp incorrectly assumed MethodResult would be populated when using a void-returning trace sink. Since the sink's operator() returns void, can_trace_return_value is false and MethodResult stays empty_value. Removed those expectations. Also adds Documentation/tracing.md documenting the tracing design including the should_trace_return_value query protocol. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- Documentation/tracing.md | 244 ++++++++++++++++++ Phantom.Coroutines.Test/tracing_test.cpp | 5 - .../include/Phantom.Coroutines/tracing.h | 36 ++- 3 files changed, 274 insertions(+), 11 deletions(-) create mode 100644 Documentation/tracing.md diff --git a/Documentation/tracing.md b/Documentation/tracing.md new file mode 100644 index 0000000..862c31c --- /dev/null +++ b/Documentation/tracing.md @@ -0,0 +1,244 @@ +# Phantom.Coroutines Tracing + +The tracing system allows coroutine lifecycle events to be observed by attaching a *trace sink* to a +`traced_promise`. The traced promise emits strongly-typed events before and after every coroutine +lifecycle operation (initial/final suspend, `co_await`, `co_yield`, `co_return`, etc.) and delivers +them to the trace sink. + +All tracing types live in `Phantom::Coroutines::tracing`. + +--- + +## Trace Sinks + +A *trace sink* is any callable object. The `is_trace_sink` concept accepts every type: + +```cpp +template +concept is_trace_sink = true; +``` + +The trace sink is called with two distinct kinds of arguments: + +| Argument kind | Purpose | +|---|---| +| `const events::event<...>&` | A lifecycle event has occurred — record or act on it. | +| `const events::should_trace_return_value&` | A query asking whether to capture the return value for the next result event. | +| `const events::should_capture_promise_creation_arguments&` | A query asking whether to capture promise creation arguments. | + +The two query types are described in detail below. + +--- + +## Events + +Every event is an instance of `events::event`. The three +members of interest are: + +| Member | Type | Description | +|---|---|---| +| `SourceLocation` | `std::source_location` | Where the `co_await`/`co_yield`/etc. appears in source. | +| `EventType` | `events::event_type