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