/*--------------------------------------------------------------------------- * Gamedriver - Garbage Collection * *--------------------------------------------------------------------------- * The garbage collection is used in times of memory shortage (or on request) * to find and deallocate any unused objects, arrays, memory blocks, or * whatever. The purpose is to detect and get rid of unreachable data (like * circular array references), but the collector is also a last line of * defense against bug-introduced memory leaks. * * This facility is available only when using the 'smalloc' * memory allocator. When using a different allocator, all garbage_collect() * does is freeing as much memory as possible. * * The garbage collector is a simple mark-and-sweep collector. First, all * references (refcounts and memory block markers) are cleared, then in * a second pass, all reachable references are recreated (refcounts are * incremented, memory blocks marked as used). The last pass then looks * for all allocated but unmarked memory blocks: these are provably * garbage and can be given back to the allocator. For debugging purposes, * the collected memory blocks can be printed onto a file for closer * inspection. * * In order to do its job, the garbage collector calls functions clear_... * and count_... in all the other driver modules, where they are supposed * to perform their clearing and counting operations. To aid the other * modules in this, the collector offers a set of primitives to clearing * and marking: * * void clear_memory_reference(void *p) * Clear the memory block marker for <p>. * * void note_malloced_block_ref(void *p) * Note the reference to memory block <p>. * * void clear_inherit_ref(program_t *p) * Clear the refcounts of all inherited programs of <p>. * * void clear_object_ref(object_t *p) * Make sure that the refcounts in object <p> are cleared. * * void mark_program_ref(program_t *p); * Set the marker of program <p> and of all data referenced by <p>. * * void reference_destructed_object(object_t *ob) * Note the reference to a destructed object <ob>. * * void count_ref_from_string(char *p); * Count the reference to shared string <p>. * * void clear_ref_in_vector(svalue_t *svp, size_t num); * Clear the refs of the <num> elements of vector <svp>. * * void count_ref_in_vector(svalue_t *svp, size_t num) * Count the references the <num> elements of vector <p>. * * The referencing code for dynamic data should mirror the destructor code, * thus, memory leaks can show up as soon as the memory is allocated. * * TODO: Allow to deactivate the dump of unreferenced memory on freeing. * TODO: Change all non-shared strings in shared ones after finishing the GC. *--------------------------------------------------------------------------- */ #include "driver.h" #include "typedefs.h" #include <sys/types.h> #ifdef HAVE_SYS_TIME_H #include <sys/time.h> #endif #include <time.h> #include <stdio.h> #define NO_REF_STRING #include "gcollect.h" #include "actions.h" #include "array.h" #include "backend.h" #include "call_out.h" #include "closure.h" #include "comm.h" #include "ed.h" #include "exec.h" #include "filestat.h" #include "heartbeat.h" #include "instrs.h" #include "interpret.h" #include "lex.h" #include "main.h" #include "mapping.h" #include "object.h" #include "otable.h" #include "parse.h" #include "prolang.h" #include "rxcache.h" #include "sent.h" #include "simulate.h" #include "simul_efun.h" #include "smalloc.h" #include "stdstrings.h" #include "stralloc.h" #include "swap.h" #include "wiz_list.h" #include "xalloc.h" #include "../mudlib/sys/driver_hook.h" /*-------------------------------------------------------------------------*/ time_t time_last_gc = 0; /* Time of last gc, used by the backend to avoid repeated collections * when the memory usage is at the edge of a shortage. */ #if defined(GC_SUPPORT) int default_gcollect_outfd = 2; /* The default file (default is stderr) to dump the reclaimed blocks on. */ int gcollect_outfd = 2; #define gout gcollect_outfd /* The current file (default is stderr) to dump the reclaimed blocks on. * After the GC, this will be reset to <default_gcollect_outfd>. */ gc_status_t gc_status = gcInactive; /* The current state of the garbage collection. * swap uses this information when swapping in objects. */ object_t *gc_obj_list_destructed; /* List of referenced but destructed objects. * Scope is global so that the GC support functions in mapping.c can * add their share of information. */ lambda_t *stale_misc_closures; /* List of non-lambda closures bound to a destructed object. * The now irrelevant .ob pointer is used to link the list elements. * Scope is global so that the GC support functions in mapping.c can * add their share of information. */ static lambda_t *stale_lambda_closures; /* List of lambda closures bound to a destructed object. * The now irrelevant .ob pointer is used to link the list elements. */ static size_t size_alloc_strings; static size_t num_alloc_strings; /* Number and size of unshared strings encountered. */ #endif /* GC_SUPPORT */ /*-------------------------------------------------------------------------*/ #if defined(MALLOC_smalloc) #define CLEAR_REF(p) ( ((p_uint *)(p))[-SMALLOC_OVERHEAD] &= ~M_REF ) /* Clear the memory block marker for <p> */ #ifdef CHECK_OBJECT_GC_REF static unsigned long gc_mark_ref(void * p, const char * file, int line) { if (is_object_allocation(p)) { dprintf3(gout, "DEBUG: Object %x referenced as something else from %s:%d\n" , (p_int)p, (p_int)file, (p_int)line); } if (is_program_allocation(p)) { dprintf3(gout, "DEBUG: Program %x referenced as something else from %s:%d\n" , (p_int)p, (p_int)file, (p_int)line); } return ( ((p_uint *)(p))[-SMALLOC_OVERHEAD] |= M_REF ); } #define MARK_REF(p) gc_mark_ref(p, __FILE__, __LINE__) #define MARK_PLAIN_REF(p) ( ((p_uint *)(p))[-SMALLOC_OVERHEAD] |= M_REF ) #else #define MARK_REF(p) ( ((p_uint *)(p))[-SMALLOC_OVERHEAD] |= M_REF ) /* Set the memory block marker for <p> */ #define MARK_PLAIN_REF(p) MARK_REF(p) #endif #define TEST_REF(p) ( !( ((p_uint *)(p))[-SMALLOC_OVERHEAD] & M_REF ) ) /* Check the memory block marker for <p>, return TRUE if _not_ set. */ #define CHECK_REF(p) ( TEST_REF(p) && ( MARK_REF(p),MY_TRUE ) ) /* Check the memory block marker for <p> and set it if necessary. * Return TRUE if the marker was not set, FALSE else. */ #define STRING_REFS(str) (*(StrRefCount *)((char *) (str)\ - sizeof(StrRefCount))) /* Return the refcount of shared string <str> */ #if !defined(CHECK_STRINGS) && !defined(DEBUG) #ifndef DUMP_GC_REFS #define MARK_STRING_REF(str) ((void)(\ STRING_REFS(str)++ || (\ CHECK_REF( (str)-sizeof(StrRefCount)-sizeof(char *) ) ||\ /* reached max ref count, which is given as 0... */ \ STRING_REFS(str)--\ ))\ ) /* Increment the refcount of shared string <str>. How it works: * If STRING_REFS() is 0, the refcount either overflowed or it is * the first visit to the block. If it's the first visit, CHECK_REF * will return TRUE, otherwise we have an overflow and the STRING_REFS-- * will undo the ++ from earlier. */ #else #define MARK_STRING_REF(p) \ do { char *p_ = p; \ dprintf4(gcollect_outfd, "Mark string ref %x (%x): %s %d\n", (long)p_, (long)(p_-sizeof(StrRefCount)-sizeof(char*)), (long)__FILE__, (long)__LINE__); \ STRING_REFS(p_)++ \ || (CHECK_REF( (p_)-sizeof(StrRefCount)-sizeof(char *) ) \ || STRING_REFS(p_)-- \ ); \ } while(0) #endif /* DUMP_GC_REFS */ #else static void MARK_STRING_REF (char * str) { if (CHECK_REF( (str)-sizeof(StrRefCount)-sizeof(char *) ) ) { /* First visit to this block */ STRING_REFS(str)++; #ifdef CHECK_STRINGS mark_shadow_string_ref(str); #endif } else if (STRING_REFS(str)) { /* Not the first visit, and refcounts didn't overrun either */ STRING_REFS(str)++; if (!STRING_REFS(str)) { /* Refcount overflow */ dprintf2(gout, "DEBUG: mark string: %x '%s' refcount reaches max!\n" , (p_int)str, (p_int)str); } #ifdef CHECK_STRINGS inc_shadow_string_ref(str); #endif } } #endif /* DEBUG || CHECK_STRINGS */ /* Forward declarations */ static void clear_map_ref_filter (svalue_t *, svalue_t *, void *); static void clear_ref_in_closure (lambda_t *l, ph_int type); static void gc_count_ref_in_closure (svalue_t *csvp); #define count_ref_in_closure(p) \ GC_REF_DUMP(svalue_t*, p, "Count ref in closure", gc_count_ref_in_closure) #endif /* MALLOC_smalloc */ /*=========================================================================*/ /* The real collector - only with smalloc. */ #if defined(MALLOC_smalloc) #if defined(MALLOC_TRACE) #define WRITES(d, s) write((d), (s), strlen(s)) /*-------------------------------------------------------------------------*/ static INLINE void write_malloc_trace (void * p) /* Dump the allocation information for <p>, if any. */ { WRITES(gout, ((char **)(p))[-3]); WRITES(gout, " "); writed(gout, (int)((p_uint *)(p))[-2]); WRITES(gout, "\n"); } /* write_malloc_trace() */ #else #define write_malloc_trace(p) #define WRITES(d, s) #endif /* MALLOC_TRACE */ /*-------------------------------------------------------------------------*/ void clear_memory_reference (void *p) /* Clear the memory block marker for block <p>. */ { CLEAR_REF(p); } /*-------------------------------------------------------------------------*/ static INLINE void gc_note_ref (void *p #ifdef CHECK_OBJECT_GC_REF , const char * file, int line #endif ) /* Note the reference to memory block <p>. * * It is no use to write a diagnostic on the second or higher reference * to the memory block, as this can happen when an object is swapped in, * marked, swapped out, and the next swapped-in object reuses the memory block * released from the one before. */ { if (TEST_REF(p)) { #ifdef CHECK_OBJECT_GC_REF gc_mark_ref(p, file, line); #else MARK_REF(p); #endif return; } } /* gc_note_ref() */ #ifdef CHECK_OBJECT_GC_REF void gc_note_malloced_block_ref (void *p, const char * file, int line) { gc_note_ref(p, file, line); } #define note_ref(p) gc_note_ref(p, __FILE__, __LINE__) #define passed_note_ref(p) gc_note_ref(p, file, line) #else void gc_note_malloced_block_ref (void *p) { gc_note_ref(p); } #define note_ref(p) GC_REF_DUMP(void*, p, "Note ref", gc_note_ref) #define passed_note_ref(p) note_ref(p) #endif /*-------------------------------------------------------------------------*/ void clear_inherit_ref (program_t *p) /* Clear the refcounts of all inherited programs of <p>. */ { int i; for (i = 0; i < p->num_inherited; i++) { /* Inherited programs are never swapped. Only programs with blueprints * are swapped, and a blueprint and one inheritance makes two refs. */ program_t *p2; p2 = p->inherit[i].prog; if (p2->ref) { p2->ref = 0; clear_inherit_ref(p2); } } } /* clear_inherit_ref() */ /*-------------------------------------------------------------------------*/ void clear_object_ref (object_t *p) /* If <p> is a destructed object, its refcounts are cleared. * If <p> is a live object, its refcounts are assumed to be cleared * by the GC main method. */ { if ((p->flags & O_DESTRUCTED) && p->ref) { p->ref = 0; p->prog->ref = 0; if (p->prog->blueprint && (p->prog->blueprint->flags & O_DESTRUCTED) && p->prog->blueprint->ref ) { p->prog->blueprint->ref = 0; } clear_inherit_ref(p->prog); } } /* clear_object_ref() */ /*-------------------------------------------------------------------------*/ void gc_mark_program_ref (program_t *p) /* Set the marker of program <p> and of all data referenced by <p>. */ { #ifdef CHECK_OBJECT_GC_REF if (TEST_REF(p) && ( MARK_PLAIN_REF(p),MY_TRUE ) ) #else if (CHECK_REF(p)) /* ...then mark referenced data */ #endif { int i; unsigned char *program = p->program; uint32 *functions = p->functions; char **strings; variable_t *variable_names; if (p->ref++) { dump_malloc_trace(1, p); fatal("First reference to program %p '%s', but ref count %ld != 0\n" , p, p->name, (long)p->ref - 1 ); } /* Mark the blueprint object, if any */ if (p->blueprint) { if (p->blueprint->flags & O_DESTRUCTED) { reference_destructed_object(p->blueprint); p->blueprint = NULL; remove_prog_swap(p, MY_TRUE); } else { p->blueprint->ref++; /* No note_ref() necessary: the blueprint is in * the global object list */ } } if (p->line_numbers) note_ref(p->line_numbers); /* Non-inherited functions */ for (i = p->num_functions; --i >= 0; ) { if ( !(functions[i] & NAME_INHERITED) ) { char *name; memcpy( (char *)&name, program + (functions[i] & FUNSTART_MASK) - 1 - sizeof name, sizeof name ); MARK_STRING_REF(name); } } /* String literals */ strings = p->strings; for (i = p->num_strings; --i >= 0; ) { char *str = *strings++; MARK_STRING_REF(str); } /* Variable names */ variable_names = p->variable_names; for (i = p->num_variables; --i >= 0; variable_names++) MARK_STRING_REF(variable_names->name); /* Inherited programs */ for (i=0; i< p->num_inherited; i++) mark_program_ref(p->inherit[i].prog); /* Included files */ for (i=0; i< p->num_includes; i++) { char *str; str = p->includes[i].name; MARK_STRING_REF(str); str = p->includes[i].filename; MARK_STRING_REF(str); } note_ref(p->name); } else { if (!p->ref++) { dump_malloc_trace(1, p); fatal("Program block %p '%s' referenced as something else\n" , p, p->name); } } } /* gc_mark_program_ref() */ /*-------------------------------------------------------------------------*/ static void mark_object_ref (object_t *ob) /* Mark the object <ob> as referenced and increase its refcount. * This method should be called only for destructed objects and * from the GC main loop for the initial count of live objects. */ { MARK_PLAIN_REF(ob); ob->ref++; mark_program_ref(ob->prog); note_ref(ob->name); MARK_STRING_REF(ob->load_name); } /* mark_object_ref() */ /*-------------------------------------------------------------------------*/ void gc_reference_destructed_object (object_t *ob) /* Note the reference to a destructed object <ob>. The referee has to * replace its reference by a svalue.number 0 since all these objects * will be freed later. */ { if (TEST_REF(ob)) { if (ob->ref) { dump_malloc_trace(1, ob); fatal("First reference to destructed object %p '%s', " "but ref count %ld != 0\n" , ob, ob->name, (long)ob->ref ); } /* Destructed objects are not swapped */ ob->next_all = gc_obj_list_destructed; gc_obj_list_destructed = ob; mark_object_ref(ob); } else { if (!ob->ref) { write_malloc_trace(ob); dump_malloc_trace(1, ob); fatal("Destructed object %p '%s' referenced as something else\n" , ob, ob->name); } } } /* gc_reference_destructed_object() */ /*-------------------------------------------------------------------------*/ void gc_count_ref_from_string (char *p) /* Count the reference to shared string <p>. */ { MARK_STRING_REF(p); } /* gc_count_ref_from_string() */ /*-------------------------------------------------------------------------*/ static void clear_map_ref_filter (svalue_t *key, svalue_t *data, void *extra) /* Auxiliary function to clear the refs in a mapping. * It is called with the <key> and <data> vector, the latter of * width (p_int)<extra> */ { clear_ref_in_vector(key, 1); clear_ref_in_vector(data, (size_t)extra); } /*-------------------------------------------------------------------------*/ void clear_ref_in_vector (svalue_t *svp, size_t num) /* Clear the refs of the <num> elements of vector <svp>. */ { svalue_t *p; if (!svp) /* e.g. when called for obj->variables */ return; for (p = svp; p < svp+num; p++) { switch(p->type) { case T_OBJECT: /* this might be a destructed object, which has it's ref not * cleared by the obj_list because it is no longer a member * Alas, swapped objects must not have prog->ref cleared. */ clear_object_ref(p->u.ob); continue; case T_POINTER: case T_QUOTED_ARRAY: if (!p->u.vec->ref) continue; p->u.vec->ref = 0; clear_ref_in_vector(&p->u.vec->item[0], VEC_SIZE(p->u.vec)); continue; case T_MAPPING: if (p->u.map->ref) { mapping_t *m; p_int num_values; m = p->u.map; m->ref = 0; num_values = m->num_values; walk_mapping(m, clear_map_ref_filter, (char *)num_values ); } continue; case T_CLOSURE: if (CLOSURE_MALLOCED(p->x.closure_type)) { lambda_t *l; l = p->u.lambda; if (l->ref) { l->ref = 0; clear_ref_in_closure(l, p->x.closure_type); } } else clear_object_ref(p->u.ob); continue; } } } /* clear_ref_in_vector() */ /*-------------------------------------------------------------------------*/ void gc_count_ref_in_vector (svalue_t *svp, size_t num #ifdef CHECK_OBJECT_GC_REF , const char * file, int line #endif ) /* Count the references the <num> elements of vector <p>. * Closures bound to destructed objects are replaced by #'undef operator * closures. * TODO: Was the done to preserve sorting orders? Apart from callbacks, * TODO:: replacing it by 0 would do (but see mapping.c). Or even better: * TODO:: introduce an ILLEGAL svalue type for such occasions which compares * TODO:: equal to svalue 0. */ { svalue_t *p; if (!svp) /* e.g. when called for obj->variables */ return; for (p = svp; p < svp+num; p++) { switch(p->type) { case T_OBJECT: { object_t *ob; ob = p->u.ob; if (ob->flags & O_DESTRUCTED) { put_number(p, 0); reference_destructed_object(ob); } else { ob->ref++; } continue; } case T_POINTER: case T_QUOTED_ARRAY: /* Don't use CHECK_REF on the null vector */ if (p->u.vec != &null_vector && CHECK_REF(p->u.vec)) { count_array_size(p->u.vec); #ifdef CHECK_OBJECT_GC_REF gc_count_ref_in_vector(&p->u.vec->item[0], VEC_SIZE(p->u.vec), file, line); #else count_ref_in_vector(&p->u.vec->item[0], VEC_SIZE(p->u.vec)); #endif } p->u.vec->ref++; continue; case T_MAPPING: if (CHECK_REF(p->u.map)) { mapping_t *m; struct condensed_mapping *cm; int num_values; m = p->u.map; cm = m->condensed; num_values = m->num_values; passed_note_ref((char *)CM_MISC(cm) - cm->misc_size *(num_values + 1)); /* hash mappings have been eleminated at the start */ count_ref_in_mapping(m); count_mapping_size(m); } p->u.map->ref++; continue; case T_STRING: switch(p->x.string_type) { case STRING_MALLOC: passed_note_ref(p->u.string); size_alloc_strings += strlen(p->u.string)+1; num_alloc_strings++; break; case STRING_SHARED: MARK_STRING_REF(p->u.string); break; } continue; case T_CLOSURE: if (CLOSURE_MALLOCED(p->x.closure_type)) { if (p->u.lambda->ref++ <= 0) { count_ref_in_closure(p); } } else { object_t *ob; ob = p->u.ob; if (ob->flags & O_DESTRUCTED) { p->x.closure_type = F_UNDEF+CLOSURE_EFUN; p->u.ob = master_ob; master_ob->ref++; reference_destructed_object(ob); } else { ob->ref++; } } continue; case T_SYMBOL: MARK_STRING_REF(p->u.string); continue; } } /* for */ } /* gc_count_ref_in_vector() */ /*-------------------------------------------------------------------------*/ static void remove_unreferenced_string (char *start, char *string) /* If the shared string <string> stored in the memory block <start> is * not referenced, it is deallocated. */ { if (TEST_REF(start)) { #ifdef KEEP_STRINGS dprintf1(gout, "shared string %x was left unreferenced.\n", (p_int) string ); MARK_REF(start); #else dprintf2(gout, "shared string %x '%s' was left unreferenced, freeing now.\n", (p_int) string, (p_int)string ); MARK_REF(start); #ifdef CHECK_STRINGS mark_shadow_string_ref(string); #endif STRING_REFS(string)++; free_string(string); #endif } } /*-------------------------------------------------------------------------*/ static void gc_note_action_ref (action_t *p) /* Mark the strings of function and verb of all sentences in list <p>. */ { do { if (p->function) MARK_STRING_REF(p->function); if (p->verb) MARK_STRING_REF(p->verb); note_ref(p); } while ( NULL != (p = (action_t *)p->sent.next) ); } #define note_action_ref(p) \ GC_REF_DUMP(action_t*, p, "Note action ref", gc_note_action_ref) /*-------------------------------------------------------------------------*/ static void gc_count_ref_in_closure (svalue_t *csvp) /* Count the reference to closure <csvp> and all referenced data. * Closures using a destructed object are stored in the stale_ lists * for later removal (and .ref is set to -1), and the svalue is transformed * into a #'undef operator closure. * TODO: Was the transform done to preserve sorting orders? Apart from callbacks, * TODO:: replacing it by 0 would do (but see mapping.c). Or even better: * TODO:: introduce an ILLEGAL svalue type for such occasions which compares * TODO:: equal to svalue 0. */ { lambda_t *l = csvp->u.lambda; ph_int type = csvp->x.closure_type; if (!l->ref) { /* This closure was bound to a destructed object, and has been * encountered before. */ l->ref--; /* Undo ref increment that was done by the caller */ if (type == CLOSURE_BOUND_LAMBDA) { csvp->x.closure_type = CLOSURE_UNBOUND_LAMBDA; (csvp->u.lambda = l->function.lambda)->ref++; } else { csvp->x.closure_type = F_UNDEF+CLOSURE_EFUN; csvp->u.ob = master_ob; master_ob->ref++; } return; } /* If the closure is bound, make sure that the object it is * bound to really exists. */ if (type != CLOSURE_UNBOUND_LAMBDA) { object_t *ob; ob = l->ob; if (ob->flags & O_DESTRUCTED || ( type == CLOSURE_ALIEN_LFUN && l->function.alien.ob->flags & O_DESTRUCTED) ) { l->ref = -1; if (type == CLOSURE_LAMBDA) { l->ob = (object_t *)stale_lambda_closures; stale_lambda_closures = l; } else { l->ob = (object_t *)stale_misc_closures; stale_misc_closures = l; if (type == CLOSURE_ALIEN_LFUN) { if (l->function.alien.ob->flags & O_DESTRUCTED) reference_destructed_object(l->function.alien.ob); } } if (ob->flags & O_DESTRUCTED) reference_destructed_object(ob); if (type == CLOSURE_BOUND_LAMBDA) { csvp->x.closure_type = CLOSURE_UNBOUND_LAMBDA; csvp->u.lambda = l->function.lambda; } else { csvp->x.closure_type = F_UNDEF+CLOSURE_EFUN; csvp->u.ob = master_ob; master_ob->ref++; } } else { /* Object exists: count reference */ ob->ref++; if (type == CLOSURE_ALIEN_LFUN) l->function.alien.ob->ref++; } } /* Count the references in the code of the closure */ if (CLOSURE_HAS_CODE(type)) { mp_int num_values; svalue_t *svp; svp = (svalue_t *)l; if ( (num_values = EXTRACT_UCHAR(l->function.code)) == 0xff) num_values = svp[-0x100].u.number; svp -= num_values; note_ref(svp); count_ref_in_vector(svp, (size_t)num_values); } else { note_ref(l); if (type == CLOSURE_BOUND_LAMBDA) { lambda_t *l2 = l->function.lambda; if (!l2->ref++) { svalue_t sv; sv.type = T_CLOSURE; sv.x.closure_type = CLOSURE_UNBOUND_LAMBDA; sv.u.lambda = l2; count_ref_in_closure(&sv); } } } } /* count_ref_in_closure() */ /*-------------------------------------------------------------------------*/ static void clear_ref_in_closure (lambda_t *l, ph_int type) /* Clear the references in closure <l> which is of type <type>. */ { if (CLOSURE_HAS_CODE(type)) { mp_int num_values; svalue_t *svp; svp = (svalue_t *)l; if ( (num_values = EXTRACT_UCHAR(l->function.code)) == 0xff) num_values = svp[-0x100].u.number; svp -= num_values; clear_ref_in_vector(svp, (size_t)num_values); } else if (type == CLOSURE_BOUND_LAMBDA) { lambda_t *l2 = l->function.lambda; if (l2->ref) { l2->ref = 0; clear_ref_in_closure(l2, CLOSURE_UNBOUND_LAMBDA); } } if (type != CLOSURE_UNBOUND_LAMBDA) clear_object_ref(l->ob); if (type == CLOSURE_ALIEN_LFUN) clear_object_ref(l->function.alien.ob); } /* clear_ref_in_closure() */ /*-------------------------------------------------------------------------*/ static void remove_uids (int smart UNUSED) /* ??? */ { #ifdef __MWERKS__ # pragma unused(smart) #endif NOOP } /*-------------------------------------------------------------------------*/ void restore_default_gc_log (void) /* If gcollect_outfd was redirected to some other file, that file is * closed and the default log file is restored. */ { if (gcollect_outfd != default_gcollect_outfd) { if (gcollect_outfd != 1 && gcollect_outfd != 2) close(gcollect_outfd); gcollect_outfd = default_gcollect_outfd; } } /* restore_default_log() */ /*-------------------------------------------------------------------------*/ void garbage_collection(void) /* The Mark-Sweep garbage collector. * * Free all possible memory, then loop through every object and variable * in the game, check the reference counts and deallocate unused memory. * This takes time and should not be used lightheartedly. * * The function must be called outside of LPC evaluations. */ { object_t *ob, *next_ob; lambda_t *l, *next_l; int i; long dobj_count; size_alloc_strings = 0; num_alloc_strings = 0; if (gcollect_outfd != 1 && gcollect_outfd != 2) { dprintf1(gcollect_outfd, "\n%s --- Garbage Collection ---\n" , (long)time_stamp()); } /* --- Pass 0: dispose of some unnecessary stuff --- */ malloc_privilege = MALLOC_MASTER; RESET_LIMITS; CLEAR_EVAL_COST; out_of_memory = MY_FALSE; assert_master_ob_loaded(); malloc_privilege = MALLOC_SYSTEM; if (obj_list_replace) replace_programs(); handle_newly_destructed_objects(); free_interpreter_temporaries(); free_action_temporaries(); remove_stale_player_data(); remove_stale_call_outs(); free_defines(); free_all_local_names(); remove_unknown_identifier(); #ifdef USE_FREE_CLOSURE_HOOK free_old_driver_hooks(); #endif purge_action_sent(); purge_shadow_sent(); check_wizlist_for_destr(); compact_mappings(num_dirty_mappings); if (current_error_trace) { free_array(current_error_trace); current_error_trace = NULL; } if (uncaught_error_trace) { free_array(uncaught_error_trace); uncaught_error_trace = NULL; } /* Lock all interactive structures (in case we're using threads) * and dispose of the written buffers. */ for (i = 0 ; i < MAX_PLAYERS; i++) { if (all_players[i] == NULL) continue; interactive_lock(all_players[i]); interactive_cleanup(all_players[i]); } remove_destructed_objects(); /* After reducing all object references! */ clear_array_size(); clear_mapping_size(); /* --- Pass 1: clear the M_REF flag in all malloced blocks --- */ clear_M_REF_flags(); /* --- Pass 2: clear the ref counts --- */ gc_status = gcClearRefs; if (d_flag > 3) { debug_message("%s start of garbage_collection\n", time_stamp()); } /* Process the list of all objects */ for (ob = obj_list; ob; ob = ob->next_all) { int was_swapped; if (d_flag > 4) { debug_message("%s clearing refs for object '%s'\n" , time_stamp(), ob->name); } was_swapped = 0; if (ob->flags & O_SWAPPED && (was_swapped = load_ob_from_swap(ob)) & 1) { /* don't clear the program ref count. It is 1 */ } else { /* Take special care of inherited programs, the associated * objects might be destructed. */ ob->prog->ref = 0; } if (was_swapped < 0) fatal("Totally out of MEMORY in GC: (swapping in '%s')\n" , ob->name); clear_inherit_ref(ob->prog); ob->ref = 0; clear_ref_in_vector(ob->variables, ob->prog->num_variables); if (ob->flags & O_SHADOW) { ed_buffer_t *buf; if ( NULL != (buf = O_GET_EDBUFFER(ob)) ) { clear_ed_buffer_refs(buf); } /* end of ed-buffer processing */ } if (was_swapped) { swap(ob, was_swapped); } } if (d_flag > 3) { debug_message("%s ref counts referenced by obj_list cleared\n" , time_stamp()); } /* Process the interactives */ for(i = 0 ; i < MAX_PLAYERS; i++) { input_to_t * it; if (all_players[i] == NULL) continue; #ifdef USE_PTHREADS { struct write_buffer_s *pwb; for (pwb = all_players[i]->write_first; pwb != NULL; pwb = pwb->next) clear_memory_reference(pwb); if ((pwb = all_players[i]->write_current) != NULL) clear_memory_reference(pwb); /* .written_first has been cleaned upL */ } #endif for ( it = all_players[i]->input_to; it != NULL; it = it->next) { clear_memory_reference(it); clear_ref_in_callback(&(it->fun)); clear_ref_in_vector(&(it->prompt), 1); } clear_ref_in_vector(&all_players[i]->prompt, 1); /* snoop_by and modify_command are known to be NULL or non-destructed * objects. */ } /* Process the driver hooks */ for (i = NUM_DRIVER_HOOKS; --i >= 0; ) { if (driver_hook[i].type == T_CLOSURE && driver_hook[i].x.closure_type == CLOSURE_LAMBDA) { driver_hook[i].x.closure_type = CLOSURE_UNBOUND_LAMBDA; } } clear_ref_in_vector(driver_hook, NUM_DRIVER_HOOKS); /* Let the modules process their data */ clear_shared_string_refs(); clear_ref_from_wiz_list(); clear_ref_from_call_outs(); #if defined(SUPPLY_PARSE_COMMAND) clear_parse_refs(); clear_old_parse_refs(); #endif clear_simul_efun_refs(); clear_interpreter_refs(); clear_comm_refs(); #ifdef RXCACHE_TABLE clear_rxcache_refs(); #endif null_vector.ref = 0; /* Finally, walk the list of destructed objects and clear all references * in them. */ for (ob = destructed_objs; ob; ob = ob->next_all) { if (d_flag > 4) { debug_message("%s clearing refs for destructed object '%s'\n" , time_stamp(), ob->name); } ob->prog->ref = 0; clear_inherit_ref(ob->prog); ob->ref = 0; } /* --- Pass 3: Compute the ref counts, and set M_REF where appropriate --- */ gc_status = gcCountRefs; gc_obj_list_destructed = NULL; stale_lambda_closures = NULL; stale_misc_closures = NULL; stale_mappings = NULL; /* Handle the known destructed objects first, as later calls to * reference_destructed_object() will clobber the list. */ for (ob = destructed_objs; ob; ) { object_t *next = ob->next_all; dprintf1(gcollect_outfd , "Freeing destructed object '%s'\n" , (p_int)ob->name ); reference_destructed_object(ob); /* Clobbers .next_all */ ob = next; } num_destructed = 0; destructed_objs = NULL; /* Process the list of all objects. */ for (ob = obj_list; ob; ob = ob->next_all) { int was_swapped; was_swapped = 0; if (ob->flags & O_SWAPPED) { was_swapped = load_ob_from_swap(ob); if (was_swapped & 1) { #ifdef DUMP_GC_REFS dprintf1(gcollect_outfd, "Clear ref of swapped-in program %x\n", (long)ob->prog); #endif CLEAR_REF(ob->prog); ob->prog->ref = 0; } } mark_object_ref(ob); if (ob->prog->num_variables) { note_ref(ob->variables); } count_ref_in_vector(ob->variables, ob->prog->num_variables); if (ob->sent) { sentence_t *sent; ed_buffer_t *buf; sent = ob->sent; if (ob->flags & O_SHADOW) { note_ref(sent); if ( NULL != (buf = ((shadow_t *)sent)->ed_buffer) ) { note_ref(buf); count_ed_buffer_refs(buf); } /* end of ed-buffer processing */ /* If there is a ->ip, it will be processed as * part of the player object handling below. */ sent = sent->next; } if (sent) note_action_ref((action_t *)sent); } if (was_swapped) { swap(ob, was_swapped); } } if (d_flag > 3) { debug_message("%s obj_list evaluated\n", time_stamp()); } /* Process the interactives. */ for(i = 0 ; i < MAX_PLAYERS; i++) { input_to_t * it; if (all_players[i] == NULL) continue; note_ref(all_players[i]); #ifdef USE_PTHREADS { struct write_buffer_s *pwb; for (pwb = all_players[i]->write_first; pwb != NULL; pwb = pwb->next) note_ref(pwb); if ((pwb = all_players[i]->write_current) != NULL) note_ref(pwb); /* .written_first has been cleaned upL */ } #endif /* There are no destructed interactives, or interactives * referencing destructed objects. */ all_players[i]->ob->ref++; if ( NULL != (ob = all_players[i]->snoop_by) ) { if (!O_IS_INTERACTIVE(ob)) { ob->ref++; } } /* end of snoop-processing */ for ( it = all_players[i]->input_to; it != NULL; it = it->next) { note_ref(it); count_ref_in_callback(&(it->fun)); count_ref_in_vector(&(it->prompt), 1); } /* end of input_to processing */ if ( NULL != (ob = all_players[i]->modify_command) ) { ob->ref++; } count_ref_in_vector(&all_players[i]->prompt, 1); if (all_players[i]->trace_prefix) { count_ref_from_string(all_players[i]->trace_prefix); } } /* Let the modules process their data */ count_ref_from_wiz_list(); count_ref_from_call_outs(); if (master_ob) master_ob->ref++; else fatal("No master object\n"); /* TODO: see array.c */ if (last_insert_alist_shared_string) { MARK_STRING_REF(last_insert_alist_shared_string); } count_lex_refs(); count_compiler_refs(); count_simul_efun_refs(); #if defined(SUPPLY_PARSE_COMMAND) count_old_parse_refs(); #endif note_shared_string_table_ref(); note_otable_ref(); count_comm_refs(); count_interpreter_refs(); count_heart_beat_refs(); #ifdef RXCACHE_TABLE count_rxcache_refs(); #endif if (reserved_user_area) note_ref(reserved_user_area); if (reserved_master_area) note_ref(reserved_master_area); if (reserved_system_area) note_ref(reserved_system_area); note_ref(mud_lib); null_vector.ref++; /* Process the driver hooks */ count_ref_in_vector(driver_hook, NUM_DRIVER_HOOKS); for (i = NUM_DRIVER_HOOKS; --i >= 0; ) { if (driver_hook[i].type == T_CLOSURE && driver_hook[i].x.closure_type == CLOSURE_UNBOUND_LAMBDA) { driver_hook[i].x.closure_type = CLOSURE_LAMBDA; } } gc_status = gcInactive; /* --- Pass 4: remove stralloced strings with M_REF cleared --- */ walk_shared_strings(remove_unreferenced_string); /* --- Pass 5: Release all destructed objects --- * * It is vital that all information freed here is already known * as referenced, so we won't free it a second time in pass 6. */ dobj_count = 0; for (ob = gc_obj_list_destructed; ob; ob = next_ob) { #ifdef DEBUG dprintf1(gcollect_outfd, "DEBUG: GC frees destructed '%s'\n", (p_int)ob->name); #endif next_ob = ob->next_all; free_object(ob, "garbage collection"); dobj_count++; } for (l = stale_lambda_closures; l; ) { svalue_t sv; next_l = (lambda_t *)l->ob; l->ref = 1; sv.type = T_CLOSURE; sv.x.closure_type = CLOSURE_UNBOUND_LAMBDA; sv.u.lambda = l; l = (lambda_t *)l->ob; free_closure(&sv); } for (l = stale_misc_closures; l; l = next_l) { next_l = (lambda_t *)l->ob; xfree((char *)l); } clean_stale_mappings(); /* --- Pass 6: Release all unused memory --- */ free_unreferenced_memory(); reallocate_reserved_areas(); if (!reserved_user_area) { svalue_t *res = NULL; if (reserved_system_area) { RESET_LIMITS; CLEAR_EVAL_COST; malloc_privilege = MALLOC_MASTER; res = callback_master(STR_QUOTA_DEMON, 0); } remove_uids(res && (res->type != T_NUMBER || res->u.number) ); } /* Reconsolidate the free lists */ consolidate_freelists(); /* Finally, try to reclaim the reserved areas */ reallocate_reserved_areas(); time_last_gc = time(NULL); dprintf2(gcollect_outfd, "%s GC freed %d destructed objects.\n" , (long)time_stamp(), dobj_count); /* Lock all interactive structures (in case we're using threads) * and dispose of the written buffers. */ for (i = 0 ; i < MAX_PLAYERS; i++) { if (all_players[i] == NULL) continue; interactive_unlock(all_players[i]); } /* If the GC log was redirected, close that file and set the * logging back to the default file. */ restore_default_gc_log(); } /* garbage_collection() */ #if defined(MALLOC_TRACE) /* Some functions to print the tracing data from the memory blocks. * The show_ functions are called from smalloc directly. */ /*-------------------------------------------------------------------------*/ static void show_string (int d, char *block, int depth UNUSED) /* Print the string from memory <block> on filedescriptor <d>. */ { #ifdef __MWERKS__ # pragma unused(depth) #endif size_t len; if (block == NULL) { WRITES(d, "<null>"); } else { WRITES(d, "\""); if ((len = strlen(block)) < 70) { write(d, block, len); WRITES(d, "\""); } else { write(d, block, 50); WRITES(d, "\" (truncated, length ");writed(d, len);WRITES(d, ")"); } } } /* show_string() */ /*-------------------------------------------------------------------------*/ static void show_added_string (int d, char *block, int depth UNUSED) /* Print the string from memory <block> on filedescriptor <d>, prefixed * by 'Added string: '. */ { #ifdef __MWERKS__ # pragma unused(depth) #endif WRITES(d, "Added string: "); show_string(d, block, 0); WRITES(d, "\n"); } /*-------------------------------------------------------------------------*/ static void show_object (int d, char *block, int depth) /* Print the data about object <block> on filedescriptor <d>. */ { object_t *ob; ob = (object_t *)block; if (depth) { object_t *o; for (o = obj_list; o && o != ob; o = o->next_all) NOOP; if (!o || o->flags & O_DESTRUCTED) { WRITES(d, "Destructed object in block 0x"); write_x(d, (p_uint)((unsigned *)block - SMALLOC_OVERHEAD)); WRITES(d, "\n"); return; } } WRITES(d, "Object: "); if (ob->flags & O_DESTRUCTED) WRITES(d, "(destructed) "); show_string(d, ob->name, 0); WRITES(d, " from "); show_string(d, ob->load_name, 0); WRITES(d, ", uid: "); show_string(d, ob->user->name ? ob->user->name : "0", 0); WRITES(d, "\n"); } /* show_object() */ /*-------------------------------------------------------------------------*/ static void show_cl_literal (int d, char *block, int depth UNUSED) /* Print the data about literal closure <block> on filedescriptor <d>. */ { #ifdef __MWERKS__ # pragma unused(depth) #endif lambda_t *l; object_t *obj; l = (lambda_t *)block; WRITES(d, "Closure literal: Object "); obj = l->ob; if (obj) { if (obj->name) show_string(d, obj->name, 0); else WRITES(d, "(no name)"); if (obj->flags & O_DESTRUCTED) WRITES(d, " (destructed)"); } else WRITES(d, "<null>"); WRITES(d, ", index "); writed(d, l->function.index); WRITES(d, ", ref "); writed(d, l->ref); WRITES(d, "\n"); } /* show_cl_literal() */ /*-------------------------------------------------------------------------*/ static void show_array(int d, char *block, int depth) /* Print the array at recursion <depth> from memory <block> on * filedescriptor <d>. Recursive printing stops at <depth> == 2. */ { vector_t *a; mp_int i, j; svalue_t *svp; wiz_list_t *user; mp_int a_size; a = (vector_t *)block; /* Can't use VEC_SIZE() here, as the memory block may have been * partly overwritten by the smalloc pointers already. */ a_size = (mp_int)( malloced_size(a) - ( SMALLOC_OVERHEAD + ( sizeof(vector_t) - sizeof(svalue_t) ) / SIZEOF_CHAR_P ) ) / (sizeof(svalue_t)/SIZEOF_CHAR_P); if (depth && a != &null_vector) { int freed; wiz_list_t *wl; wl = NULL; freed = is_freed(block, sizeof(vector_t) ); if (!freed) { user = a->user; wl = all_wiz; if (user) for ( ; wl && wl != user; wl = wl->next) NOOP; } if (freed || !wl || a_size <= 0 || a_size > MAX_ARRAY_SIZE || (malloced_size((char *)a) - SMALLOC_OVERHEAD) << 2 != sizeof(vector_t) + sizeof(svalue_t) * (a_size - 1) ) { WRITES(d, "Array in freed block 0x"); write_x(d, (p_uint)((unsigned *)block - SMALLOC_OVERHEAD)); WRITES(d, "\n"); return; } } else { user = a->user; } WRITES(d, "Array "); write_x(d, (p_int)a); WRITES(d, " size ");writed(d, (p_uint)a_size); WRITES(d, ", uid: ");show_string(d, user ? user->name : "0", 0); WRITES(d, "\n"); if (depth > 2) return; i = 32 >> depth; if (i > a_size) i = a_size; for (svp = a->item; --i >= 0; svp++) { for (j = depth + 1; --j >= 0;) WRITES(d, " "); switch(svp->type) { case T_POINTER: show_array(d, (char *)svp->u.vec, depth+1); break; case T_NUMBER: writed(d, (p_uint)svp->u.number); WRITES(d, "\n"); break; case T_STRING: if (svp->x.string_type == STRING_MALLOC && is_freed(svp->u.string, 1) ) { WRITES(d, "Malloced string in freed block 0x"); write_x(d, (p_uint)((unsigned *)block - SMALLOC_OVERHEAD)); WRITES(d, "\n"); break; } if (svp->x.string_type == STRING_SHARED && is_freed(SHSTR_BLOCK(svp->u.string), sizeof(char *) + sizeof(StrRefCount) + 1) ) { WRITES(d, "Shared string in freed block 0x"); write_x(d, (p_uint)( (unsigned *)(block-sizeof(char *)-sizeof(StrRefCount)) - SMALLOC_OVERHEAD )); WRITES(d, "\n"); break; } WRITES(d, "String: "); show_string(d, svp->u.string, 0); WRITES(d, "\n"); break; case T_CLOSURE: if (svp->x.closure_type == CLOSURE_LFUN || svp->x.closure_type == CLOSURE_IDENTIFIER) show_cl_literal(d, (char *)svp->u.lambda, depth); else { WRITES(d, "Closure type "); writed(d, svp->x.closure_type); WRITES(d, "\n"); } break; case T_OBJECT: show_object(d, (char *)svp->u.ob, 1); break; default: WRITES(d, "Svalue type ");writed(d, svp->type);WRITES(d, "\n"); break; } } } /* show_array() */ /*-------------------------------------------------------------------------*/ void setup_print_block_dispatcher (void) /* Setup the tracing data dispatcher in smalloc with the show_ functions * above. Remember that the data dispatcher works by storing the file * and line information of sample allocations. We just have to make sure * to cover all possible allocation locations. * * This is here because I like to avoid smalloc calling closures, and * gcollect.c is already notorious for including almost every header file * anyway. */ { svalue_t tmp_closure; vector_t *a, *b; assert_master_ob_loaded(); tmp_closure.type = T_CLOSURE; tmp_closure.x.closure_type = CLOSURE_EFUN + F_ADD; tmp_closure.u.ob = master_ob; push_volatile_string(""); push_volatile_string(""); call_lambda(&tmp_closure, 2); store_print_block_dispatch_info(inter_sp->u.string, show_added_string); free_svalue(inter_sp--); tmp_closure.type = T_CLOSURE; tmp_closure.x.closure_type = CLOSURE_EFUN + F_ALLOCATE; tmp_closure.u.ob = master_ob; push_number(1); call_lambda(&tmp_closure, 1); store_print_block_dispatch_info(inter_sp->u.vec, show_array); free_svalue(inter_sp--); a = allocate_array(1); store_print_block_dispatch_info((char *)a, show_array); b = slice_array(a, 0, 0); store_print_block_dispatch_info((char *)b, show_array); free_array(a); free_array(b); store_print_block_dispatch_info((char *)master_ob, show_object); #ifdef CHECK_OBJECT_GC_REF note_object_allocation_info((char*)master_ob); note_program_allocation_info((char*)(master_ob->prog)); #endif current_object = master_ob; closure_literal(&tmp_closure, 0); store_print_block_dispatch_info(tmp_closure.u.lambda, show_cl_literal); free_svalue(&tmp_closure); } #endif /* MALLOC_TRACE */ #endif /* MALLOC_smalloc */ /*=========================================================================*/ /* Default functions when not using the smalloc allocator */ #if !defined(GC_SUPPORT) void garbage_collection (void) /* Free as much memory as possible and try to reallocate the * reserved areas - that's all we can do. */ { assert_master_ob_loaded(); handle_newly_destructed_objects(); free_interpreter_temporaries(); free_action_temporaries(); remove_stale_player_data(); remove_stale_call_outs(); free_defines(); free_all_local_names(); remove_unknown_identifier(); purge_action_sent(); purge_shadow_sent(); check_wizlist_for_destr(); compact_mappings(num_dirty_mappings); if (current_error_trace) { free_array(current_error_trace); current_error_trace = NULL; } if (uncaught_error_trace) { free_array(uncaught_error_trace); uncaught_error_trace = NULL; } remove_destructed_objects(); reallocate_reserved_areas(); time_last_gc = time(NULL); } #endif /* GC_SUPPORT */ #if !defined(MALLOC_TRACE) || !defined(GC_SUPPORT) void setup_print_block_dispatcher (void) { NOOP } #endif /***************************************************************************/