xref: /oneTBB/src/tbb/scheduler_common.h (revision 7c79c824)
1 /*
2     Copyright (c) 2005-2023 Intel Corporation
3 
4     Licensed under the Apache License, Version 2.0 (the "License");
5     you may not use this file except in compliance with the License.
6     You may obtain a copy of the License at
7 
8         http://www.apache.org/licenses/LICENSE-2.0
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 
17 #ifndef _TBB_scheduler_common_H
18 #define _TBB_scheduler_common_H
19 
20 #include "oneapi/tbb/detail/_utils.h"
21 #include "oneapi/tbb/detail/_template_helpers.h"
22 #include "oneapi/tbb/detail/_task.h"
23 #include "oneapi/tbb/detail/_machine.h"
24 #include "oneapi/tbb/task_group.h"
25 #include "oneapi/tbb/cache_aligned_allocator.h"
26 #include "itt_notify.h"
27 #include "co_context.h"
28 #include "misc.h"
29 #include "governor.h"
30 
31 #ifndef __TBB_SCHEDULER_MUTEX_TYPE
32 #define __TBB_SCHEDULER_MUTEX_TYPE tbb::spin_mutex
33 #endif
34 // TODO: add conditional inclusion based on specified type
35 #include "oneapi/tbb/spin_mutex.h"
36 #include "oneapi/tbb/mutex.h"
37 
38 #if TBB_USE_ASSERT
39 #include <atomic>
40 #endif
41 
42 #include <cstdint>
43 #include <exception>
44 #include <memory> // unique_ptr
45 
46 //! Mutex type for global locks in the scheduler
47 using scheduler_mutex_type = __TBB_SCHEDULER_MUTEX_TYPE;
48 
49 #if _MSC_VER && !defined(__INTEL_COMPILER)
50     // Workaround for overzealous compiler warnings
51     // These particular warnings are so ubiquitous that no attempt is made to narrow
52     // the scope of the warnings.
53     #pragma warning (disable: 4100 4127 4312 4244 4267 4706)
54 #endif
55 
56 namespace tbb {
57 namespace detail {
58 namespace r1 {
59 
60 class arena;
61 class mail_inbox;
62 class mail_outbox;
63 class market;
64 class observer_proxy;
65 
66 enum task_stream_accessor_type { front_accessor = 0, back_nonnull_accessor };
67 template<task_stream_accessor_type> class task_stream;
68 
69 using isolation_type = std::intptr_t;
70 constexpr isolation_type no_isolation = 0;
71 
72 struct cache_aligned_deleter {
73     template <typename T>
operatorcache_aligned_deleter74     void operator() (T* ptr) const {
75         ptr->~T();
76         cache_aligned_deallocate(ptr);
77     }
78 };
79 
80 template <typename T>
81 using cache_aligned_unique_ptr = std::unique_ptr<T, cache_aligned_deleter>;
82 
83 template <typename T, typename ...Args>
make_cache_aligned_unique(Args &&...args)84 cache_aligned_unique_ptr<T> make_cache_aligned_unique(Args&& ...args) {
85     return cache_aligned_unique_ptr<T>(new (cache_aligned_allocate(sizeof(T))) T(std::forward<Args>(args)...));
86 }
87 
88 //------------------------------------------------------------------------
89 // Extended execute data
90 //------------------------------------------------------------------------
91 
92 //! Execute data used on a task dispatcher side, reflects a current execution state
93 struct execution_data_ext : d1::execution_data {
94     task_dispatcher* task_disp{};
95     isolation_type isolation{};
96     d1::wait_context* wait_ctx{};
97 };
98 
99 //------------------------------------------------------------------------
100 // Task accessor
101 //------------------------------------------------------------------------
102 
103 //! Interpretation of reserved task fields inside a task dispatcher
104 struct task_accessor {
105     static constexpr std::uint64_t proxy_task_trait = 1;
106     static constexpr std::uint64_t resume_task_trait = 2;
contexttask_accessor107     static d1::task_group_context*& context(d1::task& t) {
108         task_group_context** tgc = reinterpret_cast<task_group_context**>(&t.m_reserved[0]);
109         return *tgc;
110     }
isolationtask_accessor111     static isolation_type& isolation(d1::task& t) {
112         isolation_type* tag = reinterpret_cast<isolation_type*>(&t.m_reserved[2]);
113         return *tag;
114     }
set_proxy_traittask_accessor115     static void set_proxy_trait(d1::task& t) {
116         // TODO: refactor proxy tasks not to work on uninitialized memory.
117         //__TBB_ASSERT((t.m_version_and_traits & proxy_task_trait) == 0, nullptr);
118         t.m_version_and_traits |= proxy_task_trait;
119     }
is_proxy_tasktask_accessor120     static bool is_proxy_task(d1::task& t) {
121         return (t.m_version_and_traits & proxy_task_trait) != 0;
122     }
set_resume_traittask_accessor123     static void set_resume_trait(d1::task& t) {
124         __TBB_ASSERT((t.m_version_and_traits & resume_task_trait) == 0, nullptr);
125         t.m_version_and_traits |= resume_task_trait;
126     }
is_resume_tasktask_accessor127     static bool is_resume_task(d1::task& t) {
128         return (t.m_version_and_traits & resume_task_trait) != 0;
129     }
130 };
131 
132 //------------------------------------------------------------------------
133 //! Extended variant of the standard offsetof macro
134 /** The standard offsetof macro is not sufficient for TBB as it can be used for
135     POD-types only. The constant 0x1000 (not nullptr) is necessary to appease GCC. **/
136 #define __TBB_offsetof(class_name, member_name) \
137     ((ptrdiff_t)&(reinterpret_cast<class_name*>(0x1000)->member_name) - 0x1000)
138 
139 //! Returns address of the object containing a member with the given name and address
140 #define __TBB_get_object_ref(class_name, member_name, member_addr) \
141     (*reinterpret_cast<class_name*>((char*)member_addr - __TBB_offsetof(class_name, member_name)))
142 
143 //! Helper class for tracking floating point context and task group context switches
144 /** Assuming presence of an itt collector, in addition to keeping track of floating
145     point context, this class emits itt events to indicate begin and end of task group
146     context execution **/
147 template <bool report_tasks>
148 class context_guard_helper {
149     const d1::task_group_context* curr_ctx;
150     d1::cpu_ctl_env guard_cpu_ctl_env;
151     d1::cpu_ctl_env curr_cpu_ctl_env;
152 public:
context_guard_helper()153     context_guard_helper() : curr_ctx(nullptr) {
154         guard_cpu_ctl_env.get_env();
155         curr_cpu_ctl_env = guard_cpu_ctl_env;
156     }
~context_guard_helper()157     ~context_guard_helper() {
158         if (curr_cpu_ctl_env != guard_cpu_ctl_env)
159             guard_cpu_ctl_env.set_env();
160         if (report_tasks && curr_ctx)
161             ITT_TASK_END;
162     }
163     // The function is called from bypass dispatch loop on the hot path.
164     // Consider performance issues when refactoring.
set_ctx(const d1::task_group_context * ctx)165     void set_ctx(const d1::task_group_context* ctx) {
166         if (!ctx)
167             return;
168         const d1::cpu_ctl_env* ctl = reinterpret_cast<const d1::cpu_ctl_env*>(&ctx->my_cpu_ctl_env);
169         // Compare the FPU settings directly because the context can be reused between parallel algorithms.
170         if (*ctl != curr_cpu_ctl_env) {
171             curr_cpu_ctl_env = *ctl;
172             curr_cpu_ctl_env.set_env();
173         }
174         if (report_tasks && ctx != curr_ctx) {
175             // if task group context was active, report end of current execution frame.
176             if (curr_ctx)
177                 ITT_TASK_END;
178             // reporting begin of new task group context execution frame.
179             // using address of task group context object to group tasks (parent).
180             // id of task execution frame is nullptr and reserved for future use.
181             ITT_TASK_BEGIN(ctx, ctx->my_name, nullptr);
182             curr_ctx = ctx;
183         }
184     }
185 #if _WIN64
restore_default()186     void restore_default() {
187         if (curr_cpu_ctl_env != guard_cpu_ctl_env) {
188             guard_cpu_ctl_env.set_env();
189             curr_cpu_ctl_env = guard_cpu_ctl_env;
190         }
191     }
192 #endif // _WIN64
193 };
194 
195 #if (_WIN32 || _WIN64 || __unix__ || __APPLE__) && (__TBB_x86_32 || __TBB_x86_64)
196 #if _MSC_VER
197 #pragma intrinsic(__rdtsc)
198 #endif
machine_time_stamp()199 inline std::uint64_t machine_time_stamp() {
200 #if __INTEL_COMPILER
201     return _rdtsc();
202 #elif _MSC_VER
203     return __rdtsc();
204 #else
205     std::uint32_t hi, lo;
206     __asm__ __volatile__("rdtsc" : "=d"(hi), "=a"(lo));
207     return (std::uint64_t(hi) << 32) | lo;
208 #endif
209 }
210 
prolonged_pause_impl()211 inline void prolonged_pause_impl() {
212     // Assumption based on practice: 1000-2000 ticks seems to be a suitable invariant for the
213     // majority of platforms. Currently, skip platforms that define __TBB_STEALING_PAUSE
214     // because these platforms require very careful tuning.
215     std::uint64_t prev = machine_time_stamp();
216     const std::uint64_t finish = prev + 1000;
217     atomic_backoff backoff;
218     do {
219         backoff.bounded_pause();
220         std::uint64_t curr = machine_time_stamp();
221         if (curr <= prev)
222             // Possibly, the current logical thread is moved to another hardware thread or overflow is occurred.
223             break;
224         prev = curr;
225     } while (prev < finish);
226 }
227 #else
prolonged_pause_impl()228 inline void prolonged_pause_impl() {
229 #ifdef __TBB_ipf
230     static const long PauseTime = 1500;
231 #else
232     static const long PauseTime = 80;
233 #endif
234     // TODO IDEA: Update PauseTime adaptively?
235     machine_pause(PauseTime);
236 }
237 #endif
238 
prolonged_pause()239 inline void prolonged_pause() {
240 #if __TBB_WAITPKG_INTRINSICS_PRESENT
241     if (governor::wait_package_enabled()) {
242         std::uint64_t time_stamp = machine_time_stamp();
243         // _tpause function directs the processor to enter an implementation-dependent optimized state
244         // until the Time Stamp Counter reaches or exceeds the value specified in second parameter.
245         // Constant "1000" is ticks to wait for.
246         // TODO : Modify this parameter based on empirical study of benchmarks.
247         // First parameter 0 selects between a lower power (cleared) or faster wakeup (set) optimized state.
248         _tpause(0, time_stamp + 1000);
249     }
250     else
251 #endif
252     prolonged_pause_impl();
253 }
254 
255 // TODO: investigate possibility to work with number of CPU cycles
256 // because for different configurations this number of pauses + yields
257 // will be calculated in different amount of CPU cycles
258 // for example use rdtsc for it
259 class stealing_loop_backoff {
260     const int my_pause_threshold;
261     const int my_yield_threshold;
262     int my_pause_count;
263     int my_yield_count;
264 public:
265     // my_yield_threshold = 100 is an experimental value. Ideally, once we start calling __TBB_Yield(),
266     // the time spent spinning before calling out_of_work() should be approximately
267     // the time it takes for a thread to be woken up. Doing so would guarantee that we do
268     // no worse than 2x the optimal spin time. Or perhaps a time-slice quantum is the right amount.
stealing_loop_backoff(int num_workers,int yields_multiplier)269     stealing_loop_backoff(int num_workers, int yields_multiplier)
270         : my_pause_threshold{ 2 * (num_workers + 1) }
271 #if __APPLE__
272         // threshold value tuned separately for macOS due to high cost of sched_yield there
273         , my_yield_threshold{10 * yields_multiplier}
274 #else
275         , my_yield_threshold{100 * yields_multiplier}
276 #endif
277         , my_pause_count{}
278         , my_yield_count{}
279     {}
pause()280     bool pause() {
281         prolonged_pause();
282         if (my_pause_count++ >= my_pause_threshold) {
283             my_pause_count = my_pause_threshold;
284             d0::yield();
285             if (my_yield_count++ >= my_yield_threshold) {
286                 my_yield_count = my_yield_threshold;
287                 return true;
288             }
289         }
290         return false;
291     }
reset_wait()292     void reset_wait() {
293         my_pause_count = my_yield_count = 0;
294     }
295 };
296 
297 //------------------------------------------------------------------------
298 // Exception support
299 //------------------------------------------------------------------------
300 //! Task group state change propagation global epoch
301 /** Together with generic_scheduler::my_context_state_propagation_epoch forms
302     cross-thread signaling mechanism that allows to avoid locking at the hot path
303     of normal execution flow.
304 
305     When a descendant task group context is registered or unregistered, the global
306     and local epochs are compared. If they differ, a state change is being propagated,
307     and thus registration/deregistration routines take slower branch that may block
308     (at most one thread of the pool can be blocked at any moment). Otherwise the
309     control path is lock-free and fast. **/
310 extern std::atomic<std::uintptr_t> the_context_state_propagation_epoch;
311 
312 //! Mutex guarding state change propagation across task groups forest.
313 /** Also protects modification of related data structures. **/
314 typedef scheduler_mutex_type context_state_propagation_mutex_type;
315 extern context_state_propagation_mutex_type the_context_state_propagation_mutex;
316 
317 class tbb_exception_ptr {
318     std::exception_ptr my_ptr;
319 public:
320     static tbb_exception_ptr* allocate() noexcept;
321 
322     //! Destroys this objects
323     /** Note that objects of this type can be created only by the allocate() method. **/
324     void destroy() noexcept;
325 
326     //! Throws the contained exception .
327     void throw_self();
328 
329 private:
tbb_exception_ptr(const std::exception_ptr & src)330     tbb_exception_ptr(const std::exception_ptr& src) : my_ptr(src) {}
331 }; // class tbb_exception_ptr
332 
333 //------------------------------------------------------------------------
334 // Debugging support
335 //------------------------------------------------------------------------
336 
337 #if TBB_USE_ASSERT
338 static const std::uintptr_t venom = tbb::detail::select_size_t_constant<0xDEADBEEFU, 0xDDEEAADDDEADBEEFULL>::value;
339 
poison_value(std::uintptr_t & val)340 inline void poison_value(std::uintptr_t& val) { val = venom; }
341 
poison_value(std::atomic<std::uintptr_t> & val)342 inline void poison_value(std::atomic<std::uintptr_t>& val) { val.store(venom, std::memory_order_relaxed); }
343 
344 /** Expected to be used in assertions only, thus no empty form is defined. **/
is_alive(std::uintptr_t v)345 inline bool is_alive(std::uintptr_t v) { return v != venom; }
346 
347 /** Logically, this method should be a member of class task.
348     But we do not want to publish it, so it is here instead. */
assert_task_valid(const d1::task * t)349 inline void assert_task_valid(const d1::task* t) {
350     assert_pointer_valid(t);
351 }
352 #else /* !TBB_USE_ASSERT */
353 
354 /** In contrast to debug version poison_value() is a macro here because
355     the variable used as its argument may be undefined in release builds. **/
356 #define poison_value(g) ((void)0)
357 
assert_task_valid(const d1::task *)358 inline void assert_task_valid(const d1::task*) {}
359 
360 #endif /* !TBB_USE_ASSERT */
361 
362 struct suspend_point_type {
363 #if __TBB_RESUMABLE_TASKS
364     //! The arena related to this task_dispatcher
365     arena* m_arena{ nullptr };
366     //! The random for the resume task
367     FastRandom m_random;
368     //! The flag is raised when the original owner should return to this task dispatcher.
369     std::atomic<bool> m_is_owner_recalled{ false };
370     //! Inicates if the resume task should be placed to the critical task stream.
371     bool m_is_critical{ false };
372     //! Associated coroutine
373     co_context m_co_context;
374     //! Supend point before resume
375     suspend_point_type* m_prev_suspend_point{nullptr};
376 
377     // Possible state transitions:
378     // A -> S -> N -> A
379     // A -> N -> S -> N -> A
380     enum class stack_state {
381         active, // some thread is working with this stack
382         suspended, // no thread is working with this stack
383         notified // some thread tried to resume this stack
384     };
385 
386     //! The flag required to protect suspend finish and resume call
387     std::atomic<stack_state> m_stack_state{stack_state::active};
388 
resumesuspend_point_type389     void resume(suspend_point_type* sp) {
390         __TBB_ASSERT(m_stack_state.load(std::memory_order_relaxed) != stack_state::suspended, "The stack is expected to be active");
391 
392         sp->m_prev_suspend_point = this;
393 
394         // Do not access sp after resume
395         m_co_context.resume(sp->m_co_context);
396         __TBB_ASSERT(m_stack_state.load(std::memory_order_relaxed) != stack_state::active, nullptr);
397 
398         finilize_resume();
399     }
400 
finilize_resumesuspend_point_type401     void finilize_resume() {
402         m_stack_state.store(stack_state::active, std::memory_order_relaxed);
403         // Set the suspended state for the stack that we left. If the state is already notified, it means that
404         // someone already tried to resume our previous stack but failed. So, we need to resume it.
405         // m_prev_suspend_point might be nullptr when destroying co_context based on threads
406         if (m_prev_suspend_point && m_prev_suspend_point->m_stack_state.exchange(stack_state::suspended) == stack_state::notified) {
407             r1::resume(m_prev_suspend_point);
408         }
409         m_prev_suspend_point = nullptr;
410     }
411 
try_notify_resumesuspend_point_type412     bool try_notify_resume() {
413         // Check that stack is already suspended. Return false if not yet.
414         return m_stack_state.exchange(stack_state::notified) == stack_state::suspended;
415     }
416 
recall_ownersuspend_point_type417     void recall_owner() {
418         __TBB_ASSERT(m_stack_state.load(std::memory_order_relaxed) == stack_state::suspended, nullptr);
419         m_stack_state.store(stack_state::notified, std::memory_order_relaxed);
420         m_is_owner_recalled.store(true, std::memory_order_release);
421     }
422 
423     struct resume_task final : public d1::task {
424         task_dispatcher& m_target;
resume_tasksuspend_point_type::final425         explicit resume_task(task_dispatcher& target) : m_target(target) {
426             task_accessor::set_resume_trait(*this);
427         }
428         d1::task* execute(d1::execution_data& ed) override;
cancelsuspend_point_type::final429         d1::task* cancel(d1::execution_data&) override {
430             __TBB_ASSERT(false, "The resume task cannot be canceled");
431             return nullptr;
432         }
433     } m_resume_task;
434 
435     suspend_point_type(arena* a, std::size_t stack_size, task_dispatcher& target);
436 #endif /*__TBB_RESUMABLE_TASKS */
437 };
438 
439 #if _MSC_VER && !defined(__INTEL_COMPILER)
440 // structure was padded due to alignment specifier
441 #pragma warning( push )
442 #pragma warning( disable: 4324 )
443 #endif
444 
alignas(max_nfs_size)445 class alignas (max_nfs_size) task_dispatcher {
446 public:
447     // TODO: reconsider low level design to better organize dependencies and files.
448     friend class thread_data;
449     friend class arena_slot;
450     friend class nested_arena_context;
451     friend class delegated_task;
452     friend struct base_waiter;
453 
454     //! The list of possible post resume actions.
455     enum class post_resume_action {
456         invalid,
457         register_waiter,
458         cleanup,
459         notify,
460         none
461     };
462 
463     //! The data of the current thread attached to this task_dispatcher
464     thread_data* m_thread_data{ nullptr };
465 
466     //! The current execution data
467     execution_data_ext m_execute_data_ext;
468 
469     //! Properties
470     struct properties {
471         bool outermost{ true };
472         bool fifo_tasks_allowed{ true };
473         bool critical_task_allowed{ true };
474     } m_properties;
475 
476     //! Position in the call stack when stealing is still allowed.
477     std::uintptr_t m_stealing_threshold{};
478 
479     //! Suspend point (null if this task dispatcher has been never suspended)
480     suspend_point_type* m_suspend_point{ nullptr };
481 
482     //! Attempt to get a task from the mailbox.
483     /** Gets a task only if it has not been executed by its sender or a thief
484         that has stolen it from the sender's task pool. Otherwise returns nullptr.
485         This method is intended to be used only by the thread extracting the proxy
486         from its mailbox. (In contrast to local task pool, mailbox can be read only
487         by its owner). **/
488     d1::task* get_mailbox_task(mail_inbox& my_inbox, execution_data_ext& ed, isolation_type isolation);
489 
490     d1::task* get_critical_task(d1::task*, execution_data_ext&, isolation_type, bool);
491 
492     template <bool ITTPossible, typename Waiter>
493     d1::task* receive_or_steal_task(thread_data& tls, execution_data_ext& ed, Waiter& waiter,
494                                 isolation_type isolation, bool outermost, bool criticality_absence);
495 
496     template <bool ITTPossible, typename Waiter>
497     d1::task* local_wait_for_all(d1::task * t, Waiter& waiter);
498 
499     task_dispatcher(const task_dispatcher&) = delete;
500 
501     bool can_steal();
502 public:
503     task_dispatcher(arena* a);
504 
505     ~task_dispatcher() {
506         if (m_suspend_point) {
507             m_suspend_point->~suspend_point_type();
508             cache_aligned_deallocate(m_suspend_point);
509         }
510         poison_pointer(m_thread_data);
511         poison_pointer(m_suspend_point);
512     }
513 
514     template <typename Waiter>
515     d1::task* local_wait_for_all(d1::task* t, Waiter& waiter);
516 
517     bool allow_fifo_task(bool new_state) {
518         bool old_state = m_properties.fifo_tasks_allowed;
519         m_properties.fifo_tasks_allowed = new_state;
520         return old_state;
521     }
522 
523     isolation_type set_isolation(isolation_type isolation) {
524         isolation_type prev = m_execute_data_ext.isolation;
525         m_execute_data_ext.isolation = isolation;
526         return prev;
527     }
528 
529     thread_data& get_thread_data() {
530         __TBB_ASSERT(m_thread_data, nullptr);
531         return *m_thread_data;
532     }
533 
534     static void execute_and_wait(d1::task* t, d1::wait_context& wait_ctx, d1::task_group_context& w_ctx);
535 
536     void set_stealing_threshold(std::uintptr_t stealing_threshold) {
537         bool assert_condition = (stealing_threshold == 0 && m_stealing_threshold != 0) ||
538                                 (stealing_threshold != 0 && m_stealing_threshold == 0);
539         __TBB_ASSERT_EX( assert_condition, nullptr );
540         m_stealing_threshold = stealing_threshold;
541     }
542 
543     d1::task* get_inbox_or_critical_task(execution_data_ext&, mail_inbox&, isolation_type, bool);
544     d1::task* get_stream_or_critical_task(execution_data_ext&, arena&, task_stream<front_accessor>&,
545                                       unsigned& /*hint_for_stream*/, isolation_type,
546                                       bool /*critical_allowed*/);
547     d1::task* steal_or_get_critical(execution_data_ext&, arena&, unsigned /*arena_index*/, FastRandom&,
548                                 isolation_type, bool /*critical_allowed*/);
549 
550 #if __TBB_RESUMABLE_TASKS
551     /* [[noreturn]] */ void co_local_wait_for_all() noexcept;
552     void suspend(suspend_callback_type suspend_callback, void* user_callback);
553     void internal_suspend();
554     void do_post_resume_action();
555 
556     bool resume(task_dispatcher& target);
557     suspend_point_type* get_suspend_point();
558     void init_suspend_point(arena* a, std::size_t stack_size);
559     friend void internal_resume(suspend_point_type*);
560     void recall_point();
561 #endif /* __TBB_RESUMABLE_TASKS */
562 };
563 
564 #if _MSC_VER && !defined(__INTEL_COMPILER)
565 #pragma warning( pop )
566 #endif
567 
calculate_stealing_threshold(std::uintptr_t base,std::size_t stack_size)568 inline std::uintptr_t calculate_stealing_threshold(std::uintptr_t base, std::size_t stack_size) {
569     __TBB_ASSERT(stack_size != 0, "Stack size cannot be zero");
570     __TBB_ASSERT(base > stack_size / 2, "Stack anchor calculation overflow");
571     return base - stack_size / 2;
572 }
573 
574 struct task_group_context_impl {
575     static void destroy(d1::task_group_context&);
576     static void initialize(d1::task_group_context&);
577     static void register_with(d1::task_group_context&, thread_data*);
578     static void bind_to_impl(d1::task_group_context&, thread_data*);
579     static void bind_to(d1::task_group_context&, thread_data*);
580     static void propagate_task_group_state(d1::task_group_context&, std::atomic<uint32_t> d1::task_group_context::*, d1::task_group_context&, uint32_t);
581     static bool cancel_group_execution(d1::task_group_context&);
582     static bool is_group_execution_cancelled(const d1::task_group_context&);
583     static void reset(d1::task_group_context&);
584     static void capture_fp_settings(d1::task_group_context&);
585     static void copy_fp_settings(d1::task_group_context& ctx, const d1::task_group_context& src);
586 };
587 
588 
589 //! Forward declaration for scheduler entities
590 bool gcc_rethrow_exception_broken();
591 void fix_broken_rethrow();
592 //! Forward declaration: throws std::runtime_error with what() returning error_code description prefixed with aux_info
593 void handle_perror(int error_code, const char* aux_info);
594 
595 } // namespace r1
596 } // namespace detail
597 } // namespace tbb
598 
599 #endif /* _TBB_scheduler_common_H */
600