fis-gtm/sr_port/alias_funcs.c

1281 lines
56 KiB
C
Raw Permalink Normal View History

/****************************************************************
* *
* 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 <stddef.h>
#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