/* $Source: bitbucket.org:berkeleylab/gasnet.git/gasnet_team.c $ * Description: GASNet implementation of teams * Copyright 2018, The Regents of the University of California * Terms of use are as specified in license.txt */ #include // TODO-EX: // // Currently we implement a "team" (the abstract distributed collection, of // which a "tm" is a representative) in terms of the legacy GASNet-1 collective // team. We should eventually using something smarter, including scalable // storage. // Given (tm,rank) return the jobrank gex_Rank_t gasneti_tm_fwd_rank(gasneti_TM_t tm, gex_Rank_t rank) { gasnete_coll_team_t team = tm->_coll_team; gasneti_assert(team != GASNET_TEAM_ALL); // TM0 should not reach here gasneti_assert(!tm->_rank_map); // Teams with dense rank map should not reach here gasneti_unreachable_error(("Unimplemented gasneti_tm_fwd_rank() call")); return GEX_RANK_INVALID; } // Given (tm,rank) return the ep_location gex_EP_Location_t gasneti_tm_fwd_location(gasneti_TM_t tm, gex_Rank_t rank, gex_Flags_t flags) { gasnete_coll_team_t team = tm->_coll_team; gasneti_assert(team != GASNET_TEAM_ALL); // TM0 should not reach here gasneti_assert(!tm->_rank_map); // Teams with dense rank map should not reach here gasneti_unreachable_error(("Unimplemented gasneti_tm_fwd_location() call")); gex_EP_Location_t result = {GEX_RANK_INVALID, 0}; return result; } // Given (tm,jobrank) return the rank or GEX_RANK_INVALID // TODO-EX: THIS IS A HORRIBLE O(size(tm)) SCAN! gex_Rank_t gasneti_tm_rev_rank(gasneti_TM_t tm, gex_Rank_t jobrank) { gasnete_coll_team_t team = tm->_coll_team; gasneti_assert(team != GASNET_TEAM_ALL); // TM0 should not reach here gex_Rank_t size = tm->_size; gasneti_assert_uint(size ,==, team->total_ranks); for (gex_Rank_t rank = 0; rank < size; ++rank) { if (team->rel2act_map[rank] == jobrank) return rank; } return GEX_RANK_INVALID; } static size_t get_scratch_size(gex_Rank_t new_tm_size, gex_Flags_t flags) { // Specially defined cases if (0 == new_tm_size) return 0; if (1 == new_tm_size) return GASNETE_COLL_SCRATCH_SIZE_MIN; static size_t recommended; static int is_init = 0; if_pf (!is_init) { static gasneti_mutex_t lock = GASNETI_MUTEX_INITIALIZER; gasneti_mutex_lock(&lock); if (!is_init) { recommended = gasneti_getenv_int_withdefault("GASNET_COLL_SCRATCH_SIZE", GASNETE_COLL_SCRATCH_SIZE_DEFAULT,1); // Silently raise to implementation-defined minimum recommended = MAX(recommended, GASNETE_COLL_SCRATCH_SIZE_MIN); gasneti_sync_writes(); is_init = 1; } gasneti_mutex_unlock(&lock); } else { gasneti_sync_reads(); } return recommended; } size_t gasneti_TM_Split(gex_TM_t *new_tm_p, gex_TM_t e_parent, int color, int key, void *addr, size_t len, gex_Flags_t flags GASNETI_THREAD_FARG) { gasneti_TM_t i_parent = gasneti_import_tm_nonpair(e_parent); gasneti_EP_t ep = i_parent->_ep; GASNETI_TRACE_PRINTF(O,("gex_TM_Split: parent="GASNETI_TMSELFFMT" color=%d key=%d flags=%d", GASNETI_TMSELFSTR(e_parent), color, key, flags)); GASNETI_CHECK_INJECT(); static int did_warn = 0; if ((flags & GEX_FLAG_TM_SCRATCH_SIZE_MIN) && !did_warn) { if (! i_parent->_rank) { gasneti_console_message("WARNING", "gex_TM_Split() called using GEX_FLAG_TM_SCRATCH_SIZE_MIN, " "deprecated since specification 0.11."); } did_warn = 1; // Some process did, even if it was not us. } #if GASNET_DEBUG if ((flags & GEX_FLAG_TM_SCRATCH_SIZE_MIN) && (flags & GEX_FLAG_TM_SCRATCH_SIZE_RECOMMENDED)) { gasneti_fatalerror("Call to gex_TM_Split() with mutually-exclusive " "GEX_FLAG_TM_SCRATCH_SIZE_MIN and " "GEX_FLAG_TM_SCRATCH_SIZE_RECOMMENDED both set in flags argument"); } #endif if (flags & (GEX_FLAG_TM_SCRATCH_SIZE_MIN | GEX_FLAG_TM_SCRATCH_SIZE_RECOMMENDED)) { // Don't know true size w/o comms, but singleton parent can only produce singleton children size_t result = new_tm_p ? get_scratch_size(i_parent->_size, flags) : 0; GASNETI_TRACE_PRINTF(O,("gex_TM_Split: scratch size query result=%"PRIuSZ, result)); return result; } // Split's scratch address is GEX_FLAG_TM_LOCAL_SCRATCH by default, // but GEX_FLAG_TM_NO_SCRATCH is also accepted. // TODO: support GEX_FLAG_TM_SYMMETRIC_SCRATCH too if (flags & GEX_FLAG_TM_GLOBAL_SCRATCH) { gasneti_fatalerror("Invalid call to gex_TM_Split with GEX_FLAG_TM_GLOBAL_SCRATCH"); } else if (flags & GEX_FLAG_TM_SYMMETRIC_SCRATCH) { gasneti_fatalerror("Invalid call to gex_TM_Split with GEX_FLAG_TM_SYMMETRIC_SCRATCH"); } else if (! (flags & GEX_FLAG_TM_NO_SCRATCH)) { flags |= GEX_FLAG_TM_LOCAL_SCRATCH; } if (!new_tm_p) { color = -1; // tell gasnete_coll_team_split() not to create a team for this caller } else { gasneti_assert_int(color ,>=, 0); if (! (flags & GEX_FLAG_TM_NO_SCRATCH)) { #if !GASNET_SEGMENT_EVERYTHING gasneti_assert(ep->_segment); gasneti_assert_ptr(addr ,>=, ep->_segment->_addr); gasneti_assert_ptr((uint8_t*)addr+len ,<=, ep->_segment->_ub); #endif if (!len) { gasneti_fatalerror("Invalid call to gex_TM_Split with scratch_size = 0"); } } } gasnete_coll_team_t team = gasnete_coll_team_split(i_parent->_coll_team, color, key, len, addr, flags GASNETI_THREAD_PASS); if (team == NULL) { gasneti_assert(!new_tm_p); GASNETI_TRACE_PRINTF(O,("gex_TM_Split: parent="GASNETI_TMSELFFMT" [No team created]", GASNETI_TMSELFSTR(e_parent))); return 0; } gasneti_TM_t i_tm = gasneti_alloc_tm(ep, team->myrank, team->total_ranks, flags); i_tm->_coll_team = team; gex_TM_t e_tm = gasneti_export_tm(i_tm); team->e_tm = e_tm; *new_tm_p = e_tm; i_tm->_rank_map = team->rel2act_map; i_tm->_index_map = NULL; // TODO-EX: provide this for teams w/ non-primordial EPs GASNETI_TRACE_PRINTF(O,("gex_TM_Split: parent="GASNETI_TMSELFFMT" result="GASNETI_TMSELFFMT, GASNETI_TMSELFSTR(e_parent), GASNETI_TMSELFSTR(e_tm))); GASNETI_STAT_EVENT(O, TEAM_NEW_SPLIT); return 1; // return is documented as undefined } // TODO-EX: implement full generality given in specification // MISSING support for: // + non-primordial EPs // - (num_new_tms > 1) // - non-zero gex_ep_index // - caller's EP not in members[] // + GEX_FLAG_SCRATCH_SEG_OFFSET size_t gasneti_TM_Create( gex_TM_t *new_tms, size_t num_new_tms, gex_TM_t e_parent, gex_EP_Location_t *members, size_t nmembers, gex_Addr_t *scratch_addrs, size_t scratch_size, gex_Flags_t flags GASNETI_THREAD_FARG) { size_t result = 0; gasneti_TM_t i_parent = gasneti_import_tm_nonpair(e_parent); // NOTE: we can simplify things by observing that ranks in TM0 are always jobranks flags |= gasneti_is_tm0(i_parent) ? GEX_FLAG_RANK_IS_JOBRANK : 0; gasneti_EP_t ep = i_parent->_ep; int is_jobrank = (flags & GEX_FLAG_RANK_IS_JOBRANK); GASNETI_TRACE_PRINTF(O,("gex_TM_Create: parent="GASNETI_TMSELFFMT" num_new_tms=%"PRIuSZ" nmembers=%"PRIuSZ" scratch_size=%"PRIuSZ" flags=%d", GASNETI_TMSELFSTR(e_parent), num_new_tms, nmembers, scratch_size, flags)); GASNETI_CHECK_INJECT(); static int did_warn = 0; if ((flags & GEX_FLAG_TM_SCRATCH_SIZE_MIN) && !did_warn) { if (! i_parent->_rank) { gasneti_console_message("WARNING", "gex_TM_Create() called using GEX_FLAG_TM_SCRATCH_SIZE_MIN, " "deprecated since specification 0.11."); } did_warn = 1; // Some process did, even if it was not us. } #if GASNET_DEBUG if ((flags & GEX_FLAG_TM_SCRATCH_SIZE_MIN) && (flags & GEX_FLAG_TM_SCRATCH_SIZE_RECOMMENDED)) { gasneti_fatalerror("Call to gex_TM_Create() with mutually-exclusive " "GEX_FLAG_TM_SCRATCH_SIZE_MIN and " "GEX_FLAG_TM_SCRATCH_SIZE_RECOMMENDED both set in flags argument"); } #endif // For now 0 or 1 are the only valid numbers of outputs. gasneti_assert(!nmembers || num_new_tms == 1); if (flags & (GEX_FLAG_TM_SCRATCH_SIZE_MIN | GEX_FLAG_TM_SCRATCH_SIZE_RECOMMENDED)) { size_t result = nmembers ? get_scratch_size(nmembers, flags) : 0; GASNETI_TRACE_PRINTF(O,("gex_TM_Create: scratch size query result=%"PRIuSZ, result)); return result; } if (num_new_tms && nmembers) { if (!scratch_size && !(flags & GEX_FLAG_TM_NO_SCRATCH)) { gasneti_fatalerror("Invalid call to gex_TM_Create with scratch_size = 0"); } GASNETI_TRACE_PRINTF(D,("gex_TM_Create: members[ %s ]", gasneti_format_eploc(members, nmembers))); } // TODO-EX: remove when subteam collectives no longer require a parent-scope entry barrier gasnete_coll_consensus_barrier(i_parent->_coll_team GASNETI_THREAD_PASS); if (! nmembers) { GASNETI_TRACE_PRINTF(O,("gex_TM_Create: parent="GASNETI_TMSELFFMT" [No team created]", GASNETI_TMSELFSTR(e_parent))); goto done; } // TODO: missing sanity checks on args, especially single-valued assertions // Find self in members[] gex_Rank_t my_member_rank = is_jobrank ? gasneti_i_tm_rank_to_jobrank(i_parent, i_parent->_rank) : i_parent->_rank; gex_Rank_t my_new_rank = GEX_RANK_INVALID; for (gex_Rank_t r = 0; r < nmembers; ++r) { if (members[r].gex_rank == my_member_rank && members[r].gex_ep_index == ep->_index) { my_new_rank = r; break; } } if (my_new_rank == GEX_RANK_INVALID) { // TODO-EX: not the right test or message in multi-EP case gasneti_fatalerror("Call to gex_TM_Create with nmembers=%"PRIuSZ" but 0 local members found in in members[]", nmembers); } // Generate rank_map[], which the new team will own gex_Rank_t *rank_map = gasneti_malloc(nmembers * sizeof(gex_Rank_t)); for (gex_Rank_t r = 0; r < nmembers; ++r) { gasneti_assert_always_uint(members[r].gex_ep_index ,==, 0); gex_Rank_t tmp = members[r].gex_rank; rank_map[r] = is_jobrank ? tmp : gasneti_i_tm_rank_to_jobrank(i_parent, tmp); } gasnete_coll_team_t team = gasnete_coll_team_create( i_parent->_coll_team, nmembers, my_new_rank, rank_map, scratch_size, scratch_addrs, flags GASNETI_THREAD_PASS); gasneti_TM_t i_tm = gasneti_alloc_tm(ep, my_new_rank, nmembers, flags); i_tm->_coll_team = team; gex_TM_t e_tm = gasneti_export_tm(i_tm); team->e_tm = e_tm; new_tms[0] = e_tm; i_tm->_rank_map = team->rel2act_map; i_tm->_index_map = NULL; // TODO-EX: provide this for teams w/ non-primordial EPs // TODO-EX: outut only correct for num_new_tms==1 GASNETI_TRACE_PRINTF(O,("gex_TM_Create: parent="GASNETI_TMSELFFMT" rank=%d size=%d result="GASNETI_TMSELFFMT, GASNETI_TMSELFSTR(e_parent), my_new_rank, (int)nmembers, GASNETI_TMSELFSTR(e_tm))); GASNETI_STAT_EVENT(O, TEAM_NEW_CREATE); result = 1; // return is documented as undefined done: return result; } int gasneti_TM_Destroy( gex_TM_t e_tm, gex_Memvec_t *scratch_p, gex_Flags_t flags GASNETI_THREAD_FARG) { gasneti_TM_t i_tm = gasneti_import_tm_nonpair(e_tm); gasnete_coll_team_t team = i_tm->_coll_team; GASNETI_TRACE_PRINTF(O,("gex_TM_Destroy: team="GASNETI_TMSELFFMT" flags=%d", GASNETI_TMSELFSTR(e_tm), flags)); if (1) { // TODO: w/ multi-EP exactly one tm per proc should log this event GASNETI_STAT_EVENT(O, TEAM_DESTROY); } GASNETI_CHECK_INJECT(); if (gasneti_is_tm0(i_tm)) { gasneti_fatalerror("Invalid gasneti_TM_Destroy() of the primordial team"); } if (! (flags & GEX_FLAG_GLOBALLY_QUIESCED)) { gasnete_coll_consensus_barrier(team GASNETI_THREAD_PASS); } gasneti_free_tm(i_tm); return gasnete_coll_team_free(team, scratch_p); } /* ------------------------------------------------------------------------------------ */ // Simple blocking Exchange utility function extern void gasneti_blockingExchange(gex_TM_t tm, void *src, size_t len, void *dst) { // TODO-EX: use gex_Coll_Exchange() once available const int coll_flags = GASNET_COLL_LOCAL | GASNET_COLL_IN_MYSYNC | GASNET_COLL_OUT_MYSYNC; gasnet_coll_gather_all(gasneti_import_tm_nonpair(tm)->_coll_team, dst, src, len, coll_flags); } /* ------------------------------------------------------------------------------------ */ /* TM trace formatting - legal even without STATS/TRACE */ // Format a gex_TM_t as a GUID extern const char *gasneti_formattm(gex_TM_t e_tm) { if (e_tm == NULL) return "JOB"; // JobRank, as with token and legacy collectives if (gasneti_e_tm_is_pair(e_tm)) { gasneti_TM_Pair_t pair = gasneti_import_tm_pair(e_tm); gex_EP_Index_t loc_idx = gasneti_tm_pair_loc_idx(pair); gex_EP_Index_t rem_idx = gasneti_tm_pair_rem_idx(pair); return gasneti_dynsprintf("TM_PAIR(%x,%x)", loc_idx, rem_idx); } else { gasnete_coll_team_t team = gasneti_import_tm(e_tm)->_coll_team; if (team == NULL) return "TM0"; // Team0 before end of Client_Init return gasneti_dynsprintf("TM%x", (unsigned int)team->team_id); } }