/**************************************************************** * * * Copyright 2009, 2011 Fidelity Information Services, Inc * * * * This source code contains the intellectual property * * of its copyright holder(s), and is made available * * under a license. If you do not know the terms of * * the license, please stop and do not read further. * * * ****************************************************************/ #include "mdef.h" #include "gtm_stdio.h" #include "gtm_string.h" #include #include "rtnhdr.h" #include "stack_frame.h" #include "op.h" #include "stp_parms.h" #include "lv_val.h" #include "error.h" #include "buddy_list.h" /* needed for tp.h */ #include "hashtab_int4.h" /* needed for tp.h */ #include "gdsroot.h" #include "gdsblk.h" #include "gtm_facility.h" #include "fileinfo.h" #include "gdsbt.h" #include "gdsfhead.h" #include "gdskill.h" #include "gdscc.h" #include "filestruct.h" #include "jnl.h" /* needed for tp.h */ #include "tp.h" #include "tp_frame.h" #include "mv_stent.h" #include "alias.h" #include "gtm_malloc.h" #include "stringpool.h" #include "mmemory.h" /* Define macros locally used by this routine only. General use macros are defined in alias.h */ /* Macro to decrement a base var reference. This lightweight version (of DECR_BASE_REF) used by "als_check_xnew_var_aliases" * is used to bypass most of the the LV_FREESLOT/LV_FLIST_ENQUEUE macro and do only reference count maint since the entire * symtab and all its lv_vals (lv_blks) are going to be released shortly. */ #define DECR_BASE_REF_LIGHT(lvp) \ { /* Perform reference count maintenance for base var */ \ lvTree *lvt_child; \ \ assert(LV_IS_BASE_VAR(lvp)); \ assert(0 < (lvp)->stats.trefcnt); \ DECR_TREFCNT(lvp); \ assert((lvp)->stats.trefcnt >= (lvp)->stats.crefcnt); \ if (0 == (lvp)->stats.trefcnt) \ { \ (lvp)->v.mvtype = 0; \ lvt_child = LV_GET_CHILD(lvp); \ if (lvt_child) \ { \ LV_CHILD(lvp) = NULL; \ als_xnew_killaliasarray(lvt_child); \ } \ } \ } /* Macro to decrement an alias container reference. This lightweight version used by "als_check_xnew_var_aliases" * is used to bypass the DECR_BASE_REF_NOSYM macro invocation (in most cases) and do only reference count maint * since the entire symtab and all its lv_vals (lv_blks) are going to be released shortly. Note this macro is * known to only be used on subscripted nodes hence the additional initial assert. */ #define DECR_AC_REF_LIGHT(LVP) \ { \ symval *lvp_sym, *lvref_sym; \ lv_val *lvp_base; \ \ assert(!LV_IS_BASE_VAR(LVP)); \ if (MV_ALIASCONT & (LVP)->v.mvtype) \ { /* Killing an alias container, perform reference count maintenance */ \ lv_val *lvref = (lv_val *)(LVP)->v.str.addr; \ assert(lvref); \ assert(LV_IS_BASE_VAR(lvref)); \ assert(0 == (LVP)->v.str.len); \ assert(0 < lvref->stats.crefcnt); \ assert(0 < lvref->stats.trefcnt); \ DECR_CREFCNT(lvref); \ lvref_sym = LV_GET_SYMVAL(lvref); \ lvp_base = LV_GET_BASE_VAR(LVP); \ lvp_sym = LV_GET_SYMVAL(lvp_base); \ if (lvref_sym == lvp_sym) \ { /* pointed to lvval owned by popd symval, use light flavor */ \ DECR_BASE_REF_LIGHT(lvref); \ } else \ { /* pointed to lvval owned by other symval, use full flavor */ \ DECR_BASE_REF_NOSYM(lvref, TRUE); \ } \ } \ } /* Macro to mark an lv_val as reachable and process its descendants if any. * Note that like the _CNTNRS_IN_TREE macros, in dbg mode, we will scan the array * for containers even if has_aliascont flag is FALSE. */ #define MARK_REACHABLE(lvp) \ { \ lvTree *lvt; \ symval *sym; \ \ assert((lvp)); \ /* Since this macro can be called in cases where the lv_val is NOT valid, such as in \ * the case an MVST_PVAL mv_stent entry with an mvs_val entry that has been deleted \ * by alias reassignment (see unw_mv_ent), we need to verify we have an actual lv_val \ * by the same methods used to build the lv_val list and only then check if this \ * lv_val has been processed yet. \ */ \ sym = LV_SYMVAL(lvp); \ if ((NULL != sym) && SYM_IS_SYMVAL(sym) && ((lvp)->stats.lvtaskcycle != lvtaskcycle)) \ { /* This lv_val has not been processed yet */ \ DBGRFCT((stderr, "\nMARK_REACHABLE: Marking lv 0x"lvaddr" as reachable\n", \ (lvp))); \ (lvp)->stats.lvtaskcycle = lvtaskcycle; /* Mark it */ \ if ((NULL != (lvt = LV_GET_CHILD(lvp))) PRO_ONLY(&& (lvp)->has_aliascont)) \ { /* And it has descendents to process */ \ DBGRFCT((stderr, "MARK_REACHABLE: Scanning same lv for containers\n")); \ als_prcs_markreached_cntnr(lvt); \ } \ } \ } /* Macro to clone an lv_val */ #define CLONE_LVVAL(oldlv, newlv, cursymval) \ { \ assert(LV_IS_BASE_VAR(oldlv)); \ newlv = lv_getslot(cursymval); \ *newlv = *oldlv; \ LV_SYMVAL(newlv) = cursymval; \ lv_var_clone(newlv, newlv); \ oldlv->v.mvtype = MV_LVCOPIED; \ oldlv->ptrs.copy_loc.newtablv = newlv; \ } /* Macro to initialize a ZWR_ZAV_BLK structure */ #define ZAV_BLK_INIT(zavb, zavbnext) \ (zavb)->next = (zavbnext); \ (zavb)->zav_base = (zavb)->zav_free = (zwr_alias_var *)((char *)(zavb) + SIZEOF(zwr_zav_blk)); \ (zavb)->zav_top = (zwr_alias_var *)((char *)(zavb)->zav_base + (SIZEOF(zwr_alias_var) * ZWR_ZAV_BLK_CNT)); /* Macro to run a given tree looking for container vars. Process what they point to in order to make sure what they point to * doesn't live in the symbol tree being popped. If so, move to the current tree (copying if necessary). If what is being pointed * to was not passed through then it will not be put into the symbol table but will instead just be data pointed to by the * container var. Like the _CNTNRS_IN_TREE macros, in dbg mode, we will scan the array for containers even if has_aliascont * flag is FALSE. */ #define RESOLV_ALIAS_CNTNRS_IN_TREE(LV_BASE, POPDSYMVAL, CURSYMVAL) \ { \ lvTree *lvt; \ \ if ((NULL != (lvt = LV_GET_CHILD(LV_BASE))) && ((LV_BASE)->stats.lvtaskcycle != lvtaskcycle) \ PRO_ONLY(&& (LV_BASE)->has_aliascont)) \ { \ (LV_BASE)->stats.lvtaskcycle = lvtaskcycle; \ als_prcs_xnew_alias_cntnr(lvt, POPDSYMVAL, CURSYMVAL); \ } \ } GBLREF stack_frame *frame_pointer; GBLREF symval *curr_symval; GBLREF unsigned char *msp, *stackbase, *stacktop, *stackwarn; GBLREF mv_stent *mv_chain; GBLREF tp_frame *tp_pointer; GBLREF zwr_hash_table *zwrhtab; GBLREF trans_num local_tn; /* transaction number for THIS PROCESS */ GBLREF uint4 tstartcycle; GBLREF uint4 lvtaskcycle; /* lv_val cycle for misc lv_val related tasks */ GBLREF mstr **stp_array; GBLREF int stp_array_size; GBLREF lv_val *zsrch_var, *zsrch_dir1, *zsrch_dir2; GBLREF tp_frame *tp_pointer; GBLREF int4 SPGC_since_LVGC; /* stringpool GCs since the last dead-data GC */ GBLREF boolean_t suspend_lvgcol; GBLREF lv_xnew_var *xnewvar_anchor; GBLREF lv_xnew_ref *xnewref_anchor; GBLREF mval *alias_retarg; LITREF mname_entry null_mname_entry; /* Local routines -- not made static so they show up in pro core stack traces */ STATICFNDCL void als_xnew_killaliasarray(lvTree *lvt); STATICFNDCL void als_prcs_xnew_alias_cntnr(lvTree *lvt, symval *popdsymval, symval *cursymval); STATICFNDCL void als_prcs_markreached_cntnr(lvTree *lvt); CONDITION_HANDLER(als_check_xnew_var_aliases_ch); /***************************************************************************************/ /* Routine to repair the l_symtab entries in the stack due to hash table expansion such that the * l_symtab entries no longer point to valid hash table entries. * * Note that the "repair" done by this routine depends on the special processing done in * expand_hashtab_mname (EXPAND_HASHTAB rtn in hashtab_implementation.h) which does not free the * old table and places the addresses of the new hash table entries in the the value of the old * hash table entries. This allows this routine to access the old table with the existing * l_symtab entries and pull the new values that should be put in the respective l_symtab * entries before it completes the cleanup and releases the old hash table. * * Operation - Loop through the stack and: * * 1) For each unique l_symtab, run through the entries in the l_symtab. * 2) If entry is null, skip. * 3) If entry falls within range of the old symbol table, load the address in it and verify * that it falls within the range of the new symbol table. * 4) If the entry does not fall within the range of the old symtab: * a) Stop the search as we must have run into an older symtab * b) If debug, assert fail if this is not the first_symbol in this l_symtab we have seen * since an l_symtab can only point to one symtab). * 5) Get new entry address from within the old entry. * 6) Debug only: Assert fail if the new entry address not in range of new symtab. * 7) Note that after procesing the stack to get to the l_symtab entries, we also process the * mv_stent types that contain hash table entry pointers and have to be processed in the same * fashion as the l_symtab entries. This processing saves us the hashtable lookup necessary to * pop NEW'd or parameter values when undoing a stack level and restoring previous values. */ void als_lsymtab_repair(hash_table_mname *table, ht_ent_mname *table_base_orig, int table_size_orig) { int htcnt; boolean_t done; mv_stent *mv_st_ent; ht_ent_mname *table_top_orig, **last_lsym_hte, **htep, *htenew; stack_frame *fp, *fpprev; DEBUG_ONLY(boolean_t first_sym;) assert(table); assert(table_base_orig); assert(table_base_orig != curr_symval->h_symtab.base); table_top_orig = table_base_orig + table_size_orig; last_lsym_hte = NULL; done = FALSE; fp = frame_pointer; assert(frame_pointer); do { /* Once through for each stackframe using the same symbol table. Note this loop is similar * to the stack frame loop in op_clralsvars.c. */ if (fp->l_symtab != last_lsym_hte) { /* Different l_symtab than last time (don't want to update twice) */ last_lsym_hte = fp->l_symtab; if (htcnt = fp->vartab_len) /* Note assignment */ { /* Only process non-zero length l_symtabs */ DEBUG_ONLY(first_sym = TRUE); for (htep = fp->l_symtab; htcnt; --htcnt, ++htep) { if (NULL == *htep) continue; if (*htep < table_base_orig || *htep >= table_top_orig) { /* Entry doesn't point to the current symbol table */ assert(first_sym); done = TRUE; break; } htenew = (ht_ent_mname *)((*htep)->value); /* Pick up entry we should now use */ assert(htenew >= table->base && htenew < (table->base + table->size)); *htep = htenew; DEBUG_ONLY(first_sym = FALSE); } } } fpprev = fp; fp = fp->old_frame_pointer; if (done) break; if (SFF_CI & fpprev->flags) { /* Callins needs to be able to crawl past apparent end of stack to earlier stack segments. * We should be in the base frame now. See if an earlier frame exists. * Note we don't worry about trigger base frames here because triggers *always* have a * different symbol table - previous symbol tables and stack levels are not affected. */ fp = *(stack_frame **)(fp + 1); /* Backups up to "prev pointer" created by base_frame() */ if (NULL == fp || fp >= (stack_frame *)stackbase || fp < (stack_frame *)stacktop) break; /* Pointer not within the stack -- must be earliest occurence */ } } while(fp); /* Next, check the mv_stents for the stackframes we processed. Certain mv_stents also have hash * table references in them that need repair. */ for (mv_st_ent = mv_chain; mv_st_ent < (mv_stent *)(fp ? fp : fpprev); /* Last stack frame actually processed */ mv_st_ent = (mv_stent *)(mv_st_ent->mv_st_next + (char *)mv_st_ent)) { switch (mv_st_ent->mv_st_type) { /* The types processed here contain hash table pointers that need to be modified */ case MVST_NTAB: htep = &mv_st_ent->mv_st_cont.mvs_ntab.hte_addr; break; case MVST_PVAL: htep = &mv_st_ent->mv_st_cont.mvs_pval.mvs_ptab.hte_addr; break; case MVST_NVAL: htep = &mv_st_ent->mv_st_cont.mvs_nval.mvs_ptab.hte_addr; break; default: continue; } if (NULL == *htep) continue; if (*htep < table_base_orig || *htep >= table_top_orig) /* Entry doesn't point to the current symbol table so ignore it since it didn't change */ continue; htenew = (ht_ent_mname *)((*htep)->value); /* Pick up entry we should now use */ assert(htenew >= table->base && htenew < (table->base + table->size)); *htep = htenew; } /* For debug at least make unusable in case any stragglers point to it -- even though we are somewhat duplicating * the SmInitAlloc gtmdgblvl flag here, this is so critical for debugging we want to do this even when general * checking is not being done. SE 09/2008 */ DEBUG_ONLY(memset(table_base_orig, 0xfe, table_size_orig * SIZEOF(ht_ent_mname))); } /* Local routine! condition handler whose sole function is to turn off the flag that says we are in "als_check_xnew_var_alias" */ CONDITION_HANDLER(als_check_xnew_var_aliases_ch) { START_CH; suspend_lvgcol = FALSE; NEXTCH; } /* When an xNEW'd symtab is popped, and there are alias concerns, this routine is called to make things right. Things that * can be wrong: * 1) If a variable was passed through to the new symtab and then was aliased to a var that belonged to the new symbol table, * the lv_val in the old symtab was released and a new one assigned in the new symbol table. We have to: * a) copy that value back to the previous symtab and * b) we have to fix the reference count since the alias owned by the new symtab is going away. * 2) If a variable is passed through to the new symtab and within that variable an alias container is created that points * to a var in the newer symtab we need to copy that var/array back to the older symtab so the value remains. * 3) This gets more interesting to deal with when that var is also aliased by a passed through variable (combining issues 1 & 2). * * Operation: * 1) When the new symtab was created by op_xnew, if there were variables that were passed through, they are recorded in the * symtab chained from xnew_var_list. * 2) Go through that list of variables, lookup each in the popped symbol table and change the hash table entries to deleted * so those entries are not found in subsequent scans. * 3) Do a more simplistic kill-alias-all in the popped symtab. This involves the following: * a) Run the hash table looking for aliased variables. If found, do the reference count maintenance but don't worry about * deleting any data since it will all go away after we return and unw_mv_ent() releases the symbol table and lv_blk chains. * b) While running the hash table, run any variable trees we find anchored. * 4) Go through the list of forwarded vars again (xnew_var_list) in the popped symval and see if any of the lv_vals are owned * by the symval being popped. If they are, then the lv_vals involved need to be copied to lv_vals owned by the (now) * current symtab because the popped symtab's lv_vals will be released by unw_mv_ent() when we return. Note that this also * involves going through the tree of these vars in case any container vars point to an array that is not being dealt with * in some fashion by one of the other vars we passed through. We avoid processing arrays multiple times by marking them * with an incremented lvtaskval. * 5) Go through the list of referenced lv_vals (via containers of xnew_var_list vars) by traversing the xnew_ref_list if it * exists. These vars were recorded because we may not be able to get to them still just by traversing the reference list vars * so they were pre-recorded so we could scan the worst case of vars that could have containers pointing to the symtab about * to be popped. * 6) If a pending alias return value exists, check it to see if it also needs to be processed. * * Note: to prevent re-scanning of already scanned array, this routine uses the lvtaskcycle value. To do this, we use the * suspend_lvgcol global variable to tell "stp_gcol" to not do LVGC processing (which also uses lvtaskcycle). */ void als_check_xnew_var_aliases(symval *popdsymval, symval *cursymval) { lv_xnew_var *xnewvar, *xnewvar_next; lv_xnew_ref *xnewref, *xnewref_next; ht_ent_mname *tabent; hash_table_mname *popdsymtab, *cursymtab; ht_ent_mname *htep, *htep_top; lv_val *lv, *prevlv, *currlv, *popdlv; lv_val *newlv, *oldlv; boolean_t bypass_lvscan, bypass_lvrepl; DBGRFCT_ONLY(mident_fixed vname;) ESTABLISH(als_check_xnew_var_aliases_ch); suspend_lvgcol = TRUE; assert(NULL != popdsymval); assert(NULL != cursymval); assert((NULL != popdsymval->xnew_var_list) || (NULL != alias_retarg)); DBGRFCT((stderr, "\nals_check_xnew_var_aliases: Beginning xvar pop processing\n")); /* Step 2: (step 1 done in op_xnew()) - Run the list of vars that were passed through the xnew and remove them * from the popped hash table so they can not be found by the step 3 scan below - meaning we won't mess with the * reference counts of these entries but we will record their locations so we can process them in step 4. */ popdsymtab = &popdsymval->h_symtab; cursymtab = &cursymval->h_symtab; for (xnewvar = popdsymval->xnew_var_list; xnewvar; xnewvar = xnewvar->next) { tabent = lookup_hashtab_mname(popdsymtab, &xnewvar->key); assert(tabent); xnewvar->lvval = (lv_val *)tabent->value; /* Cache lookup results for 2nd pass in step 4 */ delete_hashtab_ent_mname(popdsymtab, tabent); } /* Step 3: Run popped hash table undoing alias references. */ DBGRFCT((stderr, "als_check_xnew_var_aliases: Step 3 - running popped symtab tree undoing local refcounts\n")); for (htep = popdsymtab->base, htep_top = popdsymtab->top; htep < htep_top; htep++) { if (HTENT_VALID_MNAME(htep, lv_val, lv)) DECR_BASE_REF_LIGHT(lv); } /* Step 4: See what, if anything, needs to be copied from popped level to current level. There are 3 possible * cases here. Note in all cases, we must decrement the use counters of prevlv since they were incremented in * op_xnew to keep things from disappearing prematurely (we don't want the LVs we saved to be scrapped and re-used * so we make sure they stay around). * * Vars used: * * prevlv == lv from the current symbol table. * currlv == lv we are going to eventually put into the current symbol table * popdlv == lv from the popped symbol table * * Cases follow: * * Condition Action * * a) prevlv == popdlv Scan prevlv for alias containers pointing to popped symtab. * b) prevlv != popdlv && popdlv in popd symtab. Clone popdlv into currlv & do alias container scan. * c) prevlv != popdlv && popdlv not in popd symtab. Same as case (a). Note this includes the case where popdlv * already resides in cursymtab. */ DBGRFCT((stderr, "als_check_xnew_var_aliases: Step 4 - beginning unwind scan of passed through vars\n")); INCR_LVTASKCYCLE; for (xnewvar = popdsymval->xnew_var_list; xnewvar; xnewvar = xnewvar_next) { bypass_lvscan = bypass_lvrepl = FALSE; tabent = lookup_hashtab_mname(cursymtab, &xnewvar->key); assert(tabent); /* Had better be there since it was passed in thru the exclusive new */ DBGRFCT_ONLY( memcpy(vname.c, tabent->key.var_name.addr, tabent->key.var_name.len); vname.c[tabent->key.var_name.len] = '\0'; ); prevlv = (lv_val *)tabent->value; popdlv = xnewvar->lvval; /* Value of this var in popped symtab */ DBGRFCT((stderr, "als_check_xnew_var_aliases: var '%s' prevlv: 0x"lvaddr" popdlv: 0x"lvaddr"\n", &vname.c, prevlv, popdlv)); if (prevlv == popdlv) { /* Case (a) - Just do the scan below */ currlv = prevlv; bypass_lvrepl = TRUE; } else if (popdsymval == LV_GET_SYMVAL(popdlv)) { /* Case (b) - Clone the var and tree into blocks owned by current symtab with the caveat that we need not * do this if the array has already been cloned (because more than one var that was passed through it * pointing to it. */ if (MV_LVCOPIED == popdlv->v.mvtype) { /* This lv_val has been copied over already so use that pointer instead to put into the * current hash table. */ currlv = popdlv->ptrs.copy_loc.newtablv; assert(LV_GET_SYMVAL(currlv) == cursymval); bypass_lvscan = TRUE; /* lv_val would have already been scanned */ DBGRFCT((stderr, "als_check_xnew_var_aliases: lv already copied so setting currlv to 0x"lvaddr"\n", currlv)); } else { assert(LV_IS_BASE_VAR(popdlv)); /* lv_val is owned by the popped symtab .. clone it to the new current tree */ CLONE_LVVAL(popdlv, currlv, cursymval); DBGRFCT((stderr, "als_check_xnew_var_aliases: lv has been cloned from 0x"lvaddr" to 0x"lvaddr"\n", popdlv, currlv)); } } else { /* Case (c) - same as (a) except we do replace the lv in cursymtab */ assert(LV_IS_BASE_VAR(popdlv)); currlv = popdlv; } if (!bypass_lvscan) { /* Need to run this tree (if any) to look for container vars buried within */ DBGRFCT((stderr, "als_check_xnew_var_aliases: potentially scanning lv 0x"lvaddr"\n", currlv)); RESOLV_ALIAS_CNTNRS_IN_TREE(currlv, popdsymval, cursymval); } if (1 < prevlv->stats.trefcnt) /* If prevlv is going to be around after we drop op_xnew's refcnt bumps, make sure it gets processed. * If it was processed above, then it is marked such and the macro will bypass processing it again. */ RESOLV_ALIAS_CNTNRS_IN_TREE(prevlv, popdsymval, cursymval); DECR_CREFCNT(prevlv); /* undo bump by op_xnew */ if (!bypass_lvrepl) { /* Replace the lvval in the current symbol table */ DBGRFCT((stderr, "als_check_xnew_var_aliases: Resetting variable '%s' hte at 0x"lvaddr" from 0x"lvaddr " to 0x"lvaddr"\n", &vname.c, tabent, prevlv, currlv)); tabent->value = (void *)currlv; } assert(1 <= prevlv->stats.trefcnt); /* verify op_xnew's bump is still there (may be only one) */ DECR_BASE_REF_NOSYM(prevlv, TRUE); /* undo bump by op_xnew */ xnewvar_next = xnewvar->next; xnewvar->next = xnewvar_anchor; xnewvar_anchor = xnewvar; } /* Step 5: See if anything on the xnew_ref_list needs to be handled */ DBGRFCT((stderr, "als_check_xnew_var_aliases: Step 5: Process xnew_ref_list if any\n")); for (xnewref = popdsymval->xnew_ref_list; xnewref; xnewref = xnewref_next) { prevlv = xnewref->lvval; DBGRFCT((stderr, "als_check_xnew_var_aliases: xnewref-prevlv: 0x"lvaddr"\n", prevlv)); DECR_CREFCNT(prevlv); /* Will remove the trefcnt in final desposition below */ /* Only do the scan if the reference count is greater than 1 since we are going to remove the * refcnts added by op_xnew as we finish here. So if the var is going away anyway, no need * to scan. */ if (1 < prevlv->stats.trefcnt) { /* Process the array */ DBGRFCT((stderr, "als_check_xnew_var_aliases: potentially scanning lv 0x"lvaddr"\n", prevlv)); RESOLV_ALIAS_CNTNRS_IN_TREE(prevlv, popdsymval, cursymval); } else DBGRFCT((stderr, "als_check_xnew_var_aliases: prevlv was deleted\n")); /* Remove refcnt and we are done */ DECR_BASE_REF_NOSYM(prevlv, TRUE); xnewref_next = xnewref->next; xnewref->next = xnewref_anchor; xnewref_anchor = xnewref; } /* Step 6: Check if a pending alias return value exists and if so if it needs to be processed. * This type of value is created by unw_retarg() as the result of a "QUIT *" statement. It is an alias container * mval that lives in the compiler temps of the caller with a pointer in an mv_stent of the callee in the mvs_parm * block allocated by push_parm. Since this mval-container is just an mval and not an lv_val, we have to largely * do similar processing to the "als_prcs_xnew_alias_cntnr" with this block type difference in mind. */ if (NULL != alias_retarg) { assert(0 != (MV_ALIASCONT & alias_retarg->mvtype)); oldlv = (lv_val *)alias_retarg->str.addr; if (MV_LVCOPIED == oldlv->v.mvtype) { /* This lv_val has been copied over already so use that pointer instead */ newlv = oldlv->ptrs.copy_loc.newtablv; alias_retarg->str.addr = (char *)newlv; /* Replace container ptr */ DBGRFCT((stderr, "\nals_check_xnew_var_aliases: alias retarg var found - referenced array already copied" " - Setting pointer in aliascont mval 0x"lvaddr" to lv 0x"lvaddr"\n", alias_retarg, newlv)); } else { assert(LV_IS_BASE_VAR(oldlv)); if (popdsymval == LV_SYMVAL(oldlv)) { /* lv_val is owned by the popped symtab .. clone it to the new current tree */ CLONE_LVVAL(oldlv, newlv, cursymval); alias_retarg->str.addr = (char *)newlv; /* Replace container ptr */ DBGRFCT((stderr, "\nals_check_xnew_var_aliases: alias retarg var found - aliascont mval 0x"lvaddr " being reset to point to lv 0x"lvaddr" which is a clone of lv 0x"lvaddr"\n", alias_retarg, newlv, oldlv)); } else { /* lv_val is owned by current or older symval .. just use it in the subsequent scan in case it * leads us to other lv_vals owned by the popped symtab. */ DBGRFCT((stderr, "\nals_check_xnew_var_aliases: alias retarg var found - aliascont mval 0x"lvaddr " just being (potentially) scanned for container vars\n", alias_retarg)); newlv = oldlv; } RESOLV_ALIAS_CNTNRS_IN_TREE(newlv, popdsymval, cursymval); } } DBGRFCT((stderr, "als_check_xnew_var_aliases: Completed xvar pop processing\n")); suspend_lvgcol = FALSE; REVERT; } /* Local routine! * This routine is basically a slightly lightweight lv_killarray() that goes through a given tree looking for container vars * and performing the necessary reference count cleanup as well as freeing the lv tree nodes but wont go through the * bother of hashtable maintenance since the hashtable is anyways going away as part of the symbol table pop. */ STATICFNDEF void als_xnew_killaliasarray(lvTree *lvt) { lvTreeNode *node, *nextnode; lvTree *tmplvt; DEBUG_ONLY( lv_val *lv; assert(NULL != lvt); lv = (lv_val *)LVT_PARENT(lvt); assert(NULL == LV_CHILD(lv)); /* Owner lv's children pointer MUST be NULL! */ /* See comment in lv_killarray for why this is necessary */ ) /* Iterate through the tree in post-order fashion. Doing it in-order or pre-order has issues since we would have * freed up nodes in the tree but would need to access links in them to get at the NEXT node. */ for (node = lvAvlTreeFirstPostOrder(lvt); NULL != node; node = nextnode) { nextnode = lvAvlTreeNextPostOrder(node); /* determine "nextnode" before freeing "node" */ assert(NULL != node); tmplvt = LV_CHILD(node); if (NULL != tmplvt) { LV_CHILD(node) = NULL; als_xnew_killaliasarray(tmplvt); } DECR_AC_REF_LIGHT(((lv_val *)node)); /* Decrement alias contain ref and cleanup if necessary */ /* If node points to an "lv_val", we need to do a heavyweight LV_FREESLOT call to free up the lv_val. * But we instead do a simple "LVTREENODE_FREESLOT" call because we are guaranteed node points to a "lvTreeNode" * (i.e. it is a subscripted lv and never the base lv). Assert that. */ assert(!LV_IS_BASE_VAR(node)); LVTREENODE_FREESLOT(node); } LVTREE_FREESLOT(lvt); } /* Local routine! * Routine to process an alias container found in a node of a var being "returned" back through an exclusive new. * We may have to move the data. */ STATICFNDEF void als_prcs_xnew_alias_cntnr(lvTree *lvt, symval *popdsymval, symval *cursymval) { lvTree *lvt_child; lvTreeNode *node; lv_val *newlv, *oldlv; assert(NULL != lvt); /* caller should not call if no subtree */ /* In the case of lv_killarray, the only option we have is to do post-order traversal since we are freeing * nodes in the tree as we traverse. But in this case, we dont change the tree structure so we are free to * choose any order. We choose in-order as that is faster than post-order. */ for (node = lvAvlTreeFirst(lvt); NULL != node; node = lvAvlTreeNext(node)) { if (node->v.mvtype & MV_ALIASCONT) { assert(lvt->base_lv->has_aliascont); assert(!LV_IS_BASE_VAR(node)); oldlv = (lv_val *)node->v.str.addr; assert(NULL != oldlv); assert(LV_IS_BASE_VAR(oldlv)); if (MV_LVCOPIED == oldlv->v.mvtype) { /* This lv_val has been copied over already so use that pointer instead */ newlv = oldlv->ptrs.copy_loc.newtablv; assert(LV_IS_BASE_VAR(newlv)); node->v.str.addr = (char *)newlv; /* Replace container ptr */ DBGRFCT((stderr, "\nals_prcs_xnew_alias_cntnr: aliascont var found - referenced array already " "copied - Setting pointer in aliascont lv 0x"lvaddr" to lv 0x"lvaddr"\n", node, newlv)); } else { assert(LV_IS_BASE_VAR(oldlv)); if (popdsymval == LV_SYMVAL(oldlv)) { /* lv_val is owned by the popped symtab .. clone it to the new current tree */ CLONE_LVVAL(oldlv, newlv, cursymval); assert(LV_IS_BASE_VAR(newlv)); node->v.str.addr = (char *)newlv; /* Replace container ptr */ DBGRFCT((stderr, "\nals_prcs_xnew_alias_cntnr: aliascont var found - aliascont lv 0x"lvaddr " being reset to point to lv 0x"lvaddr" which is a clone of lv 0x"lvaddr"\n", node, newlv, oldlv)); } else { /* lv_val is owned by current or older symval .. just use it in the subsequent scan in case * it leads us to other lv_vals owned by the popped symtab. */ DBGRFCT((stderr, "\nals_prcs_xnew_alias_cntnr: aliascont var found - aliascont lv 0x"lvaddr " just being (potentially) scanned for container vars\n", node)); newlv = oldlv; } RESOLV_ALIAS_CNTNRS_IN_TREE(newlv, popdsymval, cursymval); } } lvt_child = LV_GET_CHILD(node); if (NULL != lvt_child) /* Descend recursively down this tree as well */ als_prcs_xnew_alias_cntnr(lvt_child, popdsymval, cursymval); } } /* Routine to process an alias container found in an array being "saved" by TSTART (op_tstart). We need to set this array up * so it gets copied just like op_tstart does for the base variables that are specified in it. In addition, this new array * itself needs to be scanned so if it points to anything, that too gets saved if modified (all handled by * TP_SAVE_RESTART_VAR macro). */ void als_prcs_tpsav_cntnr(lvTree *lvt) { lvTree *lvt_child; lvTreeNode *node; lv_val *cntnr_lv_base; assert(NULL != lvt); /* caller should not call if no subtree */ /* In the case of lv_killarray, the only option we have is to do post-order traversal since we are freeing * nodes in the tree as we traverse. But in this case, we dont change the tree structure so we are free to * choose any order. We choose in-order as that is faster than post-order. */ for (node = lvAvlTreeFirst(lvt); NULL != node; node = lvAvlTreeNext(node)) { if (node->v.mvtype & MV_ALIASCONT) { assert(lvt->base_lv->has_aliascont); assert(!LV_IS_BASE_VAR(node)); cntnr_lv_base = (lv_val *)node->v.str.addr; /* Extract container pointer */ assert(NULL != cntnr_lv_base); assert(LV_IS_BASE_VAR(cntnr_lv_base)); assert(1 <= cntnr_lv_base->stats.trefcnt); assert(1 <= cntnr_lv_base->stats.crefcnt); if (NULL == cntnr_lv_base->tp_var) { /* Save this var if it hasn't already been saved */ assert(cntnr_lv_base->stats.tstartcycle != tstartcycle); DBGRFCT((stderr, "\ntpSAV_container: Container at 0x"lvaddr " refers to lv 0x"lvaddr" -- Creating tpsav block\n", node, cntnr_lv_base)); TP_SAVE_RESTART_VAR(cntnr_lv_base, tp_pointer, &null_mname_entry); INCR_CREFCNT(cntnr_lv_base); /* 2nd increment for reference via a container node */ INCR_TREFCNT(cntnr_lv_base); if (LV_HAS_CHILD(cntnr_lv_base)) TPSAV_CNTNRS_IN_TREE(cntnr_lv_base); } else { /* If not saving it, we still need to bump the ref count(s) for this reference and * process any children if we have't already seen this node (taskcycle check will tell us this). */ DBGRFCT((stderr, "\ntpSAV_container: Container at 0x"lvaddr" refers to lv 0x"lvaddr " -- Incrementing refcnts\n", node, cntnr_lv_base)); INCR_CREFCNT(cntnr_lv_base); INCR_TREFCNT(cntnr_lv_base); assert(0 < cntnr_lv_base->stats.trefcnt); assert(0 < cntnr_lv_base->stats.crefcnt); if (cntnr_lv_base->stats.tstartcycle != tstartcycle) { DBGRFCT((stderr, "\ntpSAV_container: .. Container at 0x"lvaddr" refers to lv 0x"lvaddr " -- processing tree\n", node, cntnr_lv_base)); if (LV_HAS_CHILD(cntnr_lv_base)) TPSAV_CNTNRS_IN_TREE(cntnr_lv_base); } else { DBGRFCT((stderr, "\ntpSAV_container: .. Container at 0x"lvaddr" refers to lv 0x"lvaddr " -- Already processed -- bypassing\n", node, cntnr_lv_base)); } } } lvt_child = LV_GET_CHILD(node); if (NULL != lvt_child) /* Descend recursively down this tree as well */ als_prcs_tpsav_cntnr(lvt_child); } } /* For a given container var found in the tree we need to re-establish the reference counts for the base var * the container is pointing to. Used during a local var restore on a TP restart. */ void als_prcs_tprest_cntnr(lvTree *lvt) { lvTree *lvt_child; lvTreeNode *node; lv_val *cntnr_lv_base; assert(NULL != lvt); /* caller should not call if no subtree */ /* In the case of lv_killarray, the only option we have is to do post-order traversal since we are freeing * nodes in the tree as we traverse. But in this case, we dont change the tree structure so we are free to * choose any order. We choose in-order as that is faster than post-order. */ for (node = lvAvlTreeFirst(lvt); NULL != node; node = lvAvlTreeNext(node)) { if (node->v.mvtype & MV_ALIASCONT) { assert(lvt->base_lv->has_aliascont); assert(!LV_IS_BASE_VAR(node)); cntnr_lv_base = (lv_val *)node->v.str.addr; /* Extract container pointer */ assert(NULL != cntnr_lv_base); assert(LV_IS_BASE_VAR(cntnr_lv_base)); assert(1 <= cntnr_lv_base->stats.trefcnt); assert(1 <= cntnr_lv_base->stats.crefcnt); assert(cntnr_lv_base->tp_var); DBGRFCT((stderr, "\ntpREST_cntnr_node: Processing container at 0x"lvaddr"\n", node)); INCR_CREFCNT(cntnr_lv_base); INCR_TREFCNT(cntnr_lv_base); assert(0 < (cntnr_lv_base)->stats.trefcnt); assert(0 < (cntnr_lv_base)->stats.crefcnt); } lvt_child = LV_GET_CHILD(node); if (NULL != lvt_child) /* Descend recursively down this tree as well */ als_prcs_tprest_cntnr(lvt_child); } } /* For a given container, decrement the ref count of the creature it points to. Part of unwinding an unmodified * tp saved variable. */ void als_prcs_tpunwnd_cntnr(lvTree *lvt) { lvTree *lvt_child; lvTreeNode *node; lv_val *cntnr_lv_base; assert(NULL != lvt); /* caller should not call if no subtree */ /* In the case of lv_killarray, the only option we have is to do post-order traversal since we are freeing * nodes in the tree as we traverse. But in this case, we dont change the tree structure so we are free to * choose any order. We choose in-order as that is faster than post-order. */ for (node = lvAvlTreeFirst(lvt); NULL != node; node = lvAvlTreeNext(node)) { if (node->v.mvtype & MV_ALIASCONT) { assert(lvt->base_lv->has_aliascont); assert(!LV_IS_BASE_VAR(node)); cntnr_lv_base = (lv_val *)node->v.str.addr; /* Extract container pointer */ assert(NULL != cntnr_lv_base); assert(LV_IS_BASE_VAR(cntnr_lv_base)); assert(1 <= cntnr_lv_base->stats.trefcnt); assert(1 <= cntnr_lv_base->stats.crefcnt); /* Note we cannot assert cntnr_lv_base->tp_var here since the tp_var node may have already been freed and * cleared by unwind processing of the base var itself. We just have to undo our counts here and keep going. */ DBGRFCT((stderr, "\ntpUNWND_cntnr_node: Processing container at 0x"lvaddr"\n", cntnr_lv_base)); DECR_CREFCNT(cntnr_lv_base); DECR_BASE_REF_NOSYM(cntnr_lv_base, FALSE); } lvt_child = LV_GET_CHILD(node); if (NULL != lvt_child) /* Descend recursively down this tree as well */ als_prcs_tpunwnd_cntnr(lvt_child); } } /* This routine deletes the data pointed to by the lv_val and removes the container flag from the value making it just * a regular NULL/0 value. */ void als_prcs_kill_cntnr(lvTree *lvt) { lvTree *lvt_child; lvTreeNode *node; lv_val *cntnr_lv_base; assert(NULL != lvt); /* caller should not call if no subtree */ /* In the case of lv_killarray, the only option we have is to do post-order traversal since we are freeing * nodes in the tree as we traverse. But in this case, we dont change the tree structure so we are free to * choose any order. We choose in-order as that is faster than post-order. */ for (node = lvAvlTreeFirst(lvt); NULL != node; node = lvAvlTreeNext(node)) { if (node->v.mvtype & MV_ALIASCONT) { assert(lvt->base_lv->has_aliascont); assert(!LV_IS_BASE_VAR(node)); cntnr_lv_base = (lv_val *)node->v.str.addr; assert(NULL != cntnr_lv_base); assert(LV_IS_BASE_VAR(cntnr_lv_base)); node->v.mvtype &= ~MV_ALIASCONT; DECR_CREFCNT(cntnr_lv_base); DECR_BASE_REF_NOSYM(cntnr_lv_base, TRUE); } lvt_child = LV_GET_CHILD(node); if (NULL != lvt_child) /* Descend recursively down this tree as well */ als_prcs_kill_cntnr(lvt_child); } } /* Local routine! * This routine checks if the supplied container points to an lv_val that is already marked as having been processd in this pass. * If not, the lv_val is marked and processed recursively. */ STATICFNDEF void als_prcs_markreached_cntnr(lvTree *lvt) { lvTree *lvt_child; lvTreeNode *node; lv_val *cntnr_lv_base; assert(NULL != lvt); /* caller should not call if no subtree */ /* In the case of lv_killarray, the only option we have is to do post-order traversal since we are freeing * nodes in the tree as we traverse. But in this case, we dont change the tree structure so we are free to * choose any order. We choose in-order as that is faster than post-order. */ for (node = lvAvlTreeFirst(lvt); NULL != node; node = lvAvlTreeNext(node)) { if (node->v.mvtype & MV_ALIASCONT) { assert(lvt->base_lv->has_aliascont); assert(!LV_IS_BASE_VAR(node)); cntnr_lv_base = (lv_val *)node->v.str.addr; assert(NULL != cntnr_lv_base); assert(LV_IS_BASE_VAR(cntnr_lv_base)); MARK_REACHABLE(cntnr_lv_base); } lvt_child = LV_GET_CHILD(node); if (NULL != lvt_child) /* Descend recursively down this tree as well */ als_prcs_markreached_cntnr(lvt_child); } } /* Function to scan an lvval for containers pointing to other structures that need to be scanned in xnew pop processing. * This goes through the entire tree of lv nodes looking for containers and if it finds any, it finds the base var pointed * to by the container if it has not already been processed in this pass (as determined by lvtaskcycle). Processing includes * incrementing refcnts and creating an lv_xnew_ref entry for the base var so we can check it again when the symtab pops to * see if any containers were created in them that point to the symtab being popped. */ void als_prcs_xnewref_cntnr(lvTree *lvt) { lvTree *lvt_child; lvTreeNode *node; lv_val *cntnr_lv_base; lv_xnew_ref *xnewref; assert(NULL != lvt); /* caller should not call if no subtree */ /* In the case of lv_killarray, the only option we have is to do post-order traversal since we are freeing * nodes in the tree as we traverse. But in this case, we dont change the tree structure so we are free to * choose any order. We choose in-order as that is faster than post-order. */ for (node = lvAvlTreeFirst(lvt); NULL != node; node = lvAvlTreeNext(node)) { if (node->v.mvtype & MV_ALIASCONT) { assert(lvt->base_lv->has_aliascont); assert(!LV_IS_BASE_VAR(node)); cntnr_lv_base = (lv_val *)node->v.str.addr; assert(NULL != cntnr_lv_base); assert(LV_IS_BASE_VAR(cntnr_lv_base)); if (cntnr_lv_base->stats.lvtaskcycle != lvtaskcycle) { INCR_CREFCNT(cntnr_lv_base); INCR_TREFCNT(cntnr_lv_base); cntnr_lv_base->stats.lvtaskcycle = lvtaskcycle; if (NULL != xnewref_anchor) { /* Reuse entry from list */ xnewref = xnewref_anchor; xnewref_anchor = xnewref->next; } else { /* Malloc an entry. Note that these blocks are put back on the chain anchored at the * xnewref_anchor global in function "als_check_xnew_var_aliases". They are not freed * since xnews typically happen in subroutines for temporary periods making the * likelihood of block reuse high. Also they are small and typically few in number. */ xnewref = (lv_xnew_ref *)malloc(SIZEOF(lv_xnew_ref)); } xnewref->lvval = cntnr_lv_base; xnewref->next = curr_symval->xnew_ref_list; curr_symval->xnew_ref_list = xnewref; if (LV_HAS_CHILD(cntnr_lv_base)) XNEWREF_CNTNRS_IN_TREE(cntnr_lv_base); } } lvt_child = LV_GET_CHILD(node); if (NULL != lvt_child) /* Descend recursively down this tree as well */ als_prcs_xnewref_cntnr(lvt_child); } } /* Initialize ZWRite hash table structures used when ZWRiting in an aliased variable environment */ void als_zwrhtab_init(void) { zwr_zav_blk *zavb, *zavb_next; if (zwrhtab && zwrhtab->cleaned) return; if (NULL == zwrhtab) { /* none yet .. allocate and init one */ zwrhtab = (zwr_hash_table *)malloc(SIZEOF(zwr_hash_table)); zwrhtab->first_zwrzavb = NULL; init_hashtab_addr(&zwrhtab->h_zwrtab, ZWR_HTAB_INIT_SIZE, HASHTAB_COMPACT, HASHTAB_SPARE_TABLE); } else { /* Have one, reinitialize it */ assert(zwrhtab->first_zwrzavb); zavb = zwrhtab->first_zwrzavb; if (zavb) { for (zavb_next = zavb->next; zavb_next; zavb = zavb_next, zavb_next = zavb->next) { /* Leave one block on queue if it exists .. get rid of others */ free(zavb); } assert(zavb); zwrhtab->first_zwrzavb = zavb; } reinitialize_hashtab_addr(&zwrhtab->h_zwrtab); } if (NULL == zwrhtab->first_zwrzavb) zwrhtab->first_zwrzavb = (zwr_zav_blk *)malloc(SIZEOF(zwr_zav_blk) + (SIZEOF(zwr_alias_var) * ZWR_ZAV_BLK_CNT)); ZAV_BLK_INIT(zwrhtab->first_zwrzavb, NULL); zwrhtab->cleaned = TRUE; } /* Obtain a zwr_alias_var slot for the zalias hash table */ zwr_alias_var *als_getzavslot(void) { zwr_alias_var *zav; zwr_zav_blk *zavb; assert(zwrhtab); assert(zwrhtab->first_zwrzavb); zwrhtab->cleaned = FALSE; /* No longer in a clean/initialized state */ /* Check if a block can be allocated out of a zavb super block */ zavb = zwrhtab->first_zwrzavb; assert(zavb); if (zavb->zav_free >= zavb->zav_top) { /* This block is full too .. need a new one */ zavb = (zwr_zav_blk *)malloc(SIZEOF(zwr_zav_blk) + (SIZEOF(zwr_alias_var) * ZWR_ZAV_BLK_CNT)); ZAV_BLK_INIT(zavb, zwrhtab->first_zwrzavb); zwrhtab->first_zwrzavb = zavb; } assert(zavb->zav_free < zavb->zav_top); zav = zavb->zav_free++; zav->value_printed = FALSE; return zav; } /* See if lv_val is associated with a base named var in the current symval. * Scan the hash table for valid entries and see if any of them point to supplied lv_val. * If so, return the hash table entry which contains the var's name that is "lowest" which * necessitates a full scan of the hash table (which ZWrite processing is going to do several * times anyway. */ ht_ent_mname *als_lookup_base_lvval(lv_val *lvp) { ht_ent_mname *htep, *htep_top, *htep_loweq; lv_val *lvhtval; assert(LV_IS_BASE_VAR(lvp)); htep_loweq = NULL; htep = curr_symval->h_symtab.base; htep_top = curr_symval->h_symtab.base + curr_symval->h_symtab.size; assert(htep_top == curr_symval->h_symtab.top); for (; htep < htep_top; htep++) { if (HTENT_VALID_MNAME(htep, lv_val, lvhtval) && '$' != *htep->key.var_name.addr && lvp == lvhtval) { /* HT entry is valid and has a key that is not a $ZWRTAC type key and the lval matches * so we have a candidate to check for "lowest" alias name for given lv_val addr. */ if (htep_loweq) { /* See current champ higher than candidate, then get new champ */ if (0 < memvcmp(htep_loweq->key.var_name.addr, htep_loweq->key.var_name.len, htep->key.var_name.addr, htep->key.var_name.len)) htep_loweq = htep; } else /* First time thru, free-ride assignment */ htep_loweq = htep; } } return htep_loweq; } /* Routine to do a garbage collection on the lv_vals in the current symbol table in order to detect if * there is any "lost data" which are 2 or more base lv_vals that point to each other thus keeping their * reference counts non-zero and prevent them from being deleted but otherwise have no entry in the hash table * themselves nor are linked to by any combination of container var linked arrays that do have an entry in * the hash table -- in other words, they are totally orphaned with no way to retrieve them so are effectively * dead but are not able to be killed in an automated fashion. This routine will find and kill those blocks * returning a count of the lv_vals thus found and killed. * * Operation: * * 1) Run lv_blks which contain all lv_val structures in use for this symbol table. * 2) Record each base lv_val in our version of the array used by stp_gcol. Base lv_vals can be identified * by having a non-zero parent.sym field pointing to a block with type MV_SYM. There are 3 exceptions to this: In UNIX, the zsearch_var, zsearch_dir1, and zsearch_dir2 fields contain lv_vals that should not be released. Check for and avoid them. * 3) Increment lvtaskcycle with which we will mark lv_vals as having been marked accessible as we discover them. * 4) Go through the hashtable. Set the lvtaskcycle field to mark the lv_val "reachable". * 5) If the lv_val has descendants, run the decendant chain to look for container vars. * 6) The base lv_vals that container vars point to, if not already marked "reachable" will be so marked and * the search recursively invoked on the new var. * 7) Do the same by running the mv_stent chain marking the temporarily displaced vars for parameters and NEW's as * "reachable". * 8) Do the same by running the TP stack marking local variable copies made for this symbol table as reachable. * 9) Mark any pending return argument as "reachable". * 10) Once the "reachable" search is complete, run through the created list of lv_vals and locate any that were * not reachable. If they remain undeleted (deleted will have parent.sym field zeroed), delete them. * * Note this routine uses the same buffer structure that "stp_gcol" uses except it loads its array with lv_val* * instead of mstr*. An address is an address.. */ int als_lvval_gc(void) { int killcnt; lv_blk *lv_blk_ptr; ht_ent_mname *htep, *htep_top; lv_val *lvp, *lvlimit; lv_val **lvarraycur, **lvarray, **lvarraytop, **lvptr; mv_stent *mv_st_ent; tp_frame *tf; tp_var *restore_ent; lvTree *lvt_child; symval *sym; DEBUG_ONLY(uint4 savelvtaskcycle;) DCL_THREADGBL_ACCESS; SETUP_THREADGBL_ACCESS; assert(!suspend_lvgcol); DBGRFCT((stderr, "als_lvval_gc: Beginning lv_val garbage collection\n")); if (NULL == stp_array) /* Same initialization as is in stp_gcol_src.h */ stp_array = (mstr **)malloc((stp_array_size = STP_MAXITEMS) * SIZEOF(mstr *)); lvarraycur = lvarray = (lv_val **)stp_array; lvarraytop = lvarraycur + stp_array_size; /* Steps 1,2 - find all the base lv_vals and put in list */ for (lv_blk_ptr = curr_symval->lv_first_block; lv_blk_ptr; lv_blk_ptr = lv_blk_ptr->next) { for (lvp = (lv_val *)LV_BLK_GET_BASE(lv_blk_ptr), lvlimit = LV_BLK_GET_FREE(lv_blk_ptr, lvp); lvp < lvlimit; lvp++) { sym = LV_SYMVAL(lvp); assert((NULL == sym) || SYM_IS_SYMVAL(sym)); if ((NULL != sym) UNIX_ONLY(&& (TREF(zsearch_var) != lvp)) UNIX_ONLY(&& (TREF(zsearch_dir1) != lvp) && (TREF(zsearch_dir2) != lvp))) { /* Put it in the list */ assert(0 < lvp->stats.trefcnt); if (lvarraycur >= lvarraytop) { /* Need more room -- expand */ stp_expand_array(); lvarraycur = lvarray = (lv_val **)stp_array; lvarraytop = lvarraycur + stp_array_size; } *lvarraycur++ = lvp; } } } /* Step 3, increment lvtaskcycle to mark "reachable" lv_vals */ INCR_LVTASKCYCLE; DEBUG_ONLY(savelvtaskcycle = lvtaskcycle); /* Steps 4,5,6 - Find and mark reachable lv_vals */ DBGRFCT((stderr, "als_lvval_gc: Starting symtab scan\n")); htep = curr_symval->h_symtab.base; htep_top = curr_symval->h_symtab.base + curr_symval->h_symtab.size; assert(htep_top == curr_symval->h_symtab.top); for (; htep < htep_top; htep++) { if (HTENT_VALID_MNAME(htep, lv_val, lvp)) { /* HT entry is valid. Note for purposes of this loop, we do NOT bypass $ZWRTAC type keys since * they are valid reachable variables even if hidden */ MARK_REACHABLE(lvp); } } /* Step 7 - Run the mv_stent chain marking those vars as reachable. */ DBGRFCT((stderr, "als_lvval_gc: Starting mv_stent scan\n")); for (mv_st_ent = mv_chain; mv_st_ent; mv_st_ent = (mv_stent *)(mv_st_ent->mv_st_next + (char *)mv_st_ent)) { switch (mv_st_ent->mv_st_type) { /* The types processed here contain lv_vals we want to mark */ case MVST_NTAB: lvp = mv_st_ent->mv_st_cont.mvs_ntab.save_value; DBGRFCT((stderr, "als_lvval_gc: NTAB at 0x"lvaddr" has save value 0x"lvaddr"\n", mv_st_ent, lvp)); assert(NULL != lvp); break; case MVST_PVAL: /* Note the save_value in the PVAL types below can be zero since they are created in * op_bindparm with a NULL save_value value and later filled in by op_bindparm. It is * possible to trigger this code in between the push_parm call of the caller and the * op_bindparm call of the callee when tracing or with an outofband event. In that case, * we don't want that NULL save_value pointer ending our loop prematurely. */ lvp = mv_st_ent->mv_st_cont.mvs_pval.mvs_ptab.save_value; DBGRFCT((stderr, "als_lvval_gc: PVAL at 0x"lvaddr" has save value 0x"lvaddr"\n", mv_st_ent, lvp)); assert(NULL != mv_st_ent->mv_st_cont.mvs_pval.mvs_val); /* Mark created lv_val to hold current value as reachable as it may not (yet) be in the * hashtable if op_bindparm has not yet run. */ MARK_REACHABLE(mv_st_ent->mv_st_cont.mvs_pval.mvs_val); if (NULL == lvp) continue; /* Don't end loop prematurely */ break; case MVST_NVAL: lvp = mv_st_ent->mv_st_cont.mvs_nval.mvs_ptab.save_value; DBGRFCT((stderr, "als_lvval_gc: NVAL at 0x"lvaddr" has save value 0x"lvaddr"\n", mv_st_ent, lvp)); assert(NULL != mv_st_ent->mv_st_cont.mvs_nval.mvs_val); /* Mark created lv_val to hold current value as reachable as it may not (yet) be in the * hashtable if op_bindparm has not yet run. */ MARK_REACHABLE(mv_st_ent->mv_st_cont.mvs_nval.mvs_val); assert(NULL != lvp); break; case MVST_STAB: /* The symbol table is changing to be other than the table we are using so we can * stop the loop now. Exiting the switch with lvp NULL indicates that. */ DBGRFCT((stderr, "als_lvval_gc: STAB mv_stent at 0x"lvaddr" stops mv_stent scan\n", mv_st_ent)); lvp = NULL; break; default: DBGRFCT((stderr, "als_lvval_gc: Ignoring mv_stent type %d\n", mv_st_ent->mv_st_type)); continue; } if (NULL == lvp) break; MARK_REACHABLE(lvp); } /* Step 8 - Run the TP stack to see if there is anything we can mark reachable */ DBGRFCT((stderr, "als_lvval_gc: Starting TP stack scan\n")); for (tf = tp_pointer; NULL != tf && tf->sym == curr_symval; tf = tf->old_tp_frame) { for (restore_ent = tf->vars; NULL != restore_ent; restore_ent = restore_ent->next) { /* Since TP keeps its own use count on these sorts of variables, we will mark both the * current and saved values in these blocks. This is because the "current value" could * be detached from the hash table at this point but is still viable while we hold a use * count on it. */ MARK_REACHABLE(restore_ent->current_value); MARK_REACHABLE(restore_ent->save_value); } } /* Step 9 - Mark any pending alias return argument as reachable */ if (NULL != alias_retarg) { /* There is a pending alias return arg (container). Find the lv it is pointing to and mark it and its progeny * as reachable. */ assert(0 != (MV_ALIASCONT & alias_retarg->mvtype)); lvp = (lv_val *)alias_retarg->str.addr; assert(LV_IS_BASE_VAR(lvp)); MARK_REACHABLE(lvp); } /* Step 10 - Run the list of base lv_vals we created earlier and see which ones were not marked with the current * cycle. Note they may have already been deleted after the first one gets deleted so we can check for that * by looking for a zeroed parent field. Note the object of this code is not to force-delete the vars as we * encounter them but by performing a deletion of their arrays, we will kill the interlinking container vars that * are keeping these vars alive. */ killcnt = 0; DBGRFCT((stderr, "\nals_lvval_gc: final orphaned lvval scan\n")); for (lvptr = lvarray; lvptr < lvarraycur; ++lvptr) { lvp = *lvptr; if (lvp->stats.lvtaskcycle != lvtaskcycle) { /* Have an orphaned lv_val */ DBGRFCT((stderr, "\nals_lvval_gc: lvval 0x"lvaddr" has been identified as orphaned\n", lvp)); ++killcnt; if (LV_SYMVAL(lvp)) { /* Var is still intact, kill it. Note that in this situation, since there are no hash table * entries pointing to us, our container refs and total refs should be equal. We can't * use the "regular" DECR macros because those get us into trouble. For example if this * var has a container pointing to another var who has a container pointing to us and it * is only those pointers keeping both vars alive, decrementing our counter causes it to * become zero which messes up the deletion of the other var's container since the refcnts * are already zero. What we will do instead is INCREASE the trefcnt to keep this var from * being deleted, then drive the kill of any array it has to spur these vars to go away. */ assert(LV_IS_BASE_VAR(lvp)); DBGRFCT((stderr, "\nals_lvval_gc: Working to release unreachable lvval 0x"lvaddr"\n", lvp)); assert(lvp->stats.trefcnt == lvp->stats.crefcnt); INCR_TREFCNT(lvp); lvt_child = LV_GET_CHILD(lvp); if (NULL != lvt_child) { assert(lvp == (lv_val *)LVT_PARENT(lvt_child)); LV_CHILD(lvp) = NULL; lv_killarray(lvt_child, FALSE); } DECR_BASE_REF_NOSYM(lvp, FALSE); /* Var might go away now, or later if need more deletes first */ } else DBGRFCT((stderr, "\nals_lvval_gc: Seems to have become released -- lvval 0x"lvaddr"\n", lvp)); } } DBGRFCT((stderr, "\nals_lvval_gc: final orphaned lvval scan completed\n")); # ifdef DEBUG /* The removal of a reference for each lv_val should have done it but let's go back and verify they all * went away. If not, then it is not a simple case of user silliness and we have a problem. */ for (lvptr = lvarray; lvptr < lvarraycur; ++lvptr) { lvp = *lvptr; if (lvp->stats.lvtaskcycle != lvtaskcycle && LV_SYMVAL(lvp)) { /* Var is still intact, kill it */ assert(FALSE); } } # endif assert(lvtaskcycle == savelvtaskcycle); DBGRFCT((stderr, "als_lvval_gc: GC complete -- recovered %d lv_vals\n", killcnt)); SPGC_since_LVGC = 0; return killcnt; } # ifdef DEBUG_ALIAS /* Routine to check lv_val monitoring. If any lv_vals that were created during the monitoring period still exist, emit a * message to that effect with the lv_val address. In this manner we hope to find lv_val leaks (if any) in various tests. * Note that this is a debugging (not debug build) only routine since its costs are non-trivial and is only enabled * when necessary. Other tests check for memory leaks so if they find one, this monitoring can be used to discover the * source so this is not needed for test coverage. */ void als_lvmon_output(void) { symval *lvlsymtab; lv_blk *lvbp; lv_val *lvp, *lvp_top; flush_pio(); for (lvlsymtab = curr_symval; lvlsymtab; lvlsymtab = lvlsymtab->last_tab) { for (lvbp = curr_symval->lv_first_block; lvbp; lvbp = lvbp->next) { for (lvp = (lv_val *)LV_BLK_GET_BASE(lvbp), lvp_top = LV_BLK_GET_FREE(lvbp, lvp); lvp < lvp_top; lvp++) { if (lvp->lvmon_mark) { /* lv_val slot not used as an sbs and is marked. Report it */ FPRINTF(stderr, "als_lvmon_output: lv_val at 0x"lvaddr" is still marked\n", lvp); } } } } fflush(stderr); } # endif