/* * %CopyrightBegin% * * Copyright Ericsson AB 2012-2017. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * %CopyrightEnd% */ /* * Description: Process/Port table implementation. * * Author: Rickard Green */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #define ERTS_PTAB_WANT_BIF_IMPL__ #define ERTS_PTAB_WANT_DEBUG_FUNCS__ #include "erl_ptab.h" #include "global.h" #include "erl_binary.h" typedef struct ErtsPTabListBifData_ ErtsPTabListBifData; #define ERTS_PTAB_NEW_MAX_RESERVE_FAIL 1000 #define ERTS_PTAB_LIST_BIF_TAB_INSPECT_INDICES_PER_RED 25 #define ERTS_PTAB_LIST_BIF_TAB_CHUNK_SIZE 1000 #define ERTS_PTAB_LIST_BIF_MIN_START_REDS \ (ERTS_PTAB_LIST_BIF_TAB_CHUNK_SIZE \ / ERTS_PTAB_LIST_BIF_TAB_INSPECT_INDICES_PER_RED) #define ERTS_PTAB_LIST_BIF_TAB_FREE_DELETED_REDS 1 #define ERTS_PTAB_LIST_BIF_INSPECT_DELETED_PER_RED 10 #define ERTS_PTAB_LIST_INSPECT_DELETED_MAX_REDS \ (ERTS_PTAB_LIST_BIF_TAB_CHUNK_SIZE \ / ERTS_PTAB_LIST_BIF_TAB_INSPECT_INDICES_PER_RED) #define ERTS_PTAB_LIST_BIF_BUILD_RESULT_CONSES_PER_RED 75 #define ERTS_PTAB_LIST_DBG_DO_TRACE 0 #ifdef DEBUG # define ERTS_PTAB_LIST_BIF_DEBUGLEVEL 100 #else # define ERTS_PTAB_LIST_BIF_DEBUGLEVEL 0 #endif #define ERTS_PTAB_LIST_DBGLVL_CHK_HALLOC 1 #define ERTS_PTAB_LIST_DBGLVL_CHK_FOUND_PIDS 5 #define ERTS_PTAB_LIST_DBGLVL_CHK_PIDS 10 #define ERTS_PTAB_LIST_DBGLVL_CHK_DEL_LIST 20 #define ERTS_PTAB_LIST_DBGLVL_CHK_RESLIST 20 #if ERTS_PTAB_LIST_BIF_DEBUGLEVEL == 0 # define ERTS_PTAB_LIST_ASSERT(EXP) #else # define ERTS_PTAB_LIST_ASSERT(EXP) \ ((void) ((EXP) \ ? 1 \ : (debug_ptab_list_assert_error(#EXP, \ __FILE__, \ __LINE__, \ __func__), \ 0))) #endif #if ERTS_PTAB_LIST_BIF_DEBUGLEVEL >= ERTS_PTAB_LIST_DBGLVL_CHK_HALLOC # define ERTS_PTAB_LIST_DBG_SAVE_HEAP_ALLOC(PTLBDP, HP, SZ) \ do { \ ERTS_PTAB_LIST_ASSERT(!(PTLBDP)->debug.heap); \ ERTS_PTAB_LIST_ASSERT(!(PTLBDP)->debug.heap_size); \ (PTLBDP)->debug.heap = (HP); \ (PTLBDP)->debug.heap_size = (SZ); \ } while (0) # define ERTS_PTAB_LIST_DBG_VERIFY_HEAP_ALLOC_USED(PTLBDP, HP) \ do { \ ERTS_PTAB_LIST_ASSERT((PTLBDP)->debug.heap); \ ERTS_PTAB_LIST_ASSERT((PTLBDP)->debug.heap_size); \ ERTS_PTAB_LIST_ASSERT(((PTLBDP)->debug.heap \ + (PTLBDP)->debug.heap_size) \ == (HP)); \ (PTLBDP)->debug.heap = NULL; \ (PTLBDP)->debug.heap_size = 0; \ } while (0) # define ERTS_PTAB_LIST_DBG_HEAP_ALLOC_INIT(PTLBDP) \ do { \ (PTLBDP)->debug.heap = NULL; \ (PTLBDP)->debug.heap_size = 0; \ } while (0) #else # define ERTS_PTAB_LIST_DBG_SAVE_HEAP_ALLOC(PTLBDP, HP, SZ) # define ERTS_PTAB_LIST_DBG_VERIFY_HEAP_ALLOC_USED(PTLBDP, HP) # define ERTS_PTAB_LIST_DBG_HEAP_ALLOC_INIT(PTLBDP) #endif #if ERTS_PTAB_LIST_BIF_DEBUGLEVEL >= ERTS_PTAB_LIST_DBGLVL_CHK_RESLIST # define ERTS_PTAB_LIST_DBG_CHK_RESLIST(R) \ debug_ptab_list_check_res_list((R)) #else # define ERTS_PTAB_LIST_DBG_CHK_RESLIST(R) #endif #if ERTS_PTAB_LIST_BIF_DEBUGLEVEL >= ERTS_PTAB_LIST_DBGLVL_CHK_PIDS # define ERTS_PTAB_LIST_DBG_SAVE_PIDS(PTLBDP) \ debug_ptab_list_save_all_pids((PTLBDP)) # define ERTS_PTAB_LIST_DBG_VERIFY_PIDS(PTLBDP) \ do { \ if (!(PTLBDP)->debug.correct_pids_verified) \ debug_ptab_list_verify_all_pids((PTLBDP)); \ } while (0) # define ERTS_PTAB_LIST_DBG_CLEANUP_CHK_PIDS(PTLBDP) \ do { \ if ((PTLBDP)->debug.correct_pids) { \ erts_free(ERTS_ALC_T_PTAB_LIST_PIDS, \ (PTLBDP)->debug.correct_pids); \ (PTLBDP)->debug.correct_pids = NULL; \ } \ } while(0) # define ERTS_PTAB_LIST_DBG_CHK_PIDS_INIT(PTLBDP) \ do { \ (PTLBDP)->debug.correct_pids_verified = 0; \ (PTLBDP)->debug.correct_pids = NULL; \ } while (0) #else # define ERTS_PTAB_LIST_DBG_SAVE_PIDS(PTLBDP) # define ERTS_PTAB_LIST_DBG_VERIFY_PIDS(PTLBDP) # define ERTS_PTAB_LIST_DBG_CLEANUP_CHK_PIDS(PTLBDP) # define ERTS_PTAB_LIST_DBG_CHK_PIDS_INIT(PTLBDP) #endif #if ERTS_PTAB_LIST_BIF_DEBUGLEVEL >= ERTS_PTAB_LIST_DBGLVL_CHK_FOUND_PIDS # define ERTS_PTAB_LIST_DBG_CHK_PID_FOUND(PTLBDP, PID, IC) \ debug_ptab_list_check_found_pid((PTLBDP), (PID), (IC), 1) # define ERTS_PTAB_LIST_DBG_CHK_PID_NOT_FOUND(PTLBDP, PID, IC) \ debug_ptab_list_check_found_pid((PTLBDP), (PID), (IC), 0) #else # define ERTS_PTAB_LIST_DBG_CHK_PID_FOUND(PTLBDP, PID, IC) # define ERTS_PTAB_LIST_DBG_CHK_PID_NOT_FOUND(PTLBDP, PID, IC) #endif #if ERTS_PTAB_LIST_BIF_DEBUGLEVEL >= ERTS_PTAB_LIST_DBGLVL_CHK_DEL_LIST # define ERTS_PTAB_LIST_DBG_CHK_DEL_LIST(PTab) \ debug_ptab_list_check_del_list((PTab)) # define ERTS_PTAB_LIST_DBG_CHK_FREELIST(PTab, FL) \ debug_ptab_list_check_del_free_list((PTab), (FL)) #else # define ERTS_PTAB_LIST_DBG_CHK_DEL_LIST(PTab) # define ERTS_PTAB_LIST_DBG_CHK_FREELIST(PTab, FL) #endif #if ERTS_PTAB_LIST_BIF_DEBUGLEVEL == 0 #if ERTS_PTAB_LIST_DBG_DO_TRACE # define ERTS_PTAB_LIST_DBG_INIT(P, PTLBDP) \ (PTLBDP)->debug.caller = (P)->common.id # else # define ERTS_PTAB_LIST_DBG_INIT(P, PTLBDP) # endif # define ERTS_PTAB_LIST_DBG_CLEANUP(PTLBDP) #else # define ERTS_PTAB_LIST_DBG_INIT(P, PTLBDP) \ do { \ (PTLBDP)->debug.caller = (P)->common.id; \ ERTS_PTAB_LIST_DBG_HEAP_ALLOC_INIT((PTLBDP)); \ ERTS_PTAB_LIST_DBG_CHK_PIDS_INIT((PTLBDP)); \ } while (0) # define ERTS_PTAB_LIST_DBG_CLEANUP(PTLBDP) \ do { \ ERTS_PTAB_LIST_DBG_CLEANUP_CHK_PIDS((PTLBDP)); \ } while (0) #endif #if ERTS_PTAB_LIST_DBG_DO_TRACE # define ERTS_PTAB_LIST_DBG_TRACE(PID, WHAT) \ erts_fprintf(stderr, "%T %s:%d:%s(): %s\n", \ (PID), __FILE__, __LINE__, __func__, #WHAT) #else # define ERTS_PTAB_LIST_DBG_TRACE(PID, WHAT) #endif #if ERTS_PTAB_LIST_BIF_DEBUGLEVEL != 0 static void debug_ptab_list_assert_error(char* expr, const char* file, int line, const char *func); #endif #if ERTS_PTAB_LIST_BIF_DEBUGLEVEL >= ERTS_PTAB_LIST_DBGLVL_CHK_RESLIST static void debug_ptab_list_check_res_list(Eterm list); #endif #if ERTS_PTAB_LIST_BIF_DEBUGLEVEL >= ERTS_PTAB_LIST_DBGLVL_CHK_PIDS static void debug_ptab_list_save_all_pids(ErtsPTabListBifData *ptlbdp); static void debug_ptab_list_verify_all_pids(ErtsPTabListBifData *ptlbdp); #endif #if ERTS_PTAB_LIST_BIF_DEBUGLEVEL >= ERTS_PTAB_LIST_DBGLVL_CHK_FOUND_PIDS static void debug_ptab_list_check_found_pid(ErtsPTabListBifData *ptlbdp, Eterm pid, Uint64 ic, int pid_should_be_found); #endif #if ERTS_PTAB_LIST_BIF_DEBUGLEVEL >= ERTS_PTAB_LIST_DBGLVL_CHK_DEL_LIST static void debug_ptab_list_check_del_list(ErtsPTab *ptab); static void debug_ptab_list_check_del_free_list(ErtsPTab *ptab, ErtsPTabDeletedElement *ptdep); #endif struct ErtsPTabDeletedElement_ { ErtsPTabDeletedElement *next; ErtsPTabDeletedElement *prev; int ix; union { struct { Eterm id; Uint64 inserted; Uint64 deleted; } element; struct { Uint64 interval; } bif_invocation; } u; }; static Export ptab_list_continue_export; typedef struct { Uint64 interval; } ErtsPTabListBifChunkInfo; typedef enum { INITIALIZING, INSPECTING_TABLE, INSPECTING_DELETED, BUILDING_RESULT, RETURN_RESULT } ErtsPTabListBifState; struct ErtsPTabListBifData_ { ErtsPTab *ptab; ErtsPTabListBifState state; Eterm caller; ErtsPTabListBifChunkInfo *chunk; int tix; int pid_ix; int pid_sz; Eterm *pid; ErtsPTabDeletedElement *bif_invocation; /* Only used when > 1 chunk */ #if ERTS_PTAB_LIST_BIF_DEBUGLEVEL != 0 || ERTS_PTAB_LIST_DBG_DO_TRACE struct { Eterm caller; #if ERTS_PTAB_LIST_BIF_DEBUGLEVEL >= ERTS_PTAB_LIST_DBGLVL_CHK_FOUND_PIDS Uint64 *pid_started; #endif #if ERTS_PTAB_LIST_BIF_DEBUGLEVEL >= ERTS_PTAB_LIST_DBGLVL_CHK_HALLOC Eterm *heap; Uint heap_size; #endif #if ERTS_PTAB_LIST_BIF_DEBUGLEVEL >= ERTS_PTAB_LIST_DBGLVL_CHK_PIDS int correct_pids_verified; Eterm *correct_pids; #endif } debug; #endif }; static ERTS_INLINE void last_data_init_nob(ErtsPTab *ptab, Uint64 val) { erts_atomic64_init_nob(&ptab->vola.tile.last_data, (erts_aint64_t) val); } static ERTS_INLINE void last_data_set_relb(ErtsPTab *ptab, Uint64 val) { erts_atomic64_set_relb(&ptab->vola.tile.last_data, (erts_aint64_t) val); } static ERTS_INLINE Uint64 last_data_read_nob(ErtsPTab *ptab) { return (Uint64) erts_atomic64_read_nob(&ptab->vola.tile.last_data); } static ERTS_INLINE Uint64 last_data_read_acqb(ErtsPTab *ptab) { return (Uint64) erts_atomic64_read_acqb(&ptab->vola.tile.last_data); } static ERTS_INLINE Uint64 last_data_cmpxchg_relb(ErtsPTab *ptab, Uint64 new, Uint64 exp) { return (Uint64) erts_atomic64_cmpxchg_relb(&ptab->vola.tile.last_data, (erts_aint64_t) new, (erts_aint64_t) exp); } static ERTS_INLINE int last_data_cmp(Uint64 ld1, Uint64 ld2) { Uint64 ld1_wrap; if (ld1 == ld2) return 0; ld1_wrap = ld1 + (((Uint64) 1) << 63); if (ld1 < ld1_wrap) return (ld1 < ld2 && ld2 < ld1_wrap) ? -1 : 1; else return (ld1_wrap <= ld2 && ld2 < ld1) ? 1 : -1; } #define ERTS_PTAB_LastData2EtermData(LD) \ ((Eterm) ((LD) & ~(~((Uint64) 0) << ERTS_PTAB_ID_DATA_SIZE))) static ERTS_INLINE Uint32 ix_to_free_id_data_ix(ErtsPTab *ptab, Uint32 ix) { Uint32 dix; dix = ((ix & ptab->r.o.dix_cl_mask) << ptab->r.o.dix_cl_shift); dix += ((ix >> ptab->r.o.dix_cli_shift) & ptab->r.o.dix_cli_mask); ASSERT(0 <= dix && dix < ptab->r.o.max); return dix; } UWord erts_ptab_mem_size(ErtsPTab *ptab) { UWord size = ptab->r.o.max*sizeof(erts_atomic_t); if (ptab->r.o.free_id_data) size += ptab->r.o.max*sizeof(erts_atomic32_t); return size; } void erts_ptab_init_table(ErtsPTab *ptab, ErtsAlcType_t atype, void (*release_element)(void *), ErtsPTabElementCommon *invalid_element, int size, UWord element_size, char *name, int legacy, int atomic_refc) { size_t tab_sz, alloc_sz; Uint32 bits, cl, cli, ix, ix_per_cache_line, tab_cache_lines; char *tab_end; erts_atomic_t *tab_entry; erts_rwmtx_opt_t rwmtx_opts = ERTS_RWMTX_OPT_DEFAULT_INITER; rwmtx_opts.type = ERTS_RWMTX_TYPE_EXTREMELY_FREQUENT_READ; rwmtx_opts.lived = ERTS_RWMTX_LONG_LIVED; erts_rwmtx_init_opt(&ptab->list.data.rwmtx, &rwmtx_opts, name, NIL, ERTS_LOCK_FLAGS_PROPERTY_STATIC | ERTS_LOCK_FLAGS_CATEGORY_GENERIC); erts_atomic32_init_nob(&ptab->vola.tile.count, 0); last_data_init_nob(ptab, ~((Uint64) 0)); /* A size that is a power of 2 is to prefer performance wise */ bits = erts_fit_in_bits_int32(size-1); size = 1 << bits; if (size > ERTS_PTAB_MAX_SIZE) { size = ERTS_PTAB_MAX_SIZE; bits = erts_fit_in_bits_int32((Sint32) size - 1); } ptab->r.o.element_size = element_size; ptab->r.o.max = size; tab_sz = ERTS_ALC_CACHE_LINE_ALIGN_SIZE(size*sizeof(erts_atomic_t)); alloc_sz = tab_sz; if (!legacy) alloc_sz += ERTS_ALC_CACHE_LINE_ALIGN_SIZE(size*sizeof(erts_atomic32_t)); ptab->r.o.tab = erts_alloc_permanent_cache_aligned(atype, alloc_sz); tab_end = ((char *) ptab->r.o.tab) + tab_sz; tab_entry = ptab->r.o.tab; while (tab_end > ((char *) tab_entry)) { erts_atomic_init_nob(tab_entry, ERTS_AINT_NULL); tab_entry++; } tab_cache_lines = tab_sz/ERTS_CACHE_LINE_SIZE; ix_per_cache_line = (ERTS_CACHE_LINE_SIZE/sizeof(erts_atomic_t)); ASSERT((ptab->r.o.max & (ptab->r.o.max - 1)) == 0); /* power of 2 */ ASSERT((ix_per_cache_line & (ix_per_cache_line - 1)) == 0); /* power of 2 */ ASSERT((tab_cache_lines & (tab_cache_lines - 1)) == 0); /* power of 2 */ ptab->r.o.pix_mask = (1 << bits) - 1; ptab->r.o.pix_cl_mask = tab_cache_lines-1; ptab->r.o.pix_cl_shift = erts_fit_in_bits_int32(ix_per_cache_line-1); ptab->r.o.pix_cli_shift = erts_fit_in_bits_int32(ptab->r.o.pix_cl_mask); ptab->r.o.pix_cli_mask = (1 << (bits - ptab->r.o.pix_cli_shift)) - 1; ASSERT(ptab->r.o.pix_cl_shift + ptab->r.o.pix_cli_shift == bits); ptab->r.o.invalid_element = invalid_element; ptab->r.o.invalid_data = erts_ptab_id2data(ptab, invalid_element->id); ptab->r.o.release_element = release_element; ptab->r.o.atomic_refc = atomic_refc; if (legacy) { ptab->r.o.free_id_data = NULL; ptab->r.o.dix_cl_mask = 0; ptab->r.o.dix_cl_shift = 0; ptab->r.o.dix_cli_shift = 0; ptab->r.o.dix_cli_mask = 0; } else { tab_sz = ERTS_ALC_CACHE_LINE_ALIGN_SIZE(size*sizeof(erts_atomic32_t)); ptab->r.o.free_id_data = (erts_atomic32_t *) tab_end; tab_cache_lines = tab_sz/ERTS_CACHE_LINE_SIZE; ix_per_cache_line = (ERTS_CACHE_LINE_SIZE/sizeof(erts_atomic32_t)); ptab->r.o.dix_cl_mask = tab_cache_lines-1; ptab->r.o.dix_cl_shift = erts_fit_in_bits_int32(ix_per_cache_line-1); ptab->r.o.dix_cli_shift = erts_fit_in_bits_int32(ptab->r.o.dix_cl_mask); ptab->r.o.dix_cli_mask = (1 << (bits - ptab->r.o.dix_cli_shift)) - 1; ASSERT((ix_per_cache_line & (ix_per_cache_line - 1)) == 0); /* power of 2 */ ASSERT((tab_cache_lines & (tab_cache_lines - 1)) == 0); /* power of 2 */ ASSERT(ptab->r.o.dix_cl_shift + ptab->r.o.dix_cli_shift == bits); ix = 0; for (cl = 0; cl < tab_cache_lines; cl++) { for (cli = 0; cli < ix_per_cache_line; cli++) { erts_atomic32_init_nob(&ptab->r.o.free_id_data[ix], cli*tab_cache_lines+cl); ASSERT(erts_atomic32_read_nob(&ptab->r.o.free_id_data[ix]) != ptab->r.o.invalid_data); ix++; } } erts_atomic32_init_nob(&ptab->vola.tile.aid_ix, -1); erts_atomic32_init_nob(&ptab->vola.tile.fid_ix, -1); } erts_interval_init(&ptab->list.data.interval); ptab->list.data.deleted.start = NULL; ptab->list.data.deleted.end = NULL; ptab->list.data.chunks = (((ptab->r.o.max - 1) / ERTS_PTAB_LIST_BIF_TAB_CHUNK_SIZE) + 1); if (size == ERTS_PTAB_MAX_SIZE) { int pix; /* * We want a table size of a power of 2 which ERTS_PTAB_MAX_SIZE * is. We only have ERTS_PTAB_MAX_SIZE-1 unique identifiers and * we don't want to shrink the size to ERTS_PTAB_MAX_SIZE/2. * * In order to fix this, we insert a pointer from the table * to the invalid_element, which will be interpreted as a * slot currently being modified. This way we will be able to * have ERTS_PTAB_MAX_SIZE-1 valid elements in the table while * still having a table size of the power of 2. */ erts_atomic32_inc_nob(&ptab->vola.tile.count); pix = erts_ptab_data2pix(ptab, ptab->r.o.invalid_data); erts_atomic_set_relb(&ptab->r.o.tab[pix], (erts_aint_t) ptab->r.o.invalid_element); } } int erts_ptab_initialized(ErtsPTab *ptab) { return ptab->r.o.tab != NULL; } int erts_ptab_new_element(ErtsPTab *ptab, ErtsPTabElementCommon *ptab_el, void *init_arg, void (*init_ptab_el)(void *, Eterm)) { Uint32 pix, ix, data; erts_aint32_t count; erts_aint_t invalid = (erts_aint_t) ptab->r.o.invalid_element; erts_ptab_rlock(ptab); count = erts_atomic32_inc_read_acqb(&ptab->vola.tile.count); if (count > ptab->r.o.max) { while (1) { erts_aint32_t act_count; act_count = erts_atomic32_cmpxchg_relb(&ptab->vola.tile.count, count-1, count); if (act_count == count) { erts_ptab_runlock(ptab); return 0; } count = act_count; if (count <= ptab->r.o.max) break; } } ptab_el->u.alive.started_interval = erts_current_interval_nob(erts_ptab_interval(ptab)); if (ptab->r.o.free_id_data) { do { ix = (Uint32) erts_atomic32_inc_read_acqb(&ptab->vola.tile.aid_ix); ix = ix_to_free_id_data_ix(ptab, ix); data = erts_atomic32_xchg_nob(&ptab->r.o.free_id_data[ix], (erts_aint32_t)ptab->r.o.invalid_data); }while ((Eterm)data == ptab->r.o.invalid_data); init_ptab_el(init_arg, (Eterm) data); if (ptab->r.o.atomic_refc) erts_atomic_init_nob(&ptab_el->refc.atmc, 1); else ptab_el->refc.sint = 1; pix = erts_ptab_data2pix(ptab, (Eterm) data); #ifdef DEBUG ASSERT(ERTS_AINT_NULL == erts_atomic_xchg_relb(&ptab->r.o.tab[pix], (erts_aint_t) ptab_el)); #else erts_atomic_set_relb(&ptab->r.o.tab[pix], (erts_aint_t) ptab_el); #endif erts_ptab_runlock(ptab); } else { int rlocked = ERTS_PTAB_NEW_MAX_RESERVE_FAIL; Uint64 ld, exp_ld; /* Deprecated legacy algorithm... */ restart: ptab_el->u.alive.started_interval = erts_current_interval_nob(erts_ptab_interval(ptab)); ld = last_data_read_acqb(ptab); /* Reserve slot */ while (1) { ld++; pix = erts_ptab_data2pix(ptab, ERTS_PTAB_LastData2EtermData(ld)); if (erts_atomic_read_nob(&ptab->r.o.tab[pix]) == ERTS_AINT_NULL) { erts_aint_t val; val = erts_atomic_cmpxchg_relb(&ptab->r.o.tab[pix], invalid, ERTS_AINT_NULL); if (ERTS_AINT_NULL == val) break; } if (rlocked && --rlocked == 0) { erts_ptab_runlock(ptab); erts_ptab_rwlock(ptab); goto restart; } } data = ERTS_PTAB_LastData2EtermData(ld); if (data == ptab->r.o.invalid_data) { /* Do not use invalid data; fix it... */ ld += ptab->r.o.max; ASSERT(pix == erts_ptab_data2pix(ptab, ERTS_PTAB_LastData2EtermData(ld))); data = ERTS_PTAB_LastData2EtermData(ld); ASSERT(data != ptab->r.o.invalid_data); } exp_ld = last_data_read_nob(ptab); /* Move last data forward */ while (1) { Uint64 act_ld; if (last_data_cmp(ld, exp_ld) < 0) break; act_ld = last_data_cmpxchg_relb(ptab, ld, exp_ld); if (act_ld == exp_ld) break; exp_ld = act_ld; } init_ptab_el(init_arg, data); if (ptab->r.o.atomic_refc) erts_atomic_init_nob(&ptab_el->refc.atmc, 1); else ptab_el->refc.sint = 1; /* Move into slot reserved */ #ifdef DEBUG ASSERT(invalid == erts_atomic_xchg_relb(&ptab->r.o.tab[pix], (erts_aint_t) ptab_el)); #else erts_atomic_set_relb(&ptab->r.o.tab[pix], (erts_aint_t) ptab_el); #endif if (rlocked) erts_ptab_runlock(ptab); else erts_ptab_rwunlock(ptab); } return 1; } static void save_deleted_element(ErtsPTab *ptab, ErtsPTabElementCommon *ptab_el) { ErtsPTabDeletedElement *ptdep = erts_alloc(ERTS_ALC_T_PTAB_LIST_DEL, sizeof(ErtsPTabDeletedElement)); ERTS_PTAB_LIST_ASSERT(ptab->list.data.deleted.start && ptab->list.data.deleted.end); ERTS_LC_ASSERT(erts_lc_ptab_is_rwlocked(ptab)); ERTS_PTAB_LIST_DBG_CHK_DEL_LIST(ptab); ptdep->prev = ptab->list.data.deleted.end; ptdep->next = NULL; ptdep->ix = erts_ptab_id2pix(ptab, ptab_el->id); ptdep->u.element.id = ptab_el->id; ptdep->u.element.inserted = ptab_el->u.alive.started_interval; ptdep->u.element.deleted = erts_current_interval_nob(erts_ptab_interval(ptab)); ptab->list.data.deleted.end->next = ptdep; ptab->list.data.deleted.end = ptdep; ERTS_PTAB_LIST_DBG_CHK_DEL_LIST(ptab); ERTS_PTAB_LIST_ASSERT(ptdep->prev->ix >= 0 ? (ptdep->u.element.deleted >= ptdep->prev->u.element.deleted) : (ptdep->u.element.deleted >= ptdep->prev->u.bif_invocation.interval)); } void erts_ptab_delete_element(ErtsPTab *ptab, ErtsPTabElementCommon *ptab_el) { int maybe_save; Uint32 pix, ix, data; pix = erts_ptab_id2pix(ptab, ptab_el->id); /* *Need* to be an managed thread */ ERTS_LC_ASSERT(erts_thr_progress_is_managed_thread()); erts_ptab_rlock(ptab); maybe_save = ptab->list.data.deleted.end != NULL; if (maybe_save) { erts_ptab_runlock(ptab); erts_ptab_rwlock(ptab); } erts_atomic_set_relb(&ptab->r.o.tab[pix], ERTS_AINT_NULL); if (ptab->r.o.free_id_data) { Uint32 prev_data; /* Next data for this slot... */ data = (Uint32) erts_ptab_id2data(ptab, ptab_el->id); data += ptab->r.o.max; data &= ~(~((Uint32) 0) << ERTS_PTAB_ID_DATA_SIZE); if (data == ptab->r.o.invalid_data) { /* make sure not invalid */ data += ptab->r.o.max; data &= ~(~((Uint32) 0) << ERTS_PTAB_ID_DATA_SIZE); } ASSERT(data != ptab->r.o.invalid_data); ASSERT(pix == erts_ptab_data2pix(ptab, data)); do { ix = (Uint32) erts_atomic32_inc_read_relb(&ptab->vola.tile.fid_ix); ix = ix_to_free_id_data_ix(ptab, ix); prev_data = erts_atomic32_cmpxchg_nob(&ptab->r.o.free_id_data[ix], data, ptab->r.o.invalid_data); }while ((Eterm)prev_data != ptab->r.o.invalid_data); } ASSERT(erts_atomic32_read_nob(&ptab->vola.tile.count) > 0); erts_atomic32_dec_relb(&ptab->vola.tile.count); if (!maybe_save) erts_ptab_runlock(ptab); else { if (ptab->list.data.deleted.end) save_deleted_element(ptab, ptab_el); erts_ptab_rwunlock(ptab); } if (ptab->r.o.release_element) erts_schedule_thr_prgr_later_cleanup_op(ptab->r.o.release_element, (void *) ptab_el, &ptab_el->u.release, ptab->r.o.element_size); } /* * erts_ptab_list() implements BIFs listing the content of the table, * e.g. erlang:processes/0. */ static int cleanup_ptab_list_bif_data(Binary *bp); static int ptab_list_bif_engine(Process *c_p, Eterm *res_accp, Binary *mbp); BIF_RETTYPE erts_ptab_list(Process *c_p, ErtsPTab *ptab) { /* * A requirement: The list of identifiers returned should be a * consistent snapshot of all elements existing * in the table at some point in time during the * execution of the BIF calling this function. * Since elements might be deleted while the BIF * is executing, we have to keep track of all * deleted elements and add them to the result. * We also ignore elements created after the BIF * has begun executing. */ BIF_RETTYPE ret_val; Eterm res_acc = NIL; Binary *mbp = erts_create_magic_binary(sizeof(ErtsPTabListBifData), cleanup_ptab_list_bif_data); ErtsPTabListBifData *ptlbdp = ERTS_MAGIC_BIN_DATA(mbp); ERTS_PTAB_LIST_DBG_TRACE(c_p->common.id, call); ptlbdp->ptab = ptab; ptlbdp->state = INITIALIZING; ERTS_PTAB_LIST_DBG_INIT(c_p, ptlbdp); if (ERTS_BIF_REDS_LEFT(c_p) >= ERTS_PTAB_LIST_BIF_MIN_START_REDS && ptab_list_bif_engine(c_p, &res_acc, mbp)) { erts_bin_free(mbp); ERTS_PTAB_LIST_DBG_CHK_RESLIST(res_acc); ERTS_PTAB_LIST_DBG_TRACE(c_p->common.id, return); ERTS_BIF_PREP_RET(ret_val, res_acc); } else { Eterm *hp; Eterm magic_ref; ERTS_PTAB_LIST_DBG_CHK_RESLIST(res_acc); hp = HAlloc(c_p, ERTS_MAGIC_REF_THING_SIZE); ERTS_PTAB_LIST_DBG_SAVE_HEAP_ALLOC(ptlbdp, hp, ERTS_MAGIC_REF_THING_SIZE); magic_ref = erts_mk_magic_ref(&hp, &MSO(c_p), mbp); ERTS_PTAB_LIST_DBG_VERIFY_HEAP_ALLOC_USED(ptlbdp, hp); ERTS_PTAB_LIST_DBG_TRACE(c_p->common.id, trap); ERTS_BIF_PREP_YIELD2(ret_val, &ptab_list_continue_export, c_p, res_acc, magic_ref); } return ret_val; } static int cleanup_ptab_list_bif_data(Binary *bp) { ErtsPTabListBifData *ptlbdp = ERTS_MAGIC_BIN_DATA(bp); ErtsPTab *ptab = ptlbdp->ptab; ERTS_PTAB_LIST_DBG_TRACE(ptlbdp->debug.caller, call); if (ptlbdp->state != INITIALIZING) { if (ptlbdp->chunk) { erts_free(ERTS_ALC_T_PTAB_LIST_CNKI, ptlbdp->chunk); ptlbdp->chunk = NULL; } if (ptlbdp->pid) { erts_free(ERTS_ALC_T_PTAB_LIST_PIDS, ptlbdp->pid); ptlbdp->pid = NULL; } #if ERTS_PTAB_LIST_BIF_DEBUGLEVEL >= ERTS_PTAB_LIST_DBGLVL_CHK_FOUND_PIDS if (ptlbdp->debug.pid_started) { erts_free(ERTS_ALC_T_PTAB_LIST_PIDS, ptlbdp->debug.pid_started); ptlbdp->debug.pid_started = NULL; } #endif if (ptlbdp->bif_invocation) { ErtsPTabDeletedElement *ptdep; erts_ptab_rwlock(ptab); ERTS_PTAB_LIST_DBG_TRACE(ptlbdp->debug.caller, deleted_cleanup); ptdep = ptlbdp->bif_invocation; ptlbdp->bif_invocation = NULL; ERTS_PTAB_LIST_DBG_CHK_DEL_LIST(ptab); if (ptdep->prev) { /* * Only remove this bif invokation when we * have preceding invokations. */ ptdep->prev->next = ptdep->next; if (ptdep->next) ptdep->next->prev = ptdep->prev; else { /* * At the time of writing this branch cannot be * reached. I don't want to remove this code though * since it may be possible to reach this line * in the future if the cleanup order in * erts_do_exit_process() is changed. The ASSERT(0) * is only here to make us aware that the reorder * has happened. /rickard */ ASSERT(0); ptab->list.data.deleted.end = ptdep->prev; } erts_free(ERTS_ALC_T_PTAB_LIST_DEL, ptdep); } else { /* * Free all elements until next bif invokation * is found. */ ERTS_PTAB_LIST_ASSERT(ptab->list.data.deleted.start == ptdep); do { ErtsPTabDeletedElement *fptdep = ptdep; ptdep = ptdep->next; erts_free(ERTS_ALC_T_PTAB_LIST_DEL, fptdep); } while (ptdep && ptdep->ix >= 0); ptab->list.data.deleted.start = ptdep; if (ptdep) ptdep->prev = NULL; else ptab->list.data.deleted.end = NULL; } ERTS_PTAB_LIST_DBG_CHK_DEL_LIST(ptab); erts_ptab_rwunlock(ptab); } } ERTS_PTAB_LIST_DBG_TRACE(ptlbdp->debug.caller, return); ERTS_PTAB_LIST_DBG_CLEANUP(ptlbdp); return 1; } static int ptab_list_bif_engine(Process *c_p, Eterm *res_accp, Binary *mbp) { ErtsPTabListBifData *ptlbdp = ERTS_MAGIC_BIN_DATA(mbp); ErtsPTab *ptab = ptlbdp->ptab; int have_reds; int reds; int locked = 0; do { switch (ptlbdp->state) { case INITIALIZING: ptlbdp->chunk = erts_alloc(ERTS_ALC_T_PTAB_LIST_CNKI, (sizeof(ErtsPTabListBifChunkInfo) * ptab->list.data.chunks)); ptlbdp->tix = 0; ptlbdp->pid_ix = 0; erts_ptab_rwlock(ptab); locked = 1; ERTS_PTAB_LIST_DBG_TRACE(c_p->common.id, init); ptlbdp->pid_sz = erts_ptab_count(ptab); ptlbdp->pid = erts_alloc(ERTS_ALC_T_PTAB_LIST_PIDS, sizeof(Eterm)*ptlbdp->pid_sz); #if ERTS_PTAB_LIST_BIF_DEBUGLEVEL >= ERTS_PTAB_LIST_DBGLVL_CHK_FOUND_PIDS ptlbdp->debug.pid_started = erts_alloc(ERTS_ALC_T_PTAB_LIST_PIDS, sizeof(Uint64)*ptlbdp->pid_sz); #endif ERTS_PTAB_LIST_DBG_SAVE_PIDS(ptlbdp); if (ptab->list.data.chunks == 1) ptlbdp->bif_invocation = NULL; else { /* * We will have to access the table multiple times * releasing the table lock in between chunks. */ ptlbdp->bif_invocation = erts_alloc(ERTS_ALC_T_PTAB_LIST_DEL, sizeof(ErtsPTabDeletedElement)); ptlbdp->bif_invocation->ix = -1; ptlbdp->bif_invocation->u.bif_invocation.interval = erts_step_interval_nob(erts_ptab_interval(ptab)); ERTS_PTAB_LIST_DBG_CHK_DEL_LIST(ptab); ptlbdp->bif_invocation->next = NULL; if (ptab->list.data.deleted.end) { ptlbdp->bif_invocation->prev = ptab->list.data.deleted.end; ptab->list.data.deleted.end->next = ptlbdp->bif_invocation; ERTS_PTAB_LIST_ASSERT(ptab->list.data.deleted.start); } else { ptlbdp->bif_invocation->prev = NULL; ptab->list.data.deleted.start = ptlbdp->bif_invocation; } ptab->list.data.deleted.end = ptlbdp->bif_invocation; ERTS_PTAB_LIST_DBG_CHK_DEL_LIST(ptab); } ptlbdp->state = INSPECTING_TABLE; /* Fall through */ case INSPECTING_TABLE: { int ix = ptlbdp->tix; int indices = ERTS_PTAB_LIST_BIF_TAB_CHUNK_SIZE; int cix = ix / ERTS_PTAB_LIST_BIF_TAB_CHUNK_SIZE; int end_ix = ix + indices; Uint64 *invocation_interval_p; ErtsPTabElementCommon *invalid_element; invocation_interval_p = (ptlbdp->bif_invocation ? &ptlbdp->bif_invocation->u.bif_invocation.interval : NULL); ERTS_PTAB_LIST_ASSERT(is_nil(*res_accp)); if (!locked) { erts_ptab_rwlock(ptab); locked = 1; } ERTS_LC_ASSERT(erts_lc_ptab_is_rwlocked(ptab)); ERTS_PTAB_LIST_DBG_TRACE(p->common.id, insp_table); if (cix != 0) ptlbdp->chunk[cix].interval = erts_step_interval_nob(erts_ptab_interval(ptab)); else if (ptlbdp->bif_invocation) ptlbdp->chunk[0].interval = *invocation_interval_p; /* else: interval is irrelevant */ if (end_ix >= ptab->r.o.max) { ERTS_PTAB_LIST_ASSERT(cix+1 == ptab->list.data.chunks); end_ix = ptab->r.o.max; indices = end_ix - ix; /* What to do when done with this chunk */ ptlbdp->state = (ptab->list.data.chunks == 1 ? BUILDING_RESULT : INSPECTING_DELETED); } invalid_element = ptab->r.o.invalid_element; for (; ix < end_ix; ix++) { ErtsPTabElementCommon *el; el = (ErtsPTabElementCommon *) erts_ptab_pix2intptr_nob(ptab, ix); if (el && el != invalid_element && (!invocation_interval_p || el->u.alive.started_interval < *invocation_interval_p)) { ERTS_PTAB_LIST_ASSERT(erts_ptab_is_valid_id(el->id)); ptlbdp->pid[ptlbdp->pid_ix] = el->id; #if ERTS_PTAB_LIST_BIF_DEBUGLEVEL >= ERTS_PTAB_LIST_DBGLVL_CHK_FOUND_PIDS ptlbdp->debug.pid_started[ptlbdp->pid_ix] = el->u.alive.started_interval; #endif ptlbdp->pid_ix++; ERTS_PTAB_LIST_ASSERT(ptlbdp->pid_ix <= ptlbdp->pid_sz); } } ptlbdp->tix = end_ix; erts_ptab_rwunlock(ptab); locked = 0; reds = indices/ERTS_PTAB_LIST_BIF_TAB_INSPECT_INDICES_PER_RED; BUMP_REDS(c_p, reds); have_reds = ERTS_BIF_REDS_LEFT(c_p); if (have_reds && ptlbdp->state == INSPECTING_TABLE) { ix = ptlbdp->tix; indices = ERTS_PTAB_LIST_BIF_TAB_CHUNK_SIZE; end_ix = ix + indices; if (end_ix > ptab->r.o.max) { end_ix = ptab->r.o.max; indices = end_ix - ix; } reds = indices/ERTS_PTAB_LIST_BIF_TAB_INSPECT_INDICES_PER_RED; /* Pretend we have no reds left if we haven't got enough reductions to complete next chunk */ if (reds > have_reds) have_reds = 0; } break; } case INSPECTING_DELETED: { int i; int max_reds; int free_deleted = 0; Uint64 invocation_interval; ErtsPTabDeletedElement *ptdep; ErtsPTabDeletedElement *free_list = NULL; ptdep = ptlbdp->bif_invocation; ERTS_PTAB_LIST_ASSERT(ptdep); invocation_interval = ptdep->u.bif_invocation.interval; max_reds = have_reds = ERTS_BIF_REDS_LEFT(c_p); if (max_reds > ERTS_PTAB_LIST_INSPECT_DELETED_MAX_REDS) max_reds = ERTS_PTAB_LIST_INSPECT_DELETED_MAX_REDS; reds = 0; erts_ptab_rwlock(ptab); ERTS_PTAB_LIST_DBG_TRACE(p->common.id, insp_term_procs); ERTS_PTAB_LIST_DBG_CHK_DEL_LIST(ptab); if (ptdep->prev) ptdep->prev->next = ptdep->next; else { ERTS_PTAB_LIST_ASSERT(ptab->list.data.deleted.start == ptdep); ptab->list.data.deleted.start = ptdep->next; if (ptab->list.data.deleted.start && ptab->list.data.deleted.start->ix >= 0) { free_list = ptab->list.data.deleted.start; free_deleted = 1; } } if (ptdep->next) ptdep->next->prev = ptdep->prev; else ptab->list.data.deleted.end = ptdep->prev; ptdep = ptdep->next; i = 0; while (reds < max_reds && ptdep) { if (ptdep->ix < 0) { if (free_deleted) { ERTS_PTAB_LIST_ASSERT(free_list); ERTS_PTAB_LIST_ASSERT(ptdep->prev); ptdep->prev->next = NULL; /* end of free_list */ ptab->list.data.deleted.start = ptdep; ptdep->prev = NULL; free_deleted = 0; } } else { int cix = ptdep->ix/ERTS_PTAB_LIST_BIF_TAB_CHUNK_SIZE; Uint64 chunk_interval = ptlbdp->chunk[cix].interval; Eterm pid = ptdep->u.element.id; ERTS_PTAB_LIST_ASSERT(erts_ptab_is_valid_id(pid)); if (ptdep->u.element.inserted < invocation_interval) { if (ptdep->u.element.deleted < chunk_interval) { ERTS_PTAB_LIST_DBG_CHK_PID_NOT_FOUND( ptlbdp, pid, ptdep->u.element.inserted); ptlbdp->pid[ptlbdp->pid_ix] = pid; #if ERTS_PTAB_LIST_BIF_DEBUGLEVEL >= ERTS_PTAB_LIST_DBGLVL_CHK_FOUND_PIDS ptlbdp->debug.pid_started[ptlbdp->pid_ix] = ptdep->u.element.inserted; #endif ptlbdp->pid_ix++; ERTS_PTAB_LIST_ASSERT(ptlbdp->pid_ix <= ptlbdp->pid_sz); } else { ERTS_PTAB_LIST_DBG_CHK_PID_FOUND( ptlbdp, pid, ptdep->u.element.inserted); } } else { ERTS_PTAB_LIST_DBG_CHK_PID_NOT_FOUND( ptlbdp, pid, ptdep->u.element.inserted); } i++; if (i == ERTS_PTAB_LIST_BIF_INSPECT_DELETED_PER_RED) { reds++; i = 0; } if (free_deleted) reds += ERTS_PTAB_LIST_BIF_TAB_FREE_DELETED_REDS; } ptdep = ptdep->next; } if (free_deleted) { ERTS_PTAB_LIST_ASSERT(free_list); ptab->list.data.deleted.start = ptdep; if (!ptdep) ptab->list.data.deleted.end = NULL; else { ERTS_PTAB_LIST_ASSERT(ptdep->prev); ptdep->prev->next = NULL; /* end of free_list */ ptdep->prev = NULL; } } if (!ptdep) { /* Done */ ERTS_PTAB_LIST_ASSERT(ptlbdp->pid_ix == ptlbdp->pid_sz); ptlbdp->state = BUILDING_RESULT; ptlbdp->bif_invocation->next = free_list; free_list = ptlbdp->bif_invocation; ptlbdp->bif_invocation = NULL; } else { /* Link in bif_invocation again where we left off */ ptlbdp->bif_invocation->prev = ptdep->prev; ptlbdp->bif_invocation->next = ptdep; ptdep->prev = ptlbdp->bif_invocation; if (ptlbdp->bif_invocation->prev) ptlbdp->bif_invocation->prev->next = ptlbdp->bif_invocation; else { ERTS_PTAB_LIST_ASSERT(ptab->list.data.deleted.start == ptdep); ptab->list.data.deleted.start = ptlbdp->bif_invocation; } } ERTS_PTAB_LIST_DBG_CHK_DEL_LIST(ptab); ERTS_PTAB_LIST_DBG_CHK_FREELIST(ptab, free_list); erts_ptab_rwunlock(ptab); /* * We do the actual free of deleted structures now when we * have released the table lock instead of when we encountered * them. This since free() isn't for free and we don't want to * unnecessarily block other schedulers. */ while (free_list) { ptdep = free_list; free_list = ptdep->next; erts_free(ERTS_ALC_T_PTAB_LIST_DEL, ptdep); } have_reds -= reds; if (have_reds < 0) have_reds = 0; BUMP_REDS(c_p, reds); break; } case BUILDING_RESULT: { int conses, ix, min_ix; Eterm *hp; Eterm res = *res_accp; ERTS_PTAB_LIST_DBG_VERIFY_PIDS(ptlbdp); ERTS_PTAB_LIST_DBG_CHK_RESLIST(res); ERTS_PTAB_LIST_DBG_TRACE(p->common.id, begin_build_res); have_reds = ERTS_BIF_REDS_LEFT(c_p); conses = ERTS_PTAB_LIST_BIF_BUILD_RESULT_CONSES_PER_RED*have_reds; min_ix = ptlbdp->pid_ix - conses; if (min_ix < 0) { min_ix = 0; conses = ptlbdp->pid_ix; } if (conses) { hp = HAlloc(c_p, conses*2); ERTS_PTAB_LIST_DBG_SAVE_HEAP_ALLOC(ptlbdp, hp, conses*2); for (ix = ptlbdp->pid_ix - 1; ix >= min_ix; ix--) { ERTS_PTAB_LIST_ASSERT(erts_ptab_is_valid_id(ptlbdp->pid[ix])); res = CONS(hp, ptlbdp->pid[ix], res); hp += 2; } ERTS_PTAB_LIST_DBG_VERIFY_HEAP_ALLOC_USED(ptlbdp, hp); } ptlbdp->pid_ix = min_ix; if (min_ix == 0) ptlbdp->state = RETURN_RESULT; else { ptlbdp->pid_sz = min_ix; ptlbdp->pid = erts_realloc(ERTS_ALC_T_PTAB_LIST_PIDS, ptlbdp->pid, sizeof(Eterm)*ptlbdp->pid_sz); #if ERTS_PTAB_LIST_BIF_DEBUGLEVEL >= ERTS_PTAB_LIST_DBGLVL_CHK_FOUND_PIDS ptlbdp->debug.pid_started = erts_realloc(ERTS_ALC_T_PTAB_LIST_PIDS, ptlbdp->debug.pid_started, sizeof(Uint64) * ptlbdp->pid_sz); #endif } reds = conses/ERTS_PTAB_LIST_BIF_BUILD_RESULT_CONSES_PER_RED; BUMP_REDS(c_p, reds); have_reds -= reds; ERTS_PTAB_LIST_DBG_CHK_RESLIST(res); ERTS_PTAB_LIST_DBG_TRACE(c_p->common.id, end_build_res); *res_accp = res; break; } case RETURN_RESULT: cleanup_ptab_list_bif_data(mbp); return 1; default: erts_exit(ERTS_ABORT_EXIT, "%s:%d:ptab_list_bif_engine(): Invalid state: %d\n", __FILE__, __LINE__, (int) ptlbdp->state); } } while (have_reds || ptlbdp->state == RETURN_RESULT); return 0; } /* * ptab_list_continue/2 is a hidden BIF that the original BIF traps to * if there are too much work to do in one go. */ static BIF_RETTYPE ptab_list_continue(BIF_ALIST_2) { Eterm res_acc; Binary *mbp; /* * This bif cannot be called from erlang code. It can only be * trapped to from other BIFs; therefore, a bad argument * is an internal error and should never occur... */ ERTS_PTAB_LIST_DBG_TRACE(BIF_P->common.id, call); ERTS_PTAB_LIST_ASSERT(is_nil(BIF_ARG_1) || is_list(BIF_ARG_1)); res_acc = BIF_ARG_1; mbp = erts_magic_ref2bin(BIF_ARG_2); ERTS_PTAB_LIST_ASSERT(ERTS_MAGIC_BIN_DESTRUCTOR(mbp) == cleanup_ptab_list_bif_data); ERTS_PTAB_LIST_ASSERT( ((ErtsPTabListBifData *) ERTS_MAGIC_BIN_DATA(mbp))->debug.caller == BIF_P->common.id); if (ptab_list_bif_engine(BIF_P, &res_acc, mbp)) { ERTS_PTAB_LIST_DBG_TRACE(BIF_P->common.id, return); BIF_RET(res_acc); } else { ERTS_PTAB_LIST_DBG_TRACE(BIF_P->common.id, trap); ERTS_BIF_YIELD2(&ptab_list_continue_export, BIF_P, res_acc, BIF_ARG_2); } } void erts_ptab_init(void) { /* ptab_list_continue/2 is a hidden BIF that the original BIF traps to. */ erts_init_trap_export(&ptab_list_continue_export, am_erlang, am_ptab_list_continue, 2, &ptab_list_continue); } /* * Debug stuff */ static void assert_ptab_consistency(ErtsPTab *ptab) { #ifdef DEBUG if (ptab->r.o.free_id_data) { Uint32 ix, pix, data; int free_pids = 0; int null_slots = 0; for (ix=0; ix < ptab->r.o.max; ix++) { if (erts_atomic32_read_nob(&ptab->r.o.free_id_data[ix]) != ptab->r.o.invalid_data) { ++free_pids; data = erts_atomic32_read_nob(&ptab->r.o.free_id_data[ix]); pix = erts_ptab_data2pix(ptab, (Eterm) data); ASSERT(erts_ptab_pix2intptr_nob(ptab, pix) == ERTS_AINT_NULL); } if (erts_atomic_read_nob(&ptab->r.o.tab[ix]) == ERTS_AINT_NULL) { ++null_slots; } } ASSERT(free_pids == null_slots); ASSERT(free_pids == ptab->r.o.max - erts_atomic32_read_nob(&ptab->vola.tile.count)); } #endif } Sint erts_ptab_test_next_id(ErtsPTab *ptab, int set, Uint next) { Uint64 ld; Sint res; Eterm data; int first_pix = -1; erts_ptab_rwlock(ptab); assert_ptab_consistency(ptab); if (ptab->r.o.free_id_data) { Uint32 id_ix, dix; if (set) { Uint32 i, max_ix, num, stop_id_ix; max_ix = ptab->r.o.max - 1; num = next; id_ix = (Uint32) erts_atomic32_read_nob(&ptab->vola.tile.aid_ix); for (i=0; i <= max_ix; ++i) { Uint32 pix; ++num; num &= ~(~((Uint32) 0) << ERTS_PTAB_ID_DATA_SIZE); if (num == ptab->r.o.invalid_data) { num += ptab->r.o.max; num &= ~(~((Uint32) 0) << ERTS_PTAB_ID_DATA_SIZE); } pix = erts_ptab_data2pix(ptab, num); if (ERTS_AINT_NULL == erts_ptab_pix2intptr_nob(ptab, pix)) { ++id_ix; dix = ix_to_free_id_data_ix(ptab, id_ix); erts_atomic32_set_nob(&ptab->r.o.free_id_data[dix], num); ASSERT(pix == erts_ptab_data2pix(ptab, num)); } } erts_atomic32_set_nob(&ptab->vola.tile.fid_ix, id_ix); /* Write invalid_data in rest of free_id_data[]: */ stop_id_ix = (1 + erts_atomic32_read_nob(&ptab->vola.tile.aid_ix)) & max_ix; while (1) { id_ix = (id_ix+1) & max_ix; if (id_ix == stop_id_ix) break; dix = ix_to_free_id_data_ix(ptab, id_ix); erts_atomic32_set_nob(&ptab->r.o.free_id_data[dix], ptab->r.o.invalid_data); } } id_ix = (Uint32) erts_atomic32_read_nob(&ptab->vola.tile.aid_ix) + 1; dix = ix_to_free_id_data_ix(ptab, id_ix); res = (Sint) erts_atomic32_read_nob(&ptab->r.o.free_id_data[dix]); } else { /* Deprecated legacy algorithm... */ if (!set) ld = last_data_read_nob(ptab); else { ld = (Uint64) next; data = ERTS_PTAB_LastData2EtermData(ld); if (ptab->r.o.invalid_data == data) { ld += ptab->r.o.max; ASSERT(erts_ptab_data2pix(ptab, data) == erts_ptab_data2pix(ptab, ERTS_PTAB_LastData2EtermData(ld))); } last_data_set_relb(ptab, ld); } while (1) { int pix; ld++; pix = (int) (ld % ptab->r.o.max); if (first_pix < 0) first_pix = pix; else if (pix == first_pix) { res = -1; break; } if (ERTS_AINT_NULL == erts_ptab_pix2intptr_nob(ptab, pix)) { data = ERTS_PTAB_LastData2EtermData(ld); if (ptab->r.o.invalid_data == data) { ld += ptab->r.o.max; ASSERT(erts_ptab_data2pix(ptab, data) == erts_ptab_data2pix(ptab, ERTS_PTAB_LastData2EtermData(ld))); data = ERTS_PTAB_LastData2EtermData(ld); } res = data; break; } } } assert_ptab_consistency(ptab); erts_ptab_rwunlock(ptab); return res; } static ERTS_INLINE ErtsPTabElementCommon * ptab_pix2el(ErtsPTab *ptab, int ix) { ErtsPTabElementCommon *ptab_el; ASSERT(0 <= ix && ix < ptab->r.o.max); ptab_el = (ErtsPTabElementCommon *) erts_ptab_pix2intptr_nob(ptab, ix); if (ptab_el == ptab->r.o.invalid_element) return NULL; else return ptab_el; } Eterm erts_debug_ptab_list(Process *c_p, ErtsPTab *ptab) { int i; Uint need; Eterm res; Eterm* hp; Eterm *hp_end; erts_ptab_rwlock(ptab); res = NIL; need = erts_ptab_count(ptab) * 2; hp = HAlloc(c_p, need); /* we need two heap words for each id */ hp_end = hp + need; /* make the list by scanning bakward */ for (i = ptab->r.o.max-1; i >= 0; i--) { ErtsPTabElementCommon *el = ptab_pix2el(ptab, i); if (el) { res = CONS(hp, el->id, res); hp += 2; } } erts_ptab_rwunlock(ptab); HRelease(c_p, hp_end, hp); return res; } Eterm erts_debug_ptab_list_bif_info(Process *c_p, ErtsPTab *ptab) { ERTS_DECL_AM(ptab_list_bif_info); Eterm elements[] = { AM_ptab_list_bif_info, make_small((Uint) ERTS_PTAB_LIST_BIF_MIN_START_REDS), make_small((Uint) ptab->list.data.chunks), make_small((Uint) ERTS_PTAB_LIST_BIF_TAB_CHUNK_SIZE), make_small((Uint) ERTS_PTAB_LIST_BIF_TAB_INSPECT_INDICES_PER_RED), make_small((Uint) ERTS_PTAB_LIST_BIF_TAB_FREE_DELETED_REDS), make_small((Uint) ERTS_PTAB_LIST_BIF_INSPECT_DELETED_PER_RED), make_small((Uint) ERTS_PTAB_LIST_INSPECT_DELETED_MAX_REDS), make_small((Uint) ERTS_PTAB_LIST_BIF_BUILD_RESULT_CONSES_PER_RED), make_small((Uint) ERTS_PTAB_LIST_BIF_DEBUGLEVEL) }; Uint sz = 0; Eterm *hp; (void) erts_bld_tuplev(NULL, &sz, sizeof(elements)/sizeof(Eterm), elements); hp = HAlloc(c_p, sz); return erts_bld_tuplev(&hp, NULL, sizeof(elements)/sizeof(Eterm), elements); } #if ERTS_PTAB_LIST_BIF_DEBUGLEVEL >= ERTS_PTAB_LIST_DBGLVL_CHK_FOUND_PIDS static void debug_ptab_list_check_found_pid(ErtsPTabListBifData *ptlbdp, Eterm pid, Uint64 ic, int pid_should_be_found) { int i; for (i = 0; i < ptlbdp->pid_ix; i++) { if (ptlbdp->pid[i] == pid && ptlbdp->debug.pid_started[i] == ic) { ERTS_PTAB_LIST_ASSERT(pid_should_be_found); return; } } ERTS_PTAB_LIST_ASSERT(!pid_should_be_found); } #endif #if ERTS_PTAB_LIST_BIF_DEBUGLEVEL >= ERTS_PTAB_LIST_DBGLVL_CHK_RESLIST static void debug_ptab_list_check_res_list(Eterm list) { while (is_list(list)) { Eterm* consp = list_val(list); Eterm hd = CAR(consp); ERTS_PTAB_LIST_ASSERT(erts_ptab_is_valid_id(hd)); list = CDR(consp); } ERTS_PTAB_LIST_ASSERT(is_nil(list)); } #endif #if ERTS_PTAB_LIST_BIF_DEBUGLEVEL >= ERTS_PTAB_LIST_DBGLVL_CHK_PIDS static void debug_ptab_list_save_all_pids(ErtsPTabListBifData *ptlbdp) { int ix, tix, cpix; ErtsPTab *ptab = ptlbdp->ptab; ptlbdp->debug.correct_pids_verified = 0; ptlbdp->debug.correct_pids = erts_alloc(ERTS_ALC_T_PTAB_LIST_PIDS, sizeof(Eterm)*ptlbdp->pid_sz); for (tix = 0, cpix = 0; tix < ptab->r.o.max; tix++) { ErtsPTabElementCommon *el = ptab_pix2el(ptab, tix); if (el) { ERTS_PTAB_LIST_ASSERT(erts_ptab_is_valid_id(el->id)); ptlbdp->debug.correct_pids[cpix++] = el->id; ERTS_PTAB_LIST_ASSERT(cpix <= ptlbdp->pid_sz); } } ERTS_PTAB_LIST_ASSERT(cpix == ptlbdp->pid_sz); for (ix = 0; ix < ptlbdp->pid_sz; ix++) ptlbdp->pid[ix] = make_small(ix); } static void debug_ptab_list_verify_all_pids(ErtsPTabListBifData *ptlbdp) { int ix, cpix; ERTS_PTAB_LIST_ASSERT(ptlbdp->pid_ix == ptlbdp->pid_sz); for (ix = 0; ix < ptlbdp->pid_sz; ix++) { int found = 0; Eterm pid = ptlbdp->pid[ix]; ERTS_PTAB_LIST_ASSERT(erts_ptab_is_valid_id(pid)); for (cpix = ix; cpix < ptlbdp->pid_sz; cpix++) { if (ptlbdp->debug.correct_pids[cpix] == pid) { ptlbdp->debug.correct_pids[cpix] = NIL; found = 1; break; } } if (!found) { for (cpix = 0; cpix < ix; cpix++) { if (ptlbdp->debug.correct_pids[cpix] == pid) { ptlbdp->debug.correct_pids[cpix] = NIL; found = 1; break; } } } ERTS_PTAB_LIST_ASSERT(found); } ptlbdp->debug.correct_pids_verified = 1; erts_free(ERTS_ALC_T_PTAB_LIST_PIDS, ptlbdp->debug.correct_pids); ptlbdp->debug.correct_pids = NULL; } #endif /* ERTS_PTAB_LIST_BIF_DEBUGLEVEL >= ERTS_PTAB_LIST_DBGLVL_CHK_PIDS */ #if ERTS_PTAB_LIST_BIF_DEBUGLEVEL >= ERTS_PTAB_LIST_DBGLVL_CHK_DEL_LIST static void debug_ptab_list_check_del_list(ErtsPTab *ptab) { ERTS_LC_ASSERT(erts_lc_ptab_is_rwlocked(ptab)); if (!ptab->list.data.deleted.start) ERTS_PTAB_LIST_ASSERT(!ptab->list.data.deleted.end); else { Uint64 curr_interval = erts_current_interval_nob(erts_ptab_interval(ptab)); Uint64 *prev_x_interval_p = NULL; ErtsPTabDeletedElement *ptdep; for (ptdep = ptab->list.data.deleted.start; ptdep; ptdep = ptdep->next) { if (!ptdep->prev) ERTS_PTAB_LIST_ASSERT(ptab->list.data.deleted.start == ptdep); else ERTS_PTAB_LIST_ASSERT(ptdep->prev->next == ptdep); if (!ptdep->next) ERTS_PTAB_LIST_ASSERT(ptab->list.data.deleted.end == ptdep); else ERTS_PTAB_LIST_ASSERT(ptdep->next->prev == ptdep); if (ptdep->ix < 0) { Uint64 interval = ptdep->u.bif_invocation.interval; ERTS_PTAB_LIST_ASSERT(interval <= curr_interval); } else { Uint64 s_interval = ptdep->u.element.inserted; Uint64 x_interval = ptdep->u.element.deleted; ERTS_PTAB_LIST_ASSERT(s_interval <= x_interval); if (prev_x_interval_p) ERTS_PTAB_LIST_ASSERT(*prev_x_interval_p <= x_interval); prev_x_interval_p = &ptdep->u.element.deleted; ERTS_PTAB_LIST_ASSERT( erts_ptab_is_valid_id(ptdep->u.element.id)); ERTS_PTAB_LIST_ASSERT(erts_ptab_id2pix(ptab, ptdep->u.element.id) == ptdep->ix); } } } } static void debug_ptab_list_check_del_free_list(ErtsPTab *ptab, ErtsPTabDeletedElement *free_list) { if (ptab->list.data.deleted.start) { ErtsPTabDeletedElement *fptdep; ErtsPTabDeletedElement *ptdep; for (fptdep = free_list; fptdep; fptdep = fptdep->next) { for (ptdep = ptab->list.data.deleted.start; ptdep; ptdep = ptdep->next) { ERTS_PTAB_LIST_ASSERT(fptdep != ptdep); } } } } #endif #if ERTS_PTAB_LIST_BIF_DEBUGLEVEL != 0 static void debug_ptab_list_assert_error(char* expr, const char* file, int line, const char *func) { fflush(stdout); erts_fprintf(stderr, "%s:%d:%s(): Assertion failed: %s\n", (char *) file, line, (char *) func, expr); fflush(stderr); abort(); } #endif