/* * Copyright 1988, 1989 Hans-J. Boehm, Alan J. Demers * Copyright (c) 1991-1994 by Xerox Corporation. All rights reserved. * * THIS MATERIAL IS PROVIDED AS IS, WITH ABSOLUTELY NO WARRANTY EXPRESSED * OR IMPLIED. ANY USE IS AT YOUR OWN RISK. * * Permission is hereby granted to use or copy this program * for any purpose, provided the above notices are retained on all copies. * Permission to modify the code and to distribute modified code is granted, * provided the above notices are retained, and a notice that the code was * modified is included with the above copyright notice. */ /* Boehm, July 31, 1995 5:02 pm PDT */ #include #include #define I_HIDE_POINTERS /* To make GC_call_with_alloc_lock visible */ #include "gc_priv.h" #ifdef SOLARIS_THREADS # include #endif #ifdef MSWIN32 # include #endif # ifdef THREADS # ifdef PCR # include "il/PCR_IL.h" PCR_Th_ML GC_allocate_ml; # else # if defined(SRC_M3) || defined(GENERIC_THREADS) /* Critical section counter is defined in the M3 runtime */ /* That's all we use. */ # else # ifdef SOLARIS_THREADS mutex_t GC_allocate_ml; /* Implicitly initialized. */ # else # ifdef WIN32_THREADS GC_API CRITICAL_SECTION GC_allocate_ml; # else # if defined(IRIX_THREADS) || defined(LINUX_THREADS) \ || defined(IRIX_JDK_THREADS) # ifdef UNDEFINED pthread_mutex_t GC_allocate_ml = PTHREAD_MUTEX_INITIALIZER; # endif pthread_t GC_lock_holder = NO_THREAD; # else --> declare allocator lock here # endif # endif # endif # endif # endif # endif GC_FAR struct _GC_arrays GC_arrays /* = { 0 } */; GC_bool GC_debugging_started = FALSE; /* defined here so we don't have to load debug_malloc.o */ void (*GC_check_heap)() = (void (*)())0; void (*GC_start_call_back)() = (void (*)())0; ptr_t GC_stackbottom = 0; GC_bool GC_dont_gc = 0; GC_bool GC_quiet = 0; /*ARGSUSED*/ GC_PTR GC_default_oom_fn GC_PROTO((size_t bytes_requested)) { return(0); } GC_PTR (*GC_oom_fn) GC_PROTO((size_t bytes_requested)) = GC_default_oom_fn; extern signed_word GC_mem_found; # ifdef MERGE_SIZES /* Set things up so that GC_size_map[i] >= words(i), */ /* but not too much bigger */ /* and so that size_map contains relatively few distinct entries */ /* This is stolen from Russ Atkinson's Cedar quantization */ /* alogrithm (but we precompute it). */ void GC_init_size_map() { register unsigned i; /* Map size 0 to 1. This avoids problems at lower levels. */ GC_size_map[0] = 1; /* One word objects don't have to be 2 word aligned. */ for (i = 1; i < sizeof(word); i++) { GC_size_map[i] = 1; } GC_size_map[sizeof(word)] = ROUNDED_UP_WORDS(sizeof(word)); for (i = sizeof(word) + 1; i <= 8 * sizeof(word); i++) { # ifdef ALIGN_DOUBLE GC_size_map[i] = (ROUNDED_UP_WORDS(i) + 1) & (~1); # else GC_size_map[i] = ROUNDED_UP_WORDS(i); # endif } for (i = 8*sizeof(word) + 1; i <= 16 * sizeof(word); i++) { GC_size_map[i] = (ROUNDED_UP_WORDS(i) + 1) & (~1); } /* We leave the rest of the array to be filled in on demand. */ } /* Fill in additional entries in GC_size_map, including the ith one */ /* We assume the ith entry is currently 0. */ /* Note that a filled in section of the array ending at n always */ /* has length at least n/4. */ void GC_extend_size_map(i) word i; { word orig_word_sz = ROUNDED_UP_WORDS(i); word word_sz = orig_word_sz; register word byte_sz = WORDS_TO_BYTES(word_sz); /* The size we try to preserve. */ /* Close to to i, unless this would */ /* introduce too many distinct sizes. */ word smaller_than_i = byte_sz - (byte_sz >> 3); word much_smaller_than_i = byte_sz - (byte_sz >> 2); register word low_limit; /* The lowest indexed entry we */ /* initialize. */ register word j; if (GC_size_map[smaller_than_i] == 0) { low_limit = much_smaller_than_i; while (GC_size_map[low_limit] != 0) low_limit++; } else { low_limit = smaller_than_i + 1; while (GC_size_map[low_limit] != 0) low_limit++; word_sz = ROUNDED_UP_WORDS(low_limit); word_sz += word_sz >> 3; if (word_sz < orig_word_sz) word_sz = orig_word_sz; } # ifdef ALIGN_DOUBLE word_sz += 1; word_sz &= ~1; # endif if (word_sz > MAXOBJSZ) { word_sz = MAXOBJSZ; } /* If we can fit the same number of larger objects in a block, */ /* do so. */ { size_t number_of_objs = BODY_SZ/word_sz; word_sz = BODY_SZ/number_of_objs; # ifdef ALIGN_DOUBLE word_sz &= ~1; # endif } byte_sz = WORDS_TO_BYTES(word_sz); # ifdef ADD_BYTE_AT_END /* We need one extra byte; don't fill in GC_size_map[byte_sz] */ byte_sz--; # endif for (j = low_limit; j <= byte_sz; j++) GC_size_map[j] = word_sz; } # endif /* * The following is a gross hack to deal with a problem that can occur * on machines that are sloppy about stack frame sizes, notably SPARC. * Bogus pointers may be written to the stack and not cleared for * a LONG time, because they always fall into holes in stack frames * that are not written. We partially address this by clearing * sections of the stack whenever we get control. */ word GC_stack_last_cleared = 0; /* GC_no when we last did this */ # ifdef THREADS # define CLEAR_SIZE 2048 # else # define CLEAR_SIZE 213 # endif # define DEGRADE_RATE 50 word GC_min_sp; /* Coolest stack pointer value from which we've */ /* already cleared the stack. */ # ifdef STACK_GROWS_DOWN # define COOLER_THAN > # define HOTTER_THAN < # define MAKE_COOLER(x,y) if ((word)(x)+(y) > (word)(x)) {(x) += (y);} \ else {(x) = (word)ONES;} # define MAKE_HOTTER(x,y) (x) -= (y) # else # define COOLER_THAN < # define HOTTER_THAN > # define MAKE_COOLER(x,y) if ((word)(x)-(y) < (word)(x)) {(x) -= (y);} else {(x) = 0;} # define MAKE_HOTTER(x,y) (x) += (y) # endif word GC_high_water; /* "hottest" stack pointer value we have seen */ /* recently. Degrades over time. */ word GC_words_allocd_at_reset; #if defined(ASM_CLEAR_CODE) && !defined(THREADS) extern ptr_t GC_clear_stack_inner(); #endif #if !defined(ASM_CLEAR_CODE) && !defined(THREADS) /* Clear the stack up to about limit. Return arg. */ /*ARGSUSED*/ ptr_t GC_clear_stack_inner(arg, limit) ptr_t arg; word limit; { word dummy[CLEAR_SIZE]; BZERO(dummy, CLEAR_SIZE*sizeof(word)); if ((word)(dummy) COOLER_THAN limit) { (void) GC_clear_stack_inner(arg, limit); } /* Make sure the recursive call is not a tail call, and the bzero */ /* call is not recognized as dead code. */ GC_noop1((word)dummy); return(arg); } #endif /* Clear some of the inaccessible part of the stack. Returns its */ /* argument, so it can be used in a tail call position, hence clearing */ /* another frame. */ ptr_t GC_clear_stack(arg) ptr_t arg; { register word sp = (word)GC_approx_sp(); /* Hotter than actual sp */ # ifdef THREADS word dummy[CLEAR_SIZE]; # else register word limit; # endif # define SLOP 400 /* Extra bytes we clear every time. This clears our own */ /* activation record, and should cause more frequent */ /* clearing near the cold end of the stack, a good thing. */ # define GC_SLOP 4000 /* We make GC_high_water this much hotter than we really saw */ /* saw it, to cover for GC noise etc. above our current frame. */ # define CLEAR_THRESHOLD 100000 /* We restart the clearing process after this many bytes of */ /* allocation. Otherwise very heavily recursive programs */ /* with sparse stacks may result in heaps that grow almost */ /* without bounds. As the heap gets larger, collection */ /* frequency decreases, thus clearing frequency would decrease, */ /* thus more junk remains accessible, thus the heap gets */ /* larger ... */ # ifdef THREADS BZERO(dummy, CLEAR_SIZE*sizeof(word)); # else if (GC_gc_no > GC_stack_last_cleared) { /* Start things over, so we clear the entire stack again */ if (GC_stack_last_cleared == 0) GC_high_water = (word) GC_stackbottom; GC_min_sp = GC_high_water; GC_stack_last_cleared = GC_gc_no; GC_words_allocd_at_reset = GC_words_allocd; } /* Adjust GC_high_water */ MAKE_COOLER(GC_high_water, WORDS_TO_BYTES(DEGRADE_RATE) + GC_SLOP); if (sp HOTTER_THAN GC_high_water) { GC_high_water = sp; } MAKE_HOTTER(GC_high_water, GC_SLOP); limit = GC_min_sp; MAKE_HOTTER(limit, SLOP); if (sp COOLER_THAN limit) { limit &= ~0xf; /* Make it sufficiently aligned for assembly */ /* implementations of GC_clear_stack_inner. */ GC_min_sp = sp; return(GC_clear_stack_inner(arg, limit)); } else if (WORDS_TO_BYTES(GC_words_allocd - GC_words_allocd_at_reset) > CLEAR_THRESHOLD) { /* Restart clearing process, but limit how much clearing we do. */ GC_min_sp = sp; MAKE_HOTTER(GC_min_sp, CLEAR_THRESHOLD/4); if (GC_min_sp HOTTER_THAN GC_high_water) GC_min_sp = GC_high_water; GC_words_allocd_at_reset = GC_words_allocd; } # endif return(arg); } /* Return a pointer to the base address of p, given a pointer to a */ /* an address within an object. Return 0 o.w. */ # ifdef __STDC__ GC_PTR GC_base(GC_PTR p) # else GC_PTR GC_base(p) GC_PTR p; # endif { register word r; register struct hblk *h; register bottom_index *bi; register hdr *candidate_hdr; register word limit; r = (word)p; if (!GC_is_initialized) return 0; h = HBLKPTR(r); GET_BI(r, bi); candidate_hdr = HDR_FROM_BI(bi, r); if (candidate_hdr == 0) return(0); /* If it's a pointer to the middle of a large object, move it */ /* to the beginning. */ while (IS_FORWARDING_ADDR_OR_NIL(candidate_hdr)) { h = FORWARDED_ADDR(h,candidate_hdr); r = (word)h + HDR_BYTES; candidate_hdr = HDR(h); } if (candidate_hdr -> hb_map == GC_invalid_map) return(0); /* Make sure r points to the beginning of the object */ r &= ~(WORDS_TO_BYTES(1) - 1); { register int offset = (char *)r - (char *)(HBLKPTR(r)); register signed_word sz = candidate_hdr -> hb_sz; # ifdef ALL_INTERIOR_POINTERS register map_entry_type map_entry; map_entry = MAP_ENTRY((candidate_hdr -> hb_map), offset); if (map_entry == OBJ_INVALID) { return(0); } r -= WORDS_TO_BYTES(map_entry); limit = r + WORDS_TO_BYTES(sz); # else register int correction; offset = BYTES_TO_WORDS(offset - HDR_BYTES); correction = offset % sz; r -= (WORDS_TO_BYTES(correction)); limit = r + WORDS_TO_BYTES(sz); if (limit > (word)(h + 1) && sz <= BYTES_TO_WORDS(HBLKSIZE) - HDR_WORDS) { return(0); } # endif if ((word)p >= limit) return(0); } return((GC_PTR)r); } /* Return the size of an object, given a pointer to its base. */ /* (For small obects this also happens to work from interior pointers, */ /* but that shouldn't be relied upon.) */ # ifdef __STDC__ size_t GC_size(GC_PTR p) # else size_t GC_size(p) GC_PTR p; # endif { register int sz; register hdr * hhdr = HDR(p); sz = WORDS_TO_BYTES(hhdr -> hb_sz); if (sz < 0) { return(-sz); } else { return(sz); } } size_t GC_get_heap_size GC_PROTO(()) { return ((size_t) GC_heapsize); } size_t GC_get_bytes_since_gc GC_PROTO(()) { return ((size_t) WORDS_TO_BYTES(GC_words_allocd)); } GC_bool GC_is_initialized = FALSE; void GC_init() { DCL_LOCK_STATE; DISABLE_SIGNALS(); LOCK(); GC_init_inner(); UNLOCK(); ENABLE_SIGNALS(); } #ifdef MSWIN32 extern void GC_init_win32(); #endif extern void GC_setpagesize(); void GC_init_inner() { # ifndef THREADS word dummy; # endif if (GC_is_initialized) return; GC_setpagesize(); GC_exclude_static_roots(beginGC_arrays, end_gc_area); # ifdef PRINTSTATS if ((ptr_t)endGC_arrays != (ptr_t)(&GC_obj_kinds)) { GC_printf0("Reordering linker, didn't exclude obj_kinds\n"); } # endif # ifdef MSWIN32 GC_init_win32(); # endif # if defined(LINUX) && defined(POWERPC) GC_init_linuxppc(); # endif # if defined(LINUX) && defined(SPARC) GC_init_linuxsparc(); # endif # ifdef SOLARIS_THREADS GC_thr_init(); /* We need dirty bits in order to find live stack sections. */ GC_dirty_init(); # endif # if defined(IRIX_THREADS) || defined(LINUX_THREADS) \ || defined(IRIX_JDK_THREADS) GC_thr_init(); # endif # if !defined(THREADS) || defined(SOLARIS_THREADS) || defined(WIN32_THREADS) \ || defined(IRIX_THREADS) || defined(LINUX_THREADS) || defined(GENERIC_THREADS) if (GC_stackbottom == 0) { GC_stackbottom = GC_get_stack_base(); } # endif if (sizeof (ptr_t) != sizeof(word)) { ABORT("sizeof (ptr_t) != sizeof(word)\n"); } if (sizeof (signed_word) != sizeof(word)) { ABORT("sizeof (signed_word) != sizeof(word)\n"); } if (sizeof (struct hblk) != HBLKSIZE) { ABORT("sizeof (struct hblk) != HBLKSIZE\n"); } # ifndef THREADS # if defined(STACK_GROWS_UP) && defined(STACK_GROWS_DOWN) ABORT( "Only one of STACK_GROWS_UP and STACK_GROWS_DOWN should be defd\n"); # endif # if !defined(STACK_GROWS_UP) && !defined(STACK_GROWS_DOWN) ABORT( "One of STACK_GROWS_UP and STACK_GROWS_DOWN should be defd\n"); # endif # ifdef STACK_GROWS_DOWN if ((word)(&dummy) > (word)GC_stackbottom) { GC_err_printf0( "STACK_GROWS_DOWN is defd, but stack appears to grow up\n"); # ifndef UTS4 /* Compiler bug workaround */ GC_err_printf2("sp = 0x%lx, GC_stackbottom = 0x%lx\n", (unsigned long) (&dummy), (unsigned long) GC_stackbottom); # endif ABORT("stack direction 3\n"); } # else if ((word)(&dummy) < (word)GC_stackbottom) { GC_err_printf0( "STACK_GROWS_UP is defd, but stack appears to grow down\n"); GC_err_printf2("sp = 0x%lx, GC_stackbottom = 0x%lx\n", (unsigned long) (&dummy), (unsigned long) GC_stackbottom); ABORT("stack direction 4"); } # endif # endif # if !defined(_AUX_SOURCE) || defined(__GNUC__) if ((word)(-1) < (word)0) { GC_err_printf0("The type word should be an unsigned integer type\n"); GC_err_printf0("It appears to be signed\n"); ABORT("word"); } # endif if ((signed_word)(-1) >= (signed_word)0) { GC_err_printf0( "The type signed_word should be a signed integer type\n"); GC_err_printf0("It appears to be unsigned\n"); ABORT("signed_word"); } /* Add initial guess of root sets. Do this first, since sbrk(0) */ /* might be used. */ GC_register_data_segments(); GC_init_headers(); GC_bl_init(); GC_mark_init(); if (!GC_expand_hp_inner((word)MINHINCR)) { GC_err_printf0("Can't start up: not enough memory\n"); EXIT(); } /* Preallocate large object map. It's otherwise inconvenient to */ /* deal with failure. */ if (!GC_add_map_entry((word)0)) { GC_err_printf0("Can't start up: not enough memory\n"); EXIT(); } GC_register_displacement_inner(0L); # ifdef MERGE_SIZES GC_init_size_map(); # endif # ifdef PCR if (PCR_IL_Lock(PCR_Bool_false, PCR_allSigsBlocked, PCR_waitForever) != PCR_ERes_okay) { ABORT("Can't lock load state\n"); } else if (PCR_IL_Unlock() != PCR_ERes_okay) { ABORT("Can't unlock load state\n"); } PCR_IL_Unlock(); GC_pcr_install(); # endif /* Get black list set up */ GC_gcollect_inner(); # ifdef STUBBORN_ALLOC GC_stubborn_init(); # endif GC_is_initialized = TRUE; /* Convince lint that some things are used */ # ifdef LINT { extern char * GC_copyright[]; extern int GC_read(); extern void GC_register_finalizer_no_order(); GC_noop(GC_copyright, GC_find_header, GC_push_one, GC_call_with_alloc_lock, GC_read, GC_dont_expand, # ifndef NO_DEBUGGING GC_dump, # endif GC_register_finalizer_no_order); } # endif } void GC_enable_incremental GC_PROTO(()) { # if !defined(FIND_LEAK) && !defined(SMALL_CONFIG) DCL_LOCK_STATE; DISABLE_SIGNALS(); LOCK(); if (GC_incremental) goto out; GC_setpagesize(); # ifdef MSWIN32 { extern GC_bool GC_is_win32s(); /* VirtualProtect is not functional under win32s. */ if (GC_is_win32s()) goto out; } # endif /* MSWIN32 */ # ifndef SOLARIS_THREADS GC_dirty_init(); # endif if (!GC_is_initialized) { GC_init_inner(); } if (GC_dont_gc) { /* Can't easily do it. */ UNLOCK(); ENABLE_SIGNALS(); return; } if (GC_words_allocd > 0) { /* There may be unmarked reachable objects */ GC_gcollect_inner(); } /* else we're OK in assuming everything's */ /* clean since nothing can point to an */ /* unmarked object. */ GC_read_dirty(); GC_incremental = TRUE; out: UNLOCK(); ENABLE_SIGNALS(); # endif } #ifdef MSWIN32 # define LOG_FILE "gc.log" HANDLE GC_stdout = 0, GC_stderr; int GC_tmp; DWORD GC_junk; void GC_set_files() { if (!GC_stdout) { GC_stdout = CreateFile(LOG_FILE, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_FLAG_WRITE_THROUGH, NULL); if (INVALID_HANDLE_VALUE == GC_stdout) ABORT("Open of log file failed"); } if (GC_stderr == 0) { GC_stderr = GC_stdout; } } #endif #if defined(OS2) || defined(MACOS) FILE * GC_stdout = NULL; FILE * GC_stderr = NULL; int GC_tmp; /* Should really be local ... */ void GC_set_files() { if (GC_stdout == NULL) { GC_stdout = stdout; } if (GC_stderr == NULL) { GC_stderr = stderr; } } #endif #if !defined(OS2) && !defined(MACOS) && !defined(MSWIN32) int GC_stdout = 1; int GC_stderr = 2; # if !defined(AMIGA) # include # endif #endif #if !defined(MSWIN32) && !defined(OS2) && !defined(MACOS) int GC_write(fd, buf, len) int fd; char *buf; size_t len; { register int bytes_written = 0; register int result; while (bytes_written < len) { # ifdef SOLARIS_THREADS result = syscall(SYS_write, fd, buf + bytes_written, len - bytes_written); # else result = write(fd, buf + bytes_written, len - bytes_written); # endif if (-1 == result) return(result); bytes_written += result; } return(bytes_written); } #endif /* UN*X */ #ifdef MSWIN32 # define WRITE(f, buf, len) (GC_set_files(), \ GC_tmp = WriteFile((f), (buf), \ (len), &GC_junk, NULL),\ (GC_tmp? 1 : -1)) #else # if defined(OS2) || defined(MACOS) # define WRITE(f, buf, len) (GC_set_files(), \ GC_tmp = fwrite((buf), 1, (len), (f)), \ fflush(f), GC_tmp) # else # define WRITE(f, buf, len) GC_write((f), (buf), (len)) # endif #endif /* A version of printf that is unlikely to call malloc, and is thus safer */ /* to call from the collector in case malloc has been bound to GC_malloc. */ /* Assumes that no more than 1023 characters are written at once. */ /* Assumes that all arguments have been converted to something of the */ /* same size as long, and that the format conversions expect something */ /* of that size. */ void GC_printf(format, a, b, c, d, e, f) char * format; long a, b, c, d, e, f; { char buf[1025]; if (GC_quiet) return; buf[1024] = 0x15; (void) sprintf(buf, format, a, b, c, d, e, f); if (buf[1024] != 0x15) ABORT("GC_printf clobbered stack"); if (WRITE(GC_stdout, buf, strlen(buf)) < 0) ABORT("write to stdout failed"); } void GC_err_printf(format, a, b, c, d, e, f) char * format; long a, b, c, d, e, f; { char buf[1025]; buf[1024] = 0x15; (void) sprintf(buf, format, a, b, c, d, e, f); if (buf[1024] != 0x15) ABORT("GC_err_printf clobbered stack"); if (WRITE(GC_stderr, buf, strlen(buf)) < 0) ABORT("write to stderr failed"); } void GC_err_puts(s) char *s; { if (WRITE(GC_stderr, s, strlen(s)) < 0) ABORT("write to stderr failed"); } # if defined(__STDC__) || defined(__cplusplus) void GC_default_warn_proc(char *msg, GC_word arg) # else void GC_default_warn_proc(msg, arg) char *msg; GC_word arg; # endif { GC_err_printf1(msg, (unsigned long)arg); } GC_warn_proc GC_current_warn_proc = GC_default_warn_proc; # if defined(__STDC__) || defined(__cplusplus) GC_warn_proc GC_set_warn_proc(GC_warn_proc p) # else GC_warn_proc GC_set_warn_proc(p) GC_warn_proc p; # endif { GC_warn_proc result; LOCK(); result = GC_current_warn_proc; GC_current_warn_proc = p; UNLOCK(); return(result); } #ifndef PCR void GC_abort(msg) char * msg; { GC_err_printf1("%s\n", msg); (void) abort(); } #endif #ifdef NEED_CALLINFO #if defined(MACOS) && defined(POWERPC) struct traceback_table { long zero; long magic; long reserved; long codeSize; short nameLength; char name[2]; }; typedef struct traceback_table traceback_table; static char* pc2name(word pc, char name[], long size) { name[0] = '\0'; // make sure pc is instruction aligned (at least). if (pc == (pc & 0xFFFFFFFC)) { long instructionsToLook = 4096; long* instruction = (long*)pc; // look for the traceback table. while (instructionsToLook--) { if (instruction[0] == 0x4E800020 && instruction[1] == 0x00000000) { traceback_table* tb = (traceback_table*)&instruction[1]; long nameLength = (tb->nameLength > --size ? size : tb->nameLength); memcpy(name, tb->name + 1, --nameLength); name[nameLength] = '\0'; break; } ++instruction; } } return name; } extern void MWUnmangle(const char *mangled_name, char *unmangled_name, size_t buffersize); extern int GC_address_to_source(char* codeAddr, char fileName[256], UInt32* fileOffset); void GC_print_callers(struct callinfo info[NFRAMES]) { register int i; UInt32 file_offset; static char name[1024], unmangled_name[1024], file_name[256]; GC_err_printf0("Callers at location:\n"); for (i = 0; i < NFRAMES; i++) { if (info[i].ci_pc == 0) break; pc2name(info[i].ci_pc, name, sizeof(name)); MWUnmangle(name, unmangled_name, sizeof(unmangled_name)); if (GC_address_to_source((char*)info[i].ci_pc, file_name, &file_offset)) GC_err_printf3("%s[%s,%ld]\n", unmangled_name, file_name, file_offset); else GC_err_printf2("%s(%08X)\n", unmangled_name, info[i].ci_pc); } } #else void GC_print_callers (info) struct callinfo info[NFRAMES]; { register int i; # if NFRAMES == 1 GC_err_printf0("\tCaller at allocation:\n"); # else GC_err_printf0("\tCall chain at allocation:\n"); # endif for (i = 0; i < NFRAMES; i++) { if (info[i].ci_pc == 0) break; # if NARGS > 0 { int j; GC_err_printf0("\t\targs: "); for (j = 0; j < NARGS; j++) { if (j != 0) GC_err_printf0(", "); GC_err_printf2("%d (0x%X)", ~(info[i].ci_arg[j]), ~(info[i].ci_arg[j])); } GC_err_printf0("\n"); } # endif GC_err_printf1("\t\t##PC##= 0x%X\n", info[i].ci_pc); } } #endif /* !MACOS */ #endif /* SAVE_CALL_CHAIN */ # ifdef SRC_M3 void GC_enable() { GC_dont_gc--; } void GC_disable() { GC_dont_gc++; } # endif #if !defined(NO_DEBUGGING) void GC_dump() { GC_printf0("***Static roots:\n"); GC_print_static_roots(); GC_printf0("\n***Heap sections:\n"); GC_print_heap_sects(); GC_printf0("\n***Free blocks:\n"); GC_print_hblkfreelist(); GC_printf0("\n***Blocks in use:\n"); GC_print_block_list(); } # endif /* NO_DEBUGGING */