xref: /openbmc/sdbusplus/include/sdbusplus/async/stdexec/__detail/__debug.hpp (revision 10d0b4b7d1498cfd5c3d37edea271a54d1984e41)
1 /*
2  * Copyright (c) 2021-2024 NVIDIA Corporation
3  *
4  * Licensed under the Apache License Version 2.0 with LLVM Exceptions
5  * (the "License"); you may not use this file except in compliance with
6  * the License. You may obtain a copy of the License at
7  *
8  *   https://llvm.org/LICENSE.txt
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 #pragma once
17 
18 #include "__execution_fwd.hpp"
19 
20 #include "__concepts.hpp"
21 #include "__completion_signatures.hpp"
22 #include "__diagnostics.hpp"
23 #include "__env.hpp"
24 #include "__meta.hpp"
25 #include "__senders_core.hpp"
26 
27 #include <exception> // IWYU pragma: keep for std::terminate
28 
29 namespace stdexec {
30   /////////////////////////////////////////////////////////////////////////////
31   // Some utilities for debugging senders
32   namespace __debug {
33     struct __is_debug_env_t : __query<__is_debug_env_t> {
querystdexec::__debug::__is_debug_env_t34       static constexpr auto query(forwarding_query_t) noexcept -> bool {
35         return true;
36       }
37     };
38 
39     template <class _Env>
40     using __debug_env_t = env<prop<__is_debug_env_t, bool>, _Env>;
41 
42     template <class _Env>
43     concept __is_debug_env = __callable<__is_debug_env_t, _Env>;
44 
45     struct __completion_signatures { };
46 
47 #if STDEXEC_MSVC()
48     // MSVCBUG https://developercommunity.visualstudio.com/t/Explicit-variable-template-specialisatio/10360032
49     // MSVCBUG https://developercommunity.visualstudio.com/t/Non-function-type-interpreted-as-functio/10447831
50 
51     template <class _Sig>
52     struct __normalize_sig;
53 
54     template <class _Tag, class... _Args>
55     struct __normalize_sig<_Tag(_Args...)> {
56       using __type = _Tag (*)(_Args&&...);
57     };
58 
59     template <class _Sig>
60     using __normalize_sig_t = __normalize_sig<_Sig>::__type;
61 #else
62     template <class _Sig>
63     extern int __normalize_sig;
64 
65     template <class _Tag, class... _Args>
66     extern _Tag (*__normalize_sig<_Tag(_Args...)>)(_Args&&...);
67 
68     template <class _Sig>
69     using __normalize_sig_t = decltype(__normalize_sig<_Sig>);
70 #endif
71 
72     template <class... _Sigs>
73     struct __valid_completions {
74       template <class... _Args>
75         requires __one_of<set_value_t (*)(_Args&&...), _Sigs...>
STDEXEC_ATTRIBUTEstdexec::__debug::__valid_completions76       STDEXEC_ATTRIBUTE(host, device)
77       void set_value(_Args&&...) noexcept {
78         STDEXEC_TERMINATE();
79       }
80 
81       template <class _Error>
82         requires __one_of<set_error_t (*)(_Error&&), _Sigs...>
STDEXEC_ATTRIBUTEstdexec::__debug::__valid_completions83       STDEXEC_ATTRIBUTE(host, device)
84       void set_error(_Error&&) noexcept {
85         STDEXEC_TERMINATE();
86       }
87 
STDEXEC_ATTRIBUTEstdexec::__debug::__valid_completions88       STDEXEC_ATTRIBUTE(host, device)
89       void set_stopped() noexcept
90         requires __one_of<set_stopped_t (*)(), _Sigs...>
91       {
92         STDEXEC_TERMINATE();
93       }
94     };
95 
96     template <class _CvrefSenderId, class _Env, class _Completions>
97     struct __debug_receiver {
98       using __t = __debug_receiver;
99       using __id = __debug_receiver;
100       using receiver_concept = receiver_t;
101     };
102 
103     template <class _CvrefSenderId, class _Env, class... _Sigs>
104     struct __debug_receiver<_CvrefSenderId, _Env, completion_signatures<_Sigs...>>
105       : __valid_completions<__normalize_sig_t<_Sigs>...> {
106       using __t = __debug_receiver;
107       using __id = __debug_receiver;
108       using receiver_concept = receiver_t;
109 
STDEXEC_ATTRIBUTEstdexec::__debug::__debug_receiver110       STDEXEC_ATTRIBUTE(host, device) auto get_env() const noexcept -> __debug_env_t<_Env> {
111         STDEXEC_TERMINATE();
112       }
113     };
114 
115     struct _COMPLETION_SIGNATURES_MISMATCH_ { };
116 
117     template <class _Sig>
118     struct _COMPLETION_SIGNATURE_ { };
119 
120     template <class... _Sigs>
121     struct _IS_NOT_ONE_OF_ { };
122 
123     template <class _Sender>
124     struct _SIGNAL_SENT_BY_SENDER_ { };
125 
126     template <class _Warning>
127     [[deprecated(
128       "The sender claims to send a particular set of completions,"
129       " but in actual fact it completes with a result that is not"
STDEXEC_ATTRIBUTE(host,device)130       " one of the declared completion signatures.")]] STDEXEC_ATTRIBUTE(host, device) void _ATTENTION_() noexcept {
131     }
132 
133     template <class _Sig>
134     struct __invalid_completion {
135       struct __t {
136         template <class _CvrefSenderId, class _Env, class... _Sigs>
137         // BUGBUG this works around a recently (aug 2023) introduced regression in nvc++
138           requires(!__one_of<_Sig, _Sigs...>)
__tstdexec::__debug::__invalid_completion::__t139         __t(__debug_receiver<_CvrefSenderId, _Env, completion_signatures<_Sigs...>>&&) noexcept {
140           using _SenderId = __decay_t<_CvrefSenderId>;
141           using _Sender = stdexec::__t<_SenderId>;
142           using _What = _WARNING_<
143             _COMPLETION_SIGNATURES_MISMATCH_,
144             _COMPLETION_SIGNATURE_<_Sig>,
145             _IS_NOT_ONE_OF_<_Sigs...>,
146             _SIGNAL_SENT_BY_SENDER_<__name_of<_Sender>>
147           >;
148           __debug::_ATTENTION_<_What>();
149         }
150       };
151     };
152 
153     template <__completion_tag _Tag, class... _Args>
STDEXEC_ATTRIBUTE(host,device)154     STDEXEC_ATTRIBUTE(host, device)
155     void tag_invoke(_Tag, __t<__invalid_completion<_Tag(_Args...)>>, _Args&&...) noexcept {
156     }
157 
158     struct __debug_operation {
startstdexec::__debug::__debug_operation159       void start() & noexcept {
160       }
161     };
162 
163     ////////////////////////////////////////////////////////////////////////////
164     // `__debug_sender`
165     // ===============
166 
167     // Understanding why a particular sender doesn't connect to a particular
168     // receiver is nigh impossible in the current design due to limitations in
169     // how the compiler reports overload resolution failure in the presence of
170     // constraints. `__debug_sender` is a utility to assist with the process. It
171     // gives you the deep template instantiation backtrace that you need to
172     // understand where in a chain of senders the problem is occurring.
173 
174     // ```c++
175     // template <class _Sigs, class _Env = env<>, class _Sender>
176     //   void __debug_sender(_Sender&& __sndr, _Env = {});
177 
178     // template <class _Env = env<>, class _Sender>
179     //   void __debug_sender(_Sender&& __sndr, _Env = {});
180     // ```
181 
182     // **Usage:**
183 
184     // To find out where in a chain of senders a sender is failing to connect
185     // to a receiver, pass it to `__debug_sender`, optionally with an
186     // environment argument; e.g. `__debug_sender(sndr [, env])`
187 
188     // To find out why a sender will not connect to a receiver of a particular
189     // signature, specify the set of completion signatures as an explicit template
190     // argument that names an instantiation of `completion_signatures`; e.g.:
191     // `__debug_sender<completion_signatures<set_value_t(int)>>(sndr [, env])`.
192 
193     // **How it works:**
194 
195     // The `__debug_sender` function `connect`'s the sender to a
196     // `__debug_receiver`, whose environment is augmented with a special
197     // `__is_debug_env_t` query. An additional fall-back overload is added to
198     // the `connect` CPO that recognizes receivers whose environments respond to
199     // that query and lets them through. Then in a non-immediate context, it
200     // looks for a `tag_invoke(connect_t...)` overload for the input sender and
201     // receiver. This will recurse until it hits the `tag_invoke` call that is
202     // causing the failure.
203 
204     // At least with clang, this gives me a nice backtrace, at the bottom of
205     // which is the faulty `tag_invoke` overload with a mention of the
206     // constraint that failed.
207     template <class _Sigs, class _Env = env<>, class _Sender>
__debug_sender(_Sender && __sndr,const _Env &={})208     void __debug_sender(_Sender&& __sndr, const _Env& = {}) {
209       if constexpr (!__is_debug_env<_Env>) {
210         if constexpr (sender_in<_Sender, _Env>) {
211           using _Receiver = __debug_receiver<__cvref_id<_Sender>, _Env, _Sigs>;
212           using _Operation = connect_result_t<_Sender, _Receiver>;
213           //static_assert(receiver_of<_Receiver, _Sigs>);
214           if constexpr (!same_as<_Operation, __debug_operation>) {
215             if (sizeof(_Sender) == ~0u) { // never true
216               auto __op = connect(static_cast<_Sender&&>(__sndr), _Receiver{});
217               stdexec::start(__op);
218             }
219           }
220         } else {
221           stdexec::__diagnose_sender_concept_failure<_Sender, _Env>();
222         }
223       }
224     }
225 
226     template <class _Env = env<>, class _Sender>
__debug_sender(_Sender && __sndr,const _Env &={})227     void __debug_sender(_Sender&& __sndr, const _Env& = {}) {
228       if constexpr (!__is_debug_env<_Env>) {
229         if constexpr (sender_in<_Sender, _Env>) {
230           using _Sigs = __completion_signatures_of_t<_Sender, __debug_env_t<_Env>>;
231           using _Receiver = __debug_receiver<__cvref_id<_Sender>, _Env, _Sigs>;
232           if constexpr (!same_as<_Sigs, __debug::__completion_signatures>) {
233             using _Operation = connect_result_t<_Sender, _Receiver>;
234             //static_assert(receiver_of<_Receiver, _Sigs>);
235             if constexpr (!same_as<_Operation, __debug_operation>) {
236               if (sizeof(_Sender) == ~0ul) { // never true
237                 auto __op = connect(static_cast<_Sender&&>(__sndr), _Receiver{});
238                 stdexec::start(__op);
239               }
240             }
241           }
242         } else {
243           __diagnose_sender_concept_failure<_Sender, _Env>();
244         }
245       }
246     }
247   } // namespace __debug
248 
249   using __debug::__is_debug_env;
250   using __debug::__debug_sender;
251 } // namespace stdexec
252