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