#include "lint.h" #include "config.h" #include "interpret.h" #include "object.h" #include "exec.h" #include "sent.h" #include "comm.h" #include "smalloc.h" #include "instrs.h" #include "lang.h" #include "wiz_list.h" #define NO_INCREMENT_STRING_REF #include "stralloc.h" extern void dprintf1 PROT((int, char *, p_int)); /* The referencing code for dynamic data should mirror the destructor code, * thus, memory leaks can show up as soon as the memory has allocated. */ #ifdef MAPPINGS void walk_mapping PROT(( struct mapping *, void (*)(struct svalue *, struct svalue *, char *), char * )); extern mp_int num_dirty_mappings; extern void compact_mappings PROT((mp_int)); #endif extern void free_all_sent(); extern void remove_destructed_objects(); #if defined(MALLOC_smalloc) #define STRING_REFS(str) (*(unsigned short *)((char *) (str)\ - sizeof(unsigned short))) #define MARK_STRING_REF(str) ((void)(\ STRING_REFS(str)++ || (\ CHECK_REF( (str)-sizeof(short)-sizeof(char *) ) ||\ /* reached max ref count, which is given as 0... */ \ STRING_REFS(str)--\ ))\ ) extern int d_flag; #ifdef SMALLOC_TRACE extern void writex PROT((int, int)); extern void writed PROT((int, int)); extern int is_freed PROT((char *, p_uint)); #define WRITES(d, s) write((d), (s), strlen(s)) #define WRITE_SMALLOC_TRACE(p) (WRITES(2, ((char **)(p))[-3]), \ WRITES(2, " "), \ ((p_uint (*)PROT((int, int)))writed)(2, ((p_uint *)(p))[-2]), \ WRITES(2, "\n") ), #else #define WRITE_SMALLOC_TRACE(p) #endif #define CLEAR_REF(p) ( ((p_uint *)(p))[-SMALLOC_OVERHEAD] &= ~M_REF ) #define MARK_REF(p) ( ((p_uint *)(p))[-SMALLOC_OVERHEAD] |= M_REF ) #define TEST_REF(p) ( !( ((p_uint *)(p))[-SMALLOC_OVERHEAD] & M_REF ) ) #define CHECK_REF(p) ( TEST_REF(p) && ( MARK_REF(p),MY_TRUE ) ) #define NOTE_REF(p) \ ( \ TEST_REF(p) ? \ MARK_REF(p) \ : ( WRITE_SMALLOC_TRACE(p) \ ((VOLATILE p_uint (*)PROT((char *)))fatal)\ ("memory block referenced twice\n") ) \ ) void remove_unreferenced_string(start, string) char *start, *string; { if (TEST_REF(start)) { dprintf1(2, "\ '%s' was left unreferenced in the shared string table, freeing now.\n", (p_int)string ); MARK_REF(start); STRING_REFS(string)++; free_string(string); } } struct object *gc_obj_list_destructed; struct lambda *stale_lambda_closures, *stale_misc_closures; extern struct mapping *stale_mappings; void clear_inherit_ref(p) struct program *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. */ struct program *p2; p2 = p->inherit[i].prog; if (p2->ref) { p2->ref = 0; clear_inherit_ref(p2); } } } void mark_program_ref(p) struct program *p; { if (CHECK_REF(p)) { int i; unsigned char *program = p->program; uint32 *functions = p->functions; char **strings; struct variable *variable_names; if (p->ref++) fatal("First reference to program, but ref count != 0\n"); 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); } } strings = p->strings; for (i = p->num_strings; --i >= 0; ) { char *str = *strings++; MARK_STRING_REF(str); } variable_names = p->variable_names; for (i = p->num_variables; --i >= 0; variable_names++) MARK_STRING_REF(variable_names->name); for (i=0; i< p->num_inherited; i++) { mark_program_ref(p->inherit[i].prog); } NOTE_REF(p->name); } else { if (!p->ref++) fatal("Program block referenced as something else\n"); } } static void note_sentence_ref(p) struct sentence *p; { do { if (p->function) MARK_STRING_REF(p->function); if (p->verb) MARK_STRING_REF(p->verb); NOTE_REF(p); } while (p = p->next); } void clear_map_ref_filter PROT((struct svalue *, struct svalue *, char *)); void reference_destructed_object(ob) struct object *ob; { if (TEST_REF(ob)) { if (ob->ref) fatal("First reference to destructed object, but ref count != 0\n"); /* Destructed objects are not swapped */ ob->next_all = gc_obj_list_destructed; gc_obj_list_destructed = ob; MARK_REF(ob); mark_program_ref(ob->prog); NOTE_REF(ob->name); ob->ref++; } else { if (!ob->ref) { WRITE_SMALLOC_TRACE(ob) ((VOLATILE p_uint (*)PROT((char *)))fatal) ("Destructed object referenced as something else\n"); } } } void note_malloced_block_ref(p) char *p; { NOTE_REF(p); } static void count_ref_in_closure(csvp) struct svalue *csvp; { extern struct object *master_ob; struct lambda *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-F_OFFSET+CLOSURE_EFUN; csvp->u.ob = master_ob; master_ob->ref++; } return; } if (type != CLOSURE_UNBOUND_LAMBDA) { struct object *ob; ob = l->ob; if (ob->flags & O_DESTRUCTED) { l->ref = -1; if (type == CLOSURE_LAMBDA) { l->ob = (struct object *)stale_lambda_closures; stale_lambda_closures = l; } else { l->ob = (struct object *)stale_misc_closures; stale_misc_closures = l; } 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-F_OFFSET+CLOSURE_EFUN; csvp->u.ob = master_ob; master_ob->ref++; } } else { ob->ref++; } } if (CLOSURE_HAS_CODE(type)) { mp_int num_values; struct svalue *svp; svp = (struct svalue *)l; if ( (num_values = EXTRACT_UCHAR(l->function.code)) == 0xff) num_values = svp[-0xff].u.number; svp -= num_values; NOTE_REF(svp); count_ref_in_vector(svp, num_values); } else { NOTE_REF(l); if (type == CLOSURE_BOUND_LAMBDA) { struct lambda *l2 = l->function.lambda; if (!l2->ref++) { struct svalue sv; sv.type = T_CLOSURE; sv.x.closure_type = CLOSURE_UNBOUND_LAMBDA; sv.u.lambda = l2; count_ref_in_closure(&sv); } } } } static void clear_ref_in_closure(l, type) struct lambda *l; ph_int type; { if (CLOSURE_HAS_CODE(type)) { mp_int num_values; struct svalue *svp; svp = (struct svalue *)l; if ( (num_values = EXTRACT_UCHAR(l->function.code)) == 0xff) num_values = svp[-0xff].u.number; svp -= num_values; clear_ref_in_vector(svp, num_values); } else if (type == CLOSURE_BOUND_LAMBDA) { struct lambda *l2 = l->function.lambda; if (l2->ref) { l2->ref = 0; clear_ref_in_closure(l2, CLOSURE_UNBOUND_LAMBDA); } } if (type != CLOSURE_UNBOUND_LAMBDA && l->ob->flags & O_DESTRUCTED && l->ob->ref /* block against bad efficency due to multiple refs */ ) { l->ob->ref = 0; l->ob->prog->ref = 0; clear_inherit_ref(l->ob->prog); } } void count_ref_from_string(p) char *p; { MARK_STRING_REF(p); } void count_ref_in_vector(svp, num) struct svalue *svp; int num; { extern struct vector null_vector; struct svalue *p; for (p = svp; p < svp+num; p++) { switch(p->type) { case T_OBJECT: { struct object *ob; ob = p->u.ob; if (ob->flags & O_DESTRUCTED) { p->type = T_NUMBER; p->u.number = 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_ref_in_vector(&p->u.vec->item[0], p->u.vec->size); } p->u.vec->ref++; continue; #ifdef MAPPINGS case T_MAPPING: if (CHECK_REF(p->u.map)) { extern void count_ref_in_mapping PROT((struct mapping *)); struct mapping *m; struct condensed_mapping *cm; int num_values; m = p->u.map; cm = m->condensed; num_values = m->num_values; 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); } p->u.map->ref++; continue; #endif /* MAPPINGS */ case T_STRING: switch(p->x.string_type) { case STRING_MALLOC: NOTE_REF(p->u.string); 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 { extern struct object *master_ob; struct object *ob; ob = p->u.ob; if (ob->flags & O_DESTRUCTED) { p->x.closure_type = F_UNDEF-F_OFFSET+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; } } } void clear_map_ref_filter(key, data, extra) struct svalue *key, *data; char *extra; { clear_ref_in_vector(key, 1); clear_ref_in_vector(data, (p_int)extra); } /* * Clear the ref count for vectors */ void clear_ref_in_vector(svp, num) struct svalue *svp; int num; { struct svalue *p; 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. */ if (p->u.ob->flags & O_DESTRUCTED && p->u.ob->ref) { p->u.ob->ref = 0; p->u.ob->prog->ref = 0; clear_inherit_ref(p->u.ob->prog); } 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], p->u.vec->size); continue; #ifdef MAPPINGS case T_MAPPING: if (p->u.map->ref) { struct mapping *m; 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; #endif /* MAPPINGS */ case T_CLOSURE: if (CLOSURE_MALLOCED(p->x.closure_type)) { struct lambda *l; l = p->u.lambda; if (l->ref) { l->ref = 0; clear_ref_in_closure(l, p->x.closure_type); } } else if (p->u.ob->flags & O_DESTRUCTED && p->u.ob->ref) { p->u.ob->ref = 0; p->u.ob->prog->ref = 0; clear_inherit_ref(p->u.ob->prog); } continue; } } } /* * Loop through every object and variable in the game and check * all reference counts. This will surely take some time, and should * only be used for debugging. */ void garbage_collection() { extern struct object *master_ob; extern void clear_M_REF_flags(); extern void clear_shared_string_refs(); extern void walk_shared_strings PROT(( void (*)(char *, char *) )); extern void reallocate_reserved_areas(); extern struct interactive *all_players[MAX_PLAYERS]; extern char *last_insert_alist_shared_string; extern int malloc_privilege; extern int32 initial_eval_cost, eval_cost, assigned_eval_cost; extern int out_of_memory; extern char *reserved_user_area, *reserved_master_area, *reserved_system_area; extern char *mud_lib; extern struct vector null_vector; struct object *ob, *next_ob; struct lambda *l, *next_l; int i; /* * Pass 0: dispose of some unnecessary stuff. */ malloc_privilege = MALLOC_MASTER; CLEAR_EVAL_COST; out_of_memory = 0; assert_master_ob_loaded(); malloc_privilege = MALLOC_SYSTEM; remove_destructed_objects(); free_all_sent(); compact_mappings(num_dirty_mappings); free_interpreter_temporaries(); remove_stale_call_outs(); free_defines(); free_all_local_names(); remove_unknown_identifier(); /* * Pass 1: clear the M_REF flag in all malloced blocks. */ clear_M_REF_flags(); /* * Pass 2: clear the ref counts. */ if (d_flag > 3) { debug_message("start of garbage_collection\n"); } /* * List of all objects. */ for (ob=obj_list; ob; ob = ob->next_all) { int was_swapped; if (d_flag > 4) { debug_message("clearing refs for object '%s'\n",ob->name); } if (was_swapped = ob->flags & O_SWAPPED) { load_ob_from_swap(ob); /* don't clear the crogram ref count. It is 1 */ } else { /* Take special care of inherited programs, the associated * objects might me destructed. */ ob->prog->ref = 0; } clear_inherit_ref(ob->prog); ob->ref = 0; clear_ref_in_vector(ob->variables, ob->prog->num_variables); if (was_swapped) swap(ob); } if (d_flag > 3) { debug_message("ref counts referenced by obj_list cleared\n"); } /* The interactives */ for(i = 0 ; i < MAX_PLAYERS; i++) { struct input_to * it; struct ed_buffer *buf; if (all_players[i] == 0) continue; if (it = all_players[i]->input_to) { clear_ref_in_vector(it->arg, it->num_arg); if (it->ob->flags & O_DESTRUCTED && it->ob->ref) { it->ob->ref = 0; it->ob->prog->ref = 0; clear_inherit_ref(it->ob->prog); } } clear_ref_in_vector(&all_players[i]->prompt, 1); if (ob = all_players[i]->snoop_by) { if (ob->interactive == 0) { /* snooping monster */ if (ob->flags & O_DESTRUCTED && ob->ref) { ob->ref = 0; ob->prog->ref = 0; clear_inherit_ref(ob->prog); } } } /* end of snoop-processing */ if (buf = all_players[i]->ed_buffer) { clear_ed_buffer_refs(buf); } /* end of ed-buffer processing */ if (ob = all_players[i]->modify_command) { if (ob->flags & O_DESTRUCTED && ob->ref) { ob->ref = 0; ob->prog->ref = 0; clear_inherit_ref(ob->prog); } } } /* * The shared string table. */ clear_shared_string_refs(); /* The wizlist */ clear_ref_from_wiz_list(); /* call_outs */ clear_ref_from_call_outs(); clear_simul_efun_refs(); clear_interpreter_refs(); null_vector.ref = 0; /* * Pass 3: Compute the ref counts, and set M_REF where appropriate. */ gc_obj_list_destructed = 0; stale_lambda_closures = 0; stale_misc_closures = 0; stale_mappings = 0; /* * List of all objects. */ for (ob=obj_list; ob; ob = ob->next_all) { int was_swapped; if (was_swapped = ob->flags & O_SWAPPED) { load_ob_from_swap(ob); CLEAR_REF(ob->prog); ob->prog->ref = 0; } ob->ref++; NOTE_REF(ob); mark_program_ref(ob->prog); if (ob->living_name) count_ref_from_string(ob->living_name); count_ref_in_vector(ob->variables, ob->prog->num_variables); if (ob->sent) note_sentence_ref(ob->sent); NOTE_REF(ob->name); if (was_swapped) { swap(ob); } } if (d_flag > 3) { debug_message("obj_list evaluated\n"); } /* * The interactives. */ for(i = 0 ; i < MAX_PLAYERS; i++) { struct ed_buffer *buf; struct input_to * it; if (all_players[i] == 0) continue; NOTE_REF(all_players[i]); /* There are no destructed interactives */ all_players[i]->ob->ref++; if (ob = all_players[i]->snoop_by) { if (ob->interactive == 0) { /* snooping monster */ if (ob->flags & O_DESTRUCTED) { all_players[i]->snoop_by = 0; reference_destructed_object(ob); } else { ob->ref++; } } } /* end of snoop-processing */ if (buf = all_players[i]->ed_buffer) { NOTE_REF(buf); count_ed_buffer_refs(buf); } /* end of ed-buffer processing */ if (it = all_players[i]->input_to) { /* To avoid calling too high-level functions, we want the * struct input_to not to be freed by now. * Thus, we reference the object even if it is destructed. */ NOTE_REF(it); ob = it->ob; if (!ob->ref) { /* destructed */ NOTE_REF(ob); mark_program_ref(ob->prog); NOTE_REF(ob->name); } ob->ref++; MARK_STRING_REF(it->function); count_ref_in_vector(it->arg, it->num_arg); } /* end of input_to processing */ if (ob = all_players[i]->modify_command) { if (ob->flags & O_DESTRUCTED) { all_players[i]->modify_command = 0; reference_destructed_object(ob); } else { ob->ref++; } } if (all_players[i]->default_err_message) { count_ref_from_string(all_players[i]->default_err_message); } count_ref_in_vector(&all_players[i]->prompt, 1); if (all_players[i]->trace_prefix) { count_ref_from_string(all_players[i]->trace_prefix); } } /* The wizlist */ count_ref_from_wiz_list(); /* call_outs */ count_ref_from_call_outs(); if (master_ob) master_ob->ref++; else fatal("No master object\n"); if (last_insert_alist_shared_string) { MARK_STRING_REF(last_insert_alist_shared_string); } count_lex_refs(); count_compiler_refs(); count_simul_efun_refs(); note_shared_string_table_ref(); note_otable_ref(); count_iptable_ref(); count_interpreter_refs(); 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++; /* * 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. */ for (ob = gc_obj_list_destructed; ob; ob = next_ob) { next_ob = ob->next_all; free_object(ob, "garbage collection"); } for (l = stale_lambda_closures; l; ) { struct svalue sv; next_l = (struct lambda *)l->ob; l->ref = 1; sv.type = T_CLOSURE; sv.x.closure_type = CLOSURE_UNBOUND_LAMBDA; sv.u.lambda = l; l = (struct lambda *)l->ob; free_closure(&sv); } for (l = stale_misc_closures; l; l = next_l) { next_l = (struct lambda *)l->ob; xfree((char *)l); } clean_stale_mappings(); /* * Pass 6: Release all unused memory. */ free_unreferenced_memory(); reallocate_reserved_areas(); if (!reserved_user_area) { void remove_uids PROT((int)); struct svalue *res = 0; if (reserved_system_area) { CLEAR_EVAL_COST; malloc_privilege = MALLOC_MASTER; res = apply_master_ob("quota_demon", 0); } remove_uids(res && (res->type != T_NUMBER || res->u.number) ); } reallocate_reserved_areas(); } void remove_uids(smart) int smart; { } #else /* not MALLOC_smalloc :-( */ void garbage_collection() { assert_master_ob_loaded(); remove_destructed_objects(); free_all_sent(); compact_mappings(num_dirty_mappings); free_interpreter_temporaries(); remove_stale_call_outs(); free_defines(); free_all_local_names(); remove_unknown_identifier(); reallocate_reserved_areas(); } #endif /* MALLOC_smalloc */ static void clear_program_id(p) struct program *p; { int i; if (!p->id_number) return; p->id_number = 0; for (i=0; i< p->num_inherited; i++) { clear_program_id(p->inherit[i].prog); } } static void renumber_program(p) struct program *p; { extern int32 current_id_number; int i; if (p->id_number) return; p->id_number = ++current_id_number; for (i=0; i< p->num_inherited; i++) { renumber_program(p->inherit[i].prog); } } int32 renumber_programs() { extern int32 current_id_number; extern void invalidate_apply_low_cache(); struct object *ob; current_id_number = 0; for (ob = obj_list; ob; ob = ob->next_all) { if ( !(ob->flags & O_SWAPPED) ) clear_program_id(ob->prog); } for (ob = obj_list; ob; ob = ob->next_all) { if ( !(ob->flags & O_SWAPPED) ) renumber_program(ob->prog); } invalidate_apply_low_cache(); return ++current_id_number; } #if defined(SMALLOC_TRACE) && defined(MALLOC_smalloc) static void show_string(d, block, depth) int d; char *block; int depth; { mp_int len; 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, ")"); } } static void show_added_string(d, block, depth) int d; char *block; int depth; { WRITES(d, "Added string: "); show_string(d, block, 0); WRITES(d, "\n"); } static void show_object(d, block, depth) int d; char *block; int depth; { struct object *ob; ob = (struct object *)block; if (depth) { struct object *o; for (o = obj_list; o && o != ob; o = o->next_all); if (!o) { WRITES(d, "Destructed object in block 0x"); writex(d, (unsigned)((unsigned *)block - SMALLOC_OVERHEAD)); WRITES(d, "\n"); return; } } WRITES(d, "Object: "); show_string(d, ob->name, 0); WRITES(d, ", uid: "); show_string(d, ob->user ? ob->user->name : "0", 0); WRITES(d, "\n"); } static void show_array(d, block, depth) int d; char *block; int depth; { extern struct vector null_vector; struct vector *a; mp_int i, j; struct svalue *svp; struct wiz_list *user; a = (struct vector *)block; if (depth && a != &null_vector) { extern struct wiz_list *all_wiz; int freed; struct wiz_list *wl; freed = is_freed(block, sizeof(struct vector) ); if (!freed) { user = a->user; wl = all_wiz; if (user) for ( ; wl && wl != user; wl = wl->next); } if (freed || !wl || a->size <= 0 || a->size > MAX_ARRAY_SIZE || malloced_size((char *)a) - SMALLOC_OVERHEAD << 2 != sizeof(struct vector) + sizeof(struct svalue) * (a->size - 1) ) { WRITES(d, "Array in freed block 0x"); writex(d, (unsigned)((unsigned *)block - SMALLOC_OVERHEAD)); WRITES(d, "\n"); return; } } else { user = a->user; } WRITES(d, "Array size ");writed(d, 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, 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"); writex(d, (unsigned)((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(short) + 1) ) { WRITES(d, "Shared string in freed block 0x"); writex(d, (unsigned)( (unsigned *)(block-sizeof(char *)-sizeof(short)) - SMALLOC_OVERHEAD )); WRITES(d, "\n"); break; } WRITES(d, "String: "); show_string(d, svp->u.string, 0); 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; } } } void setup_print_block_dispatcher() /* This is here because I like to avoid smalloc calling closures. * gcollect.c is already notorious for including almost every header file. */ { extern struct object *master_ob; extern struct svalue *inter_sp; extern void store_print_block_dispatch_info PROT((char *, void (*)(int, char *, int))); struct svalue tmp_closure; struct vector *a; assert_master_ob_loaded(); tmp_closure.type = T_CLOSURE; tmp_closure.x.closure_type = CLOSURE_EFUN + F_ADD - F_OFFSET; tmp_closure.u.ob = master_ob; push_constant_string(""); push_constant_string(""); call_lambda(&tmp_closure, 2); store_print_block_dispatch_info(inter_sp->u.string, show_added_string); free_svalue(inter_sp--); a = allocate_array(1); store_print_block_dispatch_info((char *)a, show_array); free_vector(a); store_print_block_dispatch_info((char *)master_ob, show_object); } #else void setup_print_block_dispatcher() {} #endif /* SMALLOC_TRACE */