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