/* * kmp_dispatch_hier.h -- hierarchical scheduling methods and data structures */ //===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #ifndef KMP_DISPATCH_HIER_H #define KMP_DISPATCH_HIER_H #include "kmp.h" #include "kmp_dispatch.h" // Layer type for scheduling hierarchy enum kmp_hier_layer_e { LAYER_THREAD = -1, LAYER_L1, LAYER_L2, LAYER_L3, LAYER_NUMA, LAYER_LOOP, LAYER_LAST }; // Convert hierarchy type (LAYER_L1, LAYER_L2, etc.) to C-style string static inline const char *__kmp_get_hier_str(kmp_hier_layer_e type) { switch (type) { case kmp_hier_layer_e::LAYER_THREAD: return "THREAD"; case kmp_hier_layer_e::LAYER_L1: return "L1"; case kmp_hier_layer_e::LAYER_L2: return "L2"; case kmp_hier_layer_e::LAYER_L3: return "L3"; case kmp_hier_layer_e::LAYER_NUMA: return "NUMA"; case kmp_hier_layer_e::LAYER_LOOP: return "WHOLE_LOOP"; case kmp_hier_layer_e::LAYER_LAST: return "LAST"; } KMP_ASSERT(0); // Appease compilers, should never get here return "ERROR"; } // Structure to store values parsed from OMP_SCHEDULE for scheduling hierarchy typedef struct kmp_hier_sched_env_t { int size; int capacity; enum sched_type *scheds; kmp_int32 *small_chunks; kmp_int64 *large_chunks; kmp_hier_layer_e *layers; // Append a level of the hierarchy void append(enum sched_type sched, kmp_int32 chunk, kmp_hier_layer_e layer) { if (capacity == 0) { scheds = (enum sched_type *)__kmp_allocate(sizeof(enum sched_type) * kmp_hier_layer_e::LAYER_LAST); small_chunks = (kmp_int32 *)__kmp_allocate(sizeof(kmp_int32) * kmp_hier_layer_e::LAYER_LAST); large_chunks = (kmp_int64 *)__kmp_allocate(sizeof(kmp_int64) * kmp_hier_layer_e::LAYER_LAST); layers = (kmp_hier_layer_e *)__kmp_allocate(sizeof(kmp_hier_layer_e) * kmp_hier_layer_e::LAYER_LAST); capacity = kmp_hier_layer_e::LAYER_LAST; } int current_size = size; KMP_DEBUG_ASSERT(current_size < kmp_hier_layer_e::LAYER_LAST); scheds[current_size] = sched; layers[current_size] = layer; small_chunks[current_size] = chunk; large_chunks[current_size] = (kmp_int64)chunk; size++; } // Sort the hierarchy using selection sort, size will always be small // (less than LAYER_LAST) so it is not necessary to use an nlog(n) algorithm void sort() { if (size <= 1) return; for (int i = 0; i < size; ++i) { int switch_index = i; for (int j = i + 1; j < size; ++j) { if (layers[j] < layers[switch_index]) switch_index = j; } if (switch_index != i) { kmp_hier_layer_e temp1 = layers[i]; enum sched_type temp2 = scheds[i]; kmp_int32 temp3 = small_chunks[i]; kmp_int64 temp4 = large_chunks[i]; layers[i] = layers[switch_index]; scheds[i] = scheds[switch_index]; small_chunks[i] = small_chunks[switch_index]; large_chunks[i] = large_chunks[switch_index]; layers[switch_index] = temp1; scheds[switch_index] = temp2; small_chunks[switch_index] = temp3; large_chunks[switch_index] = temp4; } } } // Free all memory void deallocate() { if (capacity > 0) { __kmp_free(scheds); __kmp_free(layers); __kmp_free(small_chunks); __kmp_free(large_chunks); scheds = NULL; layers = NULL; small_chunks = NULL; large_chunks = NULL; } size = 0; capacity = 0; } } kmp_hier_sched_env_t; extern int __kmp_dispatch_hand_threading; extern kmp_hier_sched_env_t __kmp_hier_scheds; // Sizes of layer arrays bounded by max number of detected L1s, L2s, etc. extern int __kmp_hier_max_units[kmp_hier_layer_e::LAYER_LAST + 1]; extern int __kmp_hier_threads_per[kmp_hier_layer_e::LAYER_LAST + 1]; extern int __kmp_dispatch_get_index(int tid, kmp_hier_layer_e type); extern int __kmp_dispatch_get_id(int gtid, kmp_hier_layer_e type); extern int __kmp_dispatch_get_t1_per_t2(kmp_hier_layer_e t1, kmp_hier_layer_e t2); extern void __kmp_dispatch_free_hierarchies(kmp_team_t *team); template struct kmp_hier_shared_bdata_t { typedef typename traits_t::signed_t ST; volatile kmp_uint64 val[2]; kmp_int32 status[2]; T lb[2]; T ub[2]; ST st[2]; dispatch_shared_info_template sh[2]; void zero() { val[0] = val[1] = 0; status[0] = status[1] = 0; lb[0] = lb[1] = 0; ub[0] = ub[1] = 0; st[0] = st[1] = 0; sh[0].u.s.iteration = sh[1].u.s.iteration = 0; } void set_next_hand_thread(T nlb, T nub, ST nst, kmp_int32 nstatus, kmp_uint64 index) { lb[1 - index] = nlb; ub[1 - index] = nub; st[1 - index] = nst; status[1 - index] = nstatus; } void set_next(T nlb, T nub, ST nst, kmp_int32 nstatus, kmp_uint64 index) { lb[1 - index] = nlb; ub[1 - index] = nub; st[1 - index] = nst; status[1 - index] = nstatus; sh[1 - index].u.s.iteration = 0; } kmp_int32 get_next_status(kmp_uint64 index) const { return status[1 - index]; } T get_next_lb(kmp_uint64 index) const { return lb[1 - index]; } T get_next_ub(kmp_uint64 index) const { return ub[1 - index]; } ST get_next_st(kmp_uint64 index) const { return st[1 - index]; } dispatch_shared_info_template volatile *get_next_sh(kmp_uint64 index) { return &(sh[1 - index]); } kmp_int32 get_curr_status(kmp_uint64 index) const { return status[index]; } T get_curr_lb(kmp_uint64 index) const { return lb[index]; } T get_curr_ub(kmp_uint64 index) const { return ub[index]; } ST get_curr_st(kmp_uint64 index) const { return st[index]; } dispatch_shared_info_template volatile *get_curr_sh(kmp_uint64 index) { return &(sh[index]); } }; /* * In the barrier implementations, num_active is the number of threads that are * attached to the kmp_hier_top_unit_t structure in the scheduling hierarchy. * bdata is the shared barrier data that resides on the kmp_hier_top_unit_t * structure. tdata is the thread private data that resides on the thread * data structure. * * The reset_shared() method is used to initialize the barrier data on the * kmp_hier_top_unit_t hierarchy structure * * The reset_private() method is used to initialize the barrier data on the * thread's private dispatch buffer structure * * The barrier() method takes an id, which is that thread's id for the * kmp_hier_top_unit_t structure, and implements the barrier. All threads wait * inside barrier() until all fellow threads who are attached to that * kmp_hier_top_unit_t structure have arrived. */ // Core barrier implementation // Can be used in a unit with between 2 to 8 threads template class core_barrier_impl { static inline kmp_uint64 get_wait_val(int num_active) { kmp_uint64 wait_val = 0LL; switch (num_active) { case 2: wait_val = 0x0101LL; break; case 3: wait_val = 0x010101LL; break; case 4: wait_val = 0x01010101LL; break; case 5: wait_val = 0x0101010101LL; break; case 6: wait_val = 0x010101010101LL; break; case 7: wait_val = 0x01010101010101LL; break; case 8: wait_val = 0x0101010101010101LL; break; default: // don't use the core_barrier_impl for more than 8 threads KMP_ASSERT(0); } return wait_val; } public: static void reset_private(kmp_int32 num_active, kmp_hier_private_bdata_t *tdata); static void reset_shared(kmp_int32 num_active, kmp_hier_shared_bdata_t *bdata); static void barrier(kmp_int32 id, kmp_hier_shared_bdata_t *bdata, kmp_hier_private_bdata_t *tdata); }; template void core_barrier_impl::reset_private(kmp_int32 num_active, kmp_hier_private_bdata_t *tdata) { tdata->num_active = num_active; tdata->index = 0; tdata->wait_val[0] = tdata->wait_val[1] = get_wait_val(num_active); } template void core_barrier_impl::reset_shared(kmp_int32 num_active, kmp_hier_shared_bdata_t *bdata) { bdata->val[0] = bdata->val[1] = 0LL; bdata->status[0] = bdata->status[1] = 0LL; } template void core_barrier_impl::barrier(kmp_int32 id, kmp_hier_shared_bdata_t *bdata, kmp_hier_private_bdata_t *tdata) { kmp_uint64 current_index = tdata->index; kmp_uint64 next_index = 1 - current_index; kmp_uint64 current_wait_value = tdata->wait_val[current_index]; kmp_uint64 next_wait_value = (current_wait_value ? 0 : get_wait_val(tdata->num_active)); KD_TRACE(10, ("core_barrier_impl::barrier(): T#%d current_index:%llu " "next_index:%llu curr_wait:%llu next_wait:%llu\n", __kmp_get_gtid(), current_index, next_index, current_wait_value, next_wait_value)); char v = (current_wait_value ? 0x1 : 0x0); (RCAST(volatile char *, &(bdata->val[current_index])))[id] = v; __kmp_wait(&(bdata->val[current_index]), current_wait_value, __kmp_eq USE_ITT_BUILD_ARG(NULL)); tdata->wait_val[current_index] = next_wait_value; tdata->index = next_index; } // Counter barrier implementation // Can be used in a unit with arbitrary number of active threads template class counter_barrier_impl { public: static void reset_private(kmp_int32 num_active, kmp_hier_private_bdata_t *tdata); static void reset_shared(kmp_int32 num_active, kmp_hier_shared_bdata_t *bdata); static void barrier(kmp_int32 id, kmp_hier_shared_bdata_t *bdata, kmp_hier_private_bdata_t *tdata); }; template void counter_barrier_impl::reset_private(kmp_int32 num_active, kmp_hier_private_bdata_t *tdata) { tdata->num_active = num_active; tdata->index = 0; tdata->wait_val[0] = tdata->wait_val[1] = (kmp_uint64)num_active; } template void counter_barrier_impl::reset_shared(kmp_int32 num_active, kmp_hier_shared_bdata_t *bdata) { bdata->val[0] = bdata->val[1] = 0LL; bdata->status[0] = bdata->status[1] = 0LL; } template void counter_barrier_impl::barrier(kmp_int32 id, kmp_hier_shared_bdata_t *bdata, kmp_hier_private_bdata_t *tdata) { volatile kmp_int64 *val; kmp_uint64 current_index = tdata->index; kmp_uint64 next_index = 1 - current_index; kmp_uint64 current_wait_value = tdata->wait_val[current_index]; kmp_uint64 next_wait_value = current_wait_value + tdata->num_active; KD_TRACE(10, ("counter_barrier_impl::barrier(): T#%d current_index:%llu " "next_index:%llu curr_wait:%llu next_wait:%llu\n", __kmp_get_gtid(), current_index, next_index, current_wait_value, next_wait_value)); val = RCAST(volatile kmp_int64 *, &(bdata->val[current_index])); KMP_TEST_THEN_INC64(val); __kmp_wait(&(bdata->val[current_index]), current_wait_value, __kmp_ge USE_ITT_BUILD_ARG(NULL)); tdata->wait_val[current_index] = next_wait_value; tdata->index = next_index; } // Data associated with topology unit within a layer // For example, one kmp_hier_top_unit_t corresponds to one L1 cache template struct kmp_hier_top_unit_t { typedef typename traits_t::signed_t ST; typedef typename traits_t::unsigned_t UT; kmp_int32 active; // number of topology units that communicate with this unit // chunk information (lower/upper bound, stride, etc.) dispatch_private_info_template hier_pr; kmp_hier_top_unit_t *hier_parent; // pointer to parent unit kmp_hier_shared_bdata_t hier_barrier; // shared barrier data for this unit kmp_int32 get_hier_id() const { return hier_pr.hier_id; } void reset_shared_barrier() { KMP_DEBUG_ASSERT(active > 0); if (active == 1) return; hier_barrier.zero(); if (active >= 2 && active <= 8) { core_barrier_impl::reset_shared(active, &hier_barrier); } else { counter_barrier_impl::reset_shared(active, &hier_barrier); } } void reset_private_barrier(kmp_hier_private_bdata_t *tdata) { KMP_DEBUG_ASSERT(tdata); KMP_DEBUG_ASSERT(active > 0); if (active == 1) return; if (active >= 2 && active <= 8) { core_barrier_impl::reset_private(active, tdata); } else { counter_barrier_impl::reset_private(active, tdata); } } void barrier(kmp_int32 id, kmp_hier_private_bdata_t *tdata) { KMP_DEBUG_ASSERT(tdata); KMP_DEBUG_ASSERT(active > 0); KMP_DEBUG_ASSERT(id >= 0 && id < active); if (active == 1) { tdata->index = 1 - tdata->index; return; } if (active >= 2 && active <= 8) { core_barrier_impl::barrier(id, &hier_barrier, tdata); } else { counter_barrier_impl::barrier(id, &hier_barrier, tdata); } } kmp_int32 get_next_status(kmp_uint64 index) const { return hier_barrier.get_next_status(index); } T get_next_lb(kmp_uint64 index) const { return hier_barrier.get_next_lb(index); } T get_next_ub(kmp_uint64 index) const { return hier_barrier.get_next_ub(index); } ST get_next_st(kmp_uint64 index) const { return hier_barrier.get_next_st(index); } dispatch_shared_info_template volatile *get_next_sh(kmp_uint64 index) { return hier_barrier.get_next_sh(index); } kmp_int32 get_curr_status(kmp_uint64 index) const { return hier_barrier.get_curr_status(index); } T get_curr_lb(kmp_uint64 index) const { return hier_barrier.get_curr_lb(index); } T get_curr_ub(kmp_uint64 index) const { return hier_barrier.get_curr_ub(index); } ST get_curr_st(kmp_uint64 index) const { return hier_barrier.get_curr_st(index); } dispatch_shared_info_template volatile *get_curr_sh(kmp_uint64 index) { return hier_barrier.get_curr_sh(index); } void set_next_hand_thread(T lb, T ub, ST st, kmp_int32 status, kmp_uint64 index) { hier_barrier.set_next_hand_thread(lb, ub, st, status, index); } void set_next(T lb, T ub, ST st, kmp_int32 status, kmp_uint64 index) { hier_barrier.set_next(lb, ub, st, status, index); } dispatch_private_info_template *get_my_pr() { return &hier_pr; } kmp_hier_top_unit_t *get_parent() { return hier_parent; } dispatch_private_info_template *get_parent_pr() { return &(hier_parent->hier_pr); } kmp_int32 is_active() const { return active; } kmp_int32 get_num_active() const { return active; } #ifdef KMP_DEBUG void print() { KD_TRACE( 10, (" kmp_hier_top_unit_t: active:%d pr:%p lb:%d ub:%d st:%d tc:%d\n", active, &hier_pr, hier_pr.u.p.lb, hier_pr.u.p.ub, hier_pr.u.p.st, hier_pr.u.p.tc)); } #endif }; // Information regarding a single layer within the scheduling hierarchy template struct kmp_hier_layer_info_t { int num_active; // number of threads active in this level kmp_hier_layer_e type; // LAYER_L1, LAYER_L2, etc. enum sched_type sched; // static, dynamic, guided, etc. typename traits_t::signed_t chunk; // chunk size associated with schedule int length; // length of the kmp_hier_top_unit_t array #ifdef KMP_DEBUG // Print this layer's information void print() { const char *t = __kmp_get_hier_str(type); KD_TRACE( 10, (" kmp_hier_layer_info_t: num_active:%d type:%s sched:%d chunk:%d " "length:%d\n", num_active, t, sched, chunk, length)); } #endif }; /* * Structure to implement entire hierarchy * * The hierarchy is kept as an array of arrays to represent the different * layers. Layer 0 is the lowest layer to layer num_layers - 1 which is the * highest layer. * Example: * [ 2 ] -> [ L3 | L3 ] * [ 1 ] -> [ L2 | L2 | L2 | L2 ] * [ 0 ] -> [ L1 | L1 | L1 | L1 | L1 | L1 | L1 | L1 ] * There is also an array of layer_info_t which has information regarding * each layer */ template struct kmp_hier_t { public: typedef typename traits_t::unsigned_t UT; typedef typename traits_t::signed_t ST; private: int next_recurse(ident_t *loc, int gtid, kmp_hier_top_unit_t *current, kmp_int32 *p_last, T *p_lb, T *p_ub, ST *p_st, kmp_int32 previous_id, int hier_level) { int status; kmp_info_t *th = __kmp_threads[gtid]; auto parent = current->get_parent(); bool last_layer = (hier_level == get_num_layers() - 1); KMP_DEBUG_ASSERT(th); kmp_hier_private_bdata_t *tdata = &(th->th.th_hier_bar_data[hier_level]); KMP_DEBUG_ASSERT(current); KMP_DEBUG_ASSERT(hier_level >= 0); KMP_DEBUG_ASSERT(hier_level < get_num_layers()); KMP_DEBUG_ASSERT(tdata); KMP_DEBUG_ASSERT(parent || last_layer); KD_TRACE( 1, ("kmp_hier_t.next_recurse(): T#%d (%d) called\n", gtid, hier_level)); T hier_id = (T)current->get_hier_id(); // Attempt to grab next iteration range for this level if (previous_id == 0) { KD_TRACE(1, ("kmp_hier_t.next_recurse(): T#%d (%d) is master of unit\n", gtid, hier_level)); kmp_int32 contains_last; T my_lb, my_ub; ST my_st; T nproc; dispatch_shared_info_template volatile *my_sh; dispatch_private_info_template *my_pr; if (last_layer) { // last layer below the very top uses the single shared buffer // from the team struct. KD_TRACE(10, ("kmp_hier_t.next_recurse(): T#%d (%d) using top level sh\n", gtid, hier_level)); my_sh = reinterpret_cast volatile *>( th->th.th_dispatch->th_dispatch_sh_current); nproc = (T)get_top_level_nproc(); } else { // middle layers use the shared buffer inside the kmp_hier_top_unit_t // structure KD_TRACE(10, ("kmp_hier_t.next_recurse(): T#%d (%d) using hier sh\n", gtid, hier_level)); my_sh = parent->get_curr_sh(th->th.th_hier_bar_data[hier_level + 1].index); nproc = (T)parent->get_num_active(); } my_pr = current->get_my_pr(); KMP_DEBUG_ASSERT(my_sh); KMP_DEBUG_ASSERT(my_pr); enum sched_type schedule = get_sched(hier_level); ST chunk = (ST)get_chunk(hier_level); status = __kmp_dispatch_next_algorithm(gtid, my_pr, my_sh, &contains_last, &my_lb, &my_ub, &my_st, nproc, hier_id); KD_TRACE( 10, ("kmp_hier_t.next_recurse(): T#%d (%d) next_pr_sh() returned %d\n", gtid, hier_level, status)); // When no iterations are found (status == 0) and this is not the last // layer, attempt to go up the hierarchy for more iterations if (status == 0 && !last_layer) { status = next_recurse(loc, gtid, parent, &contains_last, &my_lb, &my_ub, &my_st, hier_id, hier_level + 1); KD_TRACE( 10, ("kmp_hier_t.next_recurse(): T#%d (%d) hier_next() returned %d\n", gtid, hier_level, status)); if (status == 1) { kmp_hier_private_bdata_t *upper_tdata = &(th->th.th_hier_bar_data[hier_level + 1]); my_sh = parent->get_curr_sh(upper_tdata->index); KD_TRACE(10, ("kmp_hier_t.next_recurse(): T#%d (%d) about to init\n", gtid, hier_level)); __kmp_dispatch_init_algorithm(loc, gtid, my_pr, schedule, parent->get_curr_lb(upper_tdata->index), parent->get_curr_ub(upper_tdata->index), parent->get_curr_st(upper_tdata->index), #if USE_ITT_BUILD NULL, #endif chunk, nproc, hier_id); status = __kmp_dispatch_next_algorithm( gtid, my_pr, my_sh, &contains_last, &my_lb, &my_ub, &my_st, nproc, hier_id); if (!status) { KD_TRACE(10, ("kmp_hier_t.next_recurse(): T#%d (%d) status not 1 " "setting to 2!\n", gtid, hier_level)); status = 2; } } } current->set_next(my_lb, my_ub, my_st, status, tdata->index); // Propagate whether a unit holds the actual global last iteration // The contains_last attribute is sent downwards from the top to the // bottom of the hierarchy via the contains_last flag inside the // private dispatch buffers in the hierarchy's middle layers if (contains_last) { // If the next_algorithm() method returns 1 for p_last and it is the // last layer or our parent contains the last serial chunk, then the // chunk must contain the last serial iteration. if (last_layer || parent->hier_pr.flags.contains_last) { KD_TRACE(10, ("kmp_hier_t.next_recurse(): T#%d (%d) Setting this pr " "to contain last.\n", gtid, hier_level)); current->hier_pr.flags.contains_last = contains_last; } if (!current->hier_pr.flags.contains_last) contains_last = FALSE; } if (p_last) *p_last = contains_last; } // if master thread of this unit if (hier_level > 0 || !__kmp_dispatch_hand_threading) { KD_TRACE(10, ("kmp_hier_t.next_recurse(): T#%d (%d) going into barrier.\n", gtid, hier_level)); current->barrier(previous_id, tdata); KD_TRACE(10, ("kmp_hier_t.next_recurse(): T#%d (%d) released and exit %d\n", gtid, hier_level, current->get_curr_status(tdata->index))); } else { KMP_DEBUG_ASSERT(previous_id == 0); return status; } return current->get_curr_status(tdata->index); } public: int top_level_nproc; int num_layers; bool valid; int type_size; kmp_hier_layer_info_t *info; kmp_hier_top_unit_t **layers; // Deallocate all memory from this hierarchy void deallocate() { for (int i = 0; i < num_layers; ++i) if (layers[i] != NULL) { __kmp_free(layers[i]); } if (layers != NULL) { __kmp_free(layers); layers = NULL; } if (info != NULL) { __kmp_free(info); info = NULL; } num_layers = 0; valid = false; } // Returns true if reallocation is needed else false bool need_to_reallocate(int n, const kmp_hier_layer_e *new_layers, const enum sched_type *new_scheds, const ST *new_chunks) const { if (!valid || layers == NULL || info == NULL || traits_t::type_size != type_size || n != num_layers) return true; for (int i = 0; i < n; ++i) { if (info[i].type != new_layers[i]) return true; if (info[i].sched != new_scheds[i]) return true; if (info[i].chunk != new_chunks[i]) return true; } return false; } // A single thread should call this function while the other threads wait // create a new scheduling hierarchy consisting of new_layers, new_scheds // and new_chunks. These should come pre-sorted according to // kmp_hier_layer_e value. This function will try to avoid reallocation // if it can void allocate_hier(int n, const kmp_hier_layer_e *new_layers, const enum sched_type *new_scheds, const ST *new_chunks) { top_level_nproc = 0; if (!need_to_reallocate(n, new_layers, new_scheds, new_chunks)) { KD_TRACE( 10, ("kmp_hier_t::allocate_hier: T#0 do not need to reallocate\n")); for (int i = 0; i < n; ++i) { info[i].num_active = 0; for (int j = 0; j < get_length(i); ++j) layers[i][j].active = 0; } return; } KD_TRACE(10, ("kmp_hier_t::allocate_hier: T#0 full alloc\n")); deallocate(); type_size = traits_t::type_size; num_layers = n; info = (kmp_hier_layer_info_t *)__kmp_allocate( sizeof(kmp_hier_layer_info_t) * n); layers = (kmp_hier_top_unit_t **)__kmp_allocate( sizeof(kmp_hier_top_unit_t *) * n); for (int i = 0; i < n; ++i) { int max = 0; kmp_hier_layer_e layer = new_layers[i]; info[i].num_active = 0; info[i].type = layer; info[i].sched = new_scheds[i]; info[i].chunk = new_chunks[i]; max = __kmp_hier_max_units[layer + 1]; if (max == 0) { valid = false; KMP_WARNING(HierSchedInvalid, __kmp_get_hier_str(layer)); deallocate(); return; } info[i].length = max; layers[i] = (kmp_hier_top_unit_t *)__kmp_allocate( sizeof(kmp_hier_top_unit_t) * max); for (int j = 0; j < max; ++j) { layers[i][j].active = 0; layers[i][j].hier_pr.flags.use_hier = TRUE; } } valid = true; } // loc - source file location // gtid - global thread identifier // pr - this thread's private dispatch buffer (corresponding with gtid) // p_last (return value) - pointer to flag indicating this set of iterations // contains last // iteration // p_lb (return value) - lower bound for this chunk of iterations // p_ub (return value) - upper bound for this chunk of iterations // p_st (return value) - stride for this chunk of iterations // // Returns 1 if there are more iterations to perform, 0 otherwise int next(ident_t *loc, int gtid, dispatch_private_info_template *pr, kmp_int32 *p_last, T *p_lb, T *p_ub, ST *p_st) { int status; kmp_int32 contains_last = 0; kmp_info_t *th = __kmp_threads[gtid]; kmp_hier_private_bdata_t *tdata = &(th->th.th_hier_bar_data[0]); auto parent = pr->get_parent(); KMP_DEBUG_ASSERT(parent); KMP_DEBUG_ASSERT(th); KMP_DEBUG_ASSERT(tdata); KMP_DEBUG_ASSERT(parent); T nproc = (T)parent->get_num_active(); T unit_id = (T)pr->get_hier_id(); KD_TRACE( 10, ("kmp_hier_t.next(): T#%d THREAD LEVEL nproc:%d unit_id:%d called\n", gtid, nproc, unit_id)); // Handthreading implementation // Each iteration is performed by all threads on last unit (typically // cores/tiles) // e.g., threads 0,1,2,3 all execute iteration 0 // threads 0,1,2,3 all execute iteration 1 // threads 4,5,6,7 all execute iteration 2 // threads 4,5,6,7 all execute iteration 3 // ... etc. if (__kmp_dispatch_hand_threading) { KD_TRACE(10, ("kmp_hier_t.next(): T#%d THREAD LEVEL using hand threading\n", gtid)); if (unit_id == 0) { // For hand threading, the sh buffer on the lowest level is only ever // modified and read by the master thread on that level. Because of // this, we can always use the first sh buffer. auto sh = &(parent->hier_barrier.sh[0]); KMP_DEBUG_ASSERT(sh); status = __kmp_dispatch_next_algorithm( gtid, pr, sh, &contains_last, p_lb, p_ub, p_st, nproc, unit_id); if (!status) { bool done = false; while (!done) { done = true; status = next_recurse(loc, gtid, parent, &contains_last, p_lb, p_ub, p_st, unit_id, 0); if (status == 1) { __kmp_dispatch_init_algorithm(loc, gtid, pr, pr->schedule, parent->get_next_lb(tdata->index), parent->get_next_ub(tdata->index), parent->get_next_st(tdata->index), #if USE_ITT_BUILD NULL, #endif pr->u.p.parm1, nproc, unit_id); sh->u.s.iteration = 0; status = __kmp_dispatch_next_algorithm( gtid, pr, sh, &contains_last, p_lb, p_ub, p_st, nproc, unit_id); if (!status) { KD_TRACE(10, ("kmp_hier_t.next(): T#%d THREAD LEVEL status == 0 " "after next_pr_sh()" "trying again.\n", gtid)); done = false; } } else if (status == 2) { KD_TRACE(10, ("kmp_hier_t.next(): T#%d THREAD LEVEL status == 2 " "trying again.\n", gtid)); done = false; } } } parent->set_next_hand_thread(*p_lb, *p_ub, *p_st, status, tdata->index); } // if master thread of lowest unit level parent->barrier(pr->get_hier_id(), tdata); if (unit_id != 0) { *p_lb = parent->get_curr_lb(tdata->index); *p_ub = parent->get_curr_ub(tdata->index); *p_st = parent->get_curr_st(tdata->index); status = parent->get_curr_status(tdata->index); } } else { // Normal implementation // Each thread grabs an iteration chunk and executes it (no cooperation) auto sh = parent->get_curr_sh(tdata->index); KMP_DEBUG_ASSERT(sh); status = __kmp_dispatch_next_algorithm( gtid, pr, sh, &contains_last, p_lb, p_ub, p_st, nproc, unit_id); KD_TRACE(10, ("kmp_hier_t.next(): T#%d THREAD LEVEL next_algorithm status:%d " "contains_last:%d p_lb:%d p_ub:%d p_st:%d\n", gtid, status, contains_last, *p_lb, *p_ub, *p_st)); if (!status) { bool done = false; while (!done) { done = true; status = next_recurse(loc, gtid, parent, &contains_last, p_lb, p_ub, p_st, unit_id, 0); if (status == 1) { sh = parent->get_curr_sh(tdata->index); __kmp_dispatch_init_algorithm(loc, gtid, pr, pr->schedule, parent->get_curr_lb(tdata->index), parent->get_curr_ub(tdata->index), parent->get_curr_st(tdata->index), #if USE_ITT_BUILD NULL, #endif pr->u.p.parm1, nproc, unit_id); status = __kmp_dispatch_next_algorithm( gtid, pr, sh, &contains_last, p_lb, p_ub, p_st, nproc, unit_id); if (!status) { KD_TRACE(10, ("kmp_hier_t.next(): T#%d THREAD LEVEL status == 0 " "after next_pr_sh()" "trying again.\n", gtid)); done = false; } } else if (status == 2) { KD_TRACE(10, ("kmp_hier_t.next(): T#%d THREAD LEVEL status == 2 " "trying again.\n", gtid)); done = false; } } } } if (contains_last && !parent->hier_pr.flags.contains_last) { KD_TRACE(10, ("kmp_hier_t.next(): T#%d THREAD LEVEL resetting " "contains_last to FALSE\n", gtid)); contains_last = FALSE; } if (p_last) *p_last = contains_last; KD_TRACE(10, ("kmp_hier_t.next(): T#%d THREAD LEVEL exit status %d\n", gtid, status)); return status; } // These functions probe the layer info structure // Returns the type of topology unit given level kmp_hier_layer_e get_type(int level) const { KMP_DEBUG_ASSERT(level >= 0); KMP_DEBUG_ASSERT(level < num_layers); return info[level].type; } // Returns the schedule type at given level enum sched_type get_sched(int level) const { KMP_DEBUG_ASSERT(level >= 0); KMP_DEBUG_ASSERT(level < num_layers); return info[level].sched; } // Returns the chunk size at given level ST get_chunk(int level) const { KMP_DEBUG_ASSERT(level >= 0); KMP_DEBUG_ASSERT(level < num_layers); return info[level].chunk; } // Returns the number of active threads at given level int get_num_active(int level) const { KMP_DEBUG_ASSERT(level >= 0); KMP_DEBUG_ASSERT(level < num_layers); return info[level].num_active; } // Returns the length of topology unit array at given level int get_length(int level) const { KMP_DEBUG_ASSERT(level >= 0); KMP_DEBUG_ASSERT(level < num_layers); return info[level].length; } // Returns the topology unit given the level and index kmp_hier_top_unit_t *get_unit(int level, int index) { KMP_DEBUG_ASSERT(level >= 0); KMP_DEBUG_ASSERT(level < num_layers); KMP_DEBUG_ASSERT(index >= 0); KMP_DEBUG_ASSERT(index < get_length(level)); return &(layers[level][index]); } // Returns the number of layers in the hierarchy int get_num_layers() const { return num_layers; } // Returns the number of threads in the top layer // This is necessary because we don't store a topology unit as // the very top level and the scheduling algorithms need this information int get_top_level_nproc() const { return top_level_nproc; } // Return whether this hierarchy is valid or not bool is_valid() const { return valid; } #ifdef KMP_DEBUG // Print the hierarchy void print() { KD_TRACE(10, ("kmp_hier_t:\n")); for (int i = num_layers - 1; i >= 0; --i) { KD_TRACE(10, ("Info[%d] = ", i)); info[i].print(); } for (int i = num_layers - 1; i >= 0; --i) { KD_TRACE(10, ("Layer[%d] =\n", i)); for (int j = 0; j < info[i].length; ++j) { layers[i][j].print(); } } } #endif }; template void __kmp_dispatch_init_hierarchy(ident_t *loc, int n, kmp_hier_layer_e *new_layers, enum sched_type *new_scheds, typename traits_t::signed_t *new_chunks, T lb, T ub, typename traits_t::signed_t st) { int tid, gtid, num_hw_threads, num_threads_per_layer1, active; int my_buffer_index; kmp_info_t *th; kmp_team_t *team; dispatch_private_info_template *pr; dispatch_shared_info_template volatile *sh; gtid = __kmp_entry_gtid(); tid = __kmp_tid_from_gtid(gtid); #ifdef KMP_DEBUG KD_TRACE(10, ("__kmp_dispatch_init_hierarchy: T#%d called: %d layer(s)\n", gtid, n)); for (int i = 0; i < n; ++i) { const char *layer = __kmp_get_hier_str(new_layers[i]); KD_TRACE(10, ("__kmp_dispatch_init_hierarchy: T#%d: new_layers[%d] = %s, " "new_scheds[%d] = %d, new_chunks[%d] = %u\n", gtid, i, layer, i, (int)new_scheds[i], i, new_chunks[i])); } #endif // KMP_DEBUG KMP_DEBUG_ASSERT(n > 0); KMP_DEBUG_ASSERT(new_layers); KMP_DEBUG_ASSERT(new_scheds); KMP_DEBUG_ASSERT(new_chunks); if (!TCR_4(__kmp_init_parallel)) __kmp_parallel_initialize(); __kmp_resume_if_soft_paused(); th = __kmp_threads[gtid]; team = th->th.th_team; active = !team->t.t_serialized; th->th.th_ident = loc; num_hw_threads = __kmp_hier_max_units[kmp_hier_layer_e::LAYER_THREAD + 1]; KMP_DEBUG_ASSERT(th->th.th_dispatch == &th->th.th_team->t.t_dispatch[th->th.th_info.ds.ds_tid]); my_buffer_index = th->th.th_dispatch->th_disp_index; pr = reinterpret_cast *>( &th->th.th_dispatch ->th_disp_buffer[my_buffer_index % __kmp_dispatch_num_buffers]); sh = reinterpret_cast volatile *>( &team->t.t_disp_buffer[my_buffer_index % __kmp_dispatch_num_buffers]); if (!active) { KD_TRACE(10, ("__kmp_dispatch_init_hierarchy: T#%d not active parallel. " "Using normal dispatch functions.\n", gtid)); KMP_DEBUG_ASSERT(pr); pr->flags.use_hier = FALSE; pr->flags.contains_last = FALSE; return; } KMP_DEBUG_ASSERT(pr); KMP_DEBUG_ASSERT(sh); pr->flags.use_hier = TRUE; pr->u.p.tc = 0; // Have master allocate the hierarchy if (__kmp_tid_from_gtid(gtid) == 0) { KD_TRACE(10, ("__kmp_dispatch_init_hierarchy: T#%d pr:%p sh:%p allocating " "hierarchy\n", gtid, pr, sh)); if (sh->hier == NULL) { sh->hier = (kmp_hier_t *)__kmp_allocate(sizeof(kmp_hier_t)); } sh->hier->allocate_hier(n, new_layers, new_scheds, new_chunks); sh->u.s.iteration = 0; } __kmp_barrier(bs_plain_barrier, gtid, FALSE, 0, NULL, NULL); // Check to make sure the hierarchy is valid kmp_hier_t *hier = sh->hier; if (!sh->hier->is_valid()) { pr->flags.use_hier = FALSE; return; } // Have threads allocate their thread-private barrier data if it hasn't // already been allocated if (th->th.th_hier_bar_data == NULL) { th->th.th_hier_bar_data = (kmp_hier_private_bdata_t *)__kmp_allocate( sizeof(kmp_hier_private_bdata_t) * kmp_hier_layer_e::LAYER_LAST); } // Have threads "register" themselves by modifiying the active count for each // level they are involved in. The active count will act as nthreads for that // level regarding the scheduling algorithms for (int i = 0; i < n; ++i) { int index = __kmp_dispatch_get_index(tid, hier->get_type(i)); kmp_hier_top_unit_t *my_unit = hier->get_unit(i, index); // Setup the thread's private dispatch buffer's hierarchy pointers if (i == 0) pr->hier_parent = my_unit; // If this unit is already active, then increment active count and wait if (my_unit->is_active()) { KD_TRACE(10, ("__kmp_dispatch_init_hierarchy: T#%d my_unit (%p) " "is already active (%d)\n", gtid, my_unit, my_unit->active)); KMP_TEST_THEN_INC32(&(my_unit->active)); break; } // Flag that this unit is active if (KMP_COMPARE_AND_STORE_ACQ32(&(my_unit->active), 0, 1)) { // Do not setup parent pointer for top level unit since it has no parent if (i < n - 1) { // Setup middle layer pointers to parents my_unit->get_my_pr()->hier_id = index % __kmp_dispatch_get_t1_per_t2(hier->get_type(i), hier->get_type(i + 1)); int parent_index = __kmp_dispatch_get_index(tid, hier->get_type(i + 1)); my_unit->hier_parent = hier->get_unit(i + 1, parent_index); } else { // Setup top layer information (no parent pointers are set) my_unit->get_my_pr()->hier_id = index % __kmp_dispatch_get_t1_per_t2(hier->get_type(i), kmp_hier_layer_e::LAYER_LOOP); KMP_TEST_THEN_INC32(&(hier->top_level_nproc)); my_unit->hier_parent = nullptr; } // Set trip count to 0 so that next() operation will initially climb up // the hierarchy to get more iterations (early exit in next() for tc == 0) my_unit->get_my_pr()->u.p.tc = 0; // Increment this layer's number of active units KMP_TEST_THEN_INC32(&(hier->info[i].num_active)); KD_TRACE(10, ("__kmp_dispatch_init_hierarchy: T#%d my_unit (%p) " "incrementing num_active\n", gtid, my_unit)); } else { KMP_TEST_THEN_INC32(&(my_unit->active)); break; } } // Set this thread's id num_threads_per_layer1 = __kmp_dispatch_get_t1_per_t2( kmp_hier_layer_e::LAYER_THREAD, hier->get_type(0)); pr->hier_id = tid % num_threads_per_layer1; // For oversubscribed threads, increment their index within the lowest unit // This is done to prevent having two or more threads with id 0, id 1, etc. if (tid >= num_hw_threads) pr->hier_id += ((tid / num_hw_threads) * num_threads_per_layer1); KD_TRACE( 10, ("__kmp_dispatch_init_hierarchy: T#%d setting lowest hier_id to %d\n", gtid, pr->hier_id)); pr->flags.contains_last = FALSE; __kmp_barrier(bs_plain_barrier, gtid, FALSE, 0, NULL, NULL); // Now that the number of active threads at each level is determined, // the barrier data for each unit can be initialized and the last layer's // loop information can be initialized. int prev_id = pr->get_hier_id(); for (int i = 0; i < n; ++i) { if (prev_id != 0) break; int index = __kmp_dispatch_get_index(tid, hier->get_type(i)); kmp_hier_top_unit_t *my_unit = hier->get_unit(i, index); // Only master threads of this unit within the hierarchy do initialization KD_TRACE(10, ("__kmp_dispatch_init_hierarchy: T#%d (%d) prev_id is 0\n", gtid, i)); my_unit->reset_shared_barrier(); my_unit->hier_pr.flags.contains_last = FALSE; // Last layer, initialize the private buffers with entire loop information // Now the next next_algorithim() call will get the first chunk of // iterations properly if (i == n - 1) { __kmp_dispatch_init_algorithm( loc, gtid, my_unit->get_my_pr(), hier->get_sched(i), lb, ub, st, #if USE_ITT_BUILD NULL, #endif hier->get_chunk(i), hier->get_num_active(i), my_unit->get_hier_id()); } prev_id = my_unit->get_hier_id(); } // Initialize each layer of the thread's private barrier data kmp_hier_top_unit_t *unit = pr->hier_parent; for (int i = 0; i < n && unit; ++i, unit = unit->get_parent()) { kmp_hier_private_bdata_t *tdata = &(th->th.th_hier_bar_data[i]); unit->reset_private_barrier(tdata); } __kmp_barrier(bs_plain_barrier, gtid, FALSE, 0, NULL, NULL); #ifdef KMP_DEBUG if (__kmp_tid_from_gtid(gtid) == 0) { for (int i = 0; i < n; ++i) { KD_TRACE(10, ("__kmp_dispatch_init_hierarchy: T#%d active count[%d] = %d\n", gtid, i, hier->get_num_active(i))); } hier->print(); } __kmp_barrier(bs_plain_barrier, gtid, FALSE, 0, NULL, NULL); #endif // KMP_DEBUG } #endif