1
Fork 0
mirror of https://git.savannah.gnu.org/git/guile.git synced 2025-06-06 12:10:28 +02:00

* gc.h: add scm_debug_cells_gc_interval to public interface

* gc-card.c ("sweep_card"): set scm_gc_running while sweeping.

* gc.c (scm_i_expensive_validation_check): separate expensive
validation checks from cheap ones.
This commit is contained in:
Han-Wen Nienhuys 2002-08-08 19:47:31 +00:00
parent 00706edc1d
commit eab1b25970
12 changed files with 179 additions and 105 deletions

6
NEWS
View file

@ -142,9 +142,9 @@ cause aborts in long running programs.
The new functions are more symmetrical and do not need cooperation The new functions are more symmetrical and do not need cooperation
from smob free routines, among other improvements. from smob free routines, among other improvements.
The new functions are scm_malloc, scm_realloc, scm_strdup, The new functions are scm_malloc, scm_realloc, scm_calloc, scm_strdup,
scm_strndup, scm_gc_malloc, scm_gc_calloc, scm_gc_realloc, scm_gc_free, scm_strndup, scm_gc_malloc, scm_gc_calloc, scm_gc_realloc,
scm_gc_register_collectable_memory, and scm_gc_free, scm_gc_register_collectable_memory, and
scm_gc_unregister_collectable_memory. Refer to the manual for more scm_gc_unregister_collectable_memory. Refer to the manual for more
details and for upgrading instructions. details and for upgrading instructions.

View file

@ -76,6 +76,7 @@ For really specialized needs, take at look at
@code{scm_gc_unregister_collectable_memory}. @code{scm_gc_unregister_collectable_memory}.
@deftypefn {C Function} void *scm_malloc (size_t @var{size}) @deftypefn {C Function} void *scm_malloc (size_t @var{size})
@deftypefnx {C Function} void *scm_calloc (size_t @var{size})
Allocate @var{size} bytes of memory and return a pointer to it. When Allocate @var{size} bytes of memory and return a pointer to it. When
@var{size} is 0, return @code{NULL}. When not enough memory is @var{size} is 0, return @code{NULL}. When not enough memory is
available, signal an error. This function runs the GC to free up some available, signal an error. This function runs the GC to free up some
@ -85,6 +86,9 @@ The memory is allocated by the libc @code{malloc} function and can be
freed with @code{free}. There is no @code{scm_free} function to go freed with @code{free}. There is no @code{scm_free} function to go
with @code{scm_malloc} to make it easier to pass memory back and forth with @code{scm_malloc} to make it easier to pass memory back and forth
between different modules. between different modules.
The function @code{scm_calloc} is similar to @code{scm_malloc}, but
initializes the block of memory to zero as well.
@end deftypefn @end deftypefn
@deftypefn {C Function} void *scm_realloc (void *@var{mem}, size_t @var{new_size}) @deftypefn {C Function} void *scm_realloc (void *@var{mem}, size_t @var{new_size})
@ -98,6 +102,9 @@ When not enough memory is available, signal an error. This function
runs the GC to free up some memory when it deems it appropriate. runs the GC to free up some memory when it deems it appropriate.
@end deftypefn @end deftypefn
@deftypefn {C Function} void scm_gc_register_collectable_memory (void *@var{mem}, size_t @var{size}, const char *@var{what}) @deftypefn {C Function} void scm_gc_register_collectable_memory (void *@var{mem}, size_t @var{size}, const char *@var{what})
Informs the GC that the memory at @var{mem} of size @var{size} can Informs the GC that the memory at @var{mem} of size @var{size} can
potentially be freed during a GC. That is, announce that @var{mem} is potentially be freed during a GC. That is, announce that @var{mem} is
@ -127,12 +134,14 @@ much less efficiently than it could.
@deftypefn {C Function} void *scm_gc_malloc (size_t @var{size}, const char *@var{what}) @deftypefn {C Function} void *scm_gc_malloc (size_t @var{size}, const char *@var{what})
@deftypefnx {C Function} void *scm_gc_realloc (void *@var{mem}, size_t @var{old_size}, size_t @var{new_size}, const char *@var{what}); @deftypefnx {C Function} void *scm_gc_realloc (void *@var{mem}, size_t @var{old_size}, size_t @var{new_size}, const char *@var{what});
Like @code{scm_malloc} or @code{scm_realloc}, but also call @deftypefnx {C Function} void *scm_gc_calloc (size_t @var{size}, const char *@var{what})
@code{scm_gc_register_collectable_memory}. Note that you need to pass Like @code{scm_malloc}, @code{scm_realloc} or @code{scm_calloc}, but
the old size of a reallocated memory block as well. See below for a also call @code{scm_gc_register_collectable_memory}. Note that you
motivation. need to pass the old size of a reallocated memory block as well. See
below for a motivation.
@end deftypefn @end deftypefn
@deftypefn {C Function} void scm_gc_free (void *@var{mem}, size_t @var{size}, const char *@var{what}) @deftypefn {C Function} void scm_gc_free (void *@var{mem}, size_t @var{size}, const char *@var{what})
Like @code{free}, but also call @code{scm_gc_unregister_collectable_memory}. Like @code{free}, but also call @code{scm_gc_unregister_collectable_memory}.

View file

@ -1,3 +1,12 @@
2002-08-08 Han-Wen Nienhuys <hanwen@cs.uu.nl>
* gc.h: add scm_debug_cells_gc_interval to public interface
* gc-card.c ("sweep_card"): set scm_gc_running while sweeping.
* gc.c (scm_i_expensive_validation_check): separate expensive
validation checks from cheap ones.
2002-08-06 Han-Wen Nienhuys <hanwen@cs.uu.nl> 2002-08-06 Han-Wen Nienhuys <hanwen@cs.uu.nl>
* read.c (scm_input_error): new function: give meaningful error * read.c (scm_input_error): new function: give meaningful error

View file

@ -86,12 +86,13 @@ scm_i_sweep_card (scm_t_cell * p, SCM *free_list, int span)
int offset =SCM_MAX (SCM_GC_CARD_N_HEADER_CELLS, span); int offset =SCM_MAX (SCM_GC_CARD_N_HEADER_CELLS, span);
int free_count = 0; int free_count = 0;
++ scm_gc_running_p;
/* /*
I tried something fancy with shifting by one bit every word from I tried something fancy with shifting by one bit every word from
the bitvec in turn, but it wasn't any faster, but quite bit the bitvec in turn, but it wasn't any faster, but quite bit
hairier. hairier.
*/ */
for (p += offset; p < end; p += span, offset += span) for (p += offset; p < end; p += span, offset += span)
{ {
SCM scmptr = PTR2SCM(p); SCM scmptr = PTR2SCM(p);
@ -273,6 +274,8 @@ scm_i_sweep_card (scm_t_cell * p, SCM *free_list, int span)
*free_list = PTR2SCM (p); *free_list = PTR2SCM (p);
free_count ++; free_count ++;
} }
--scm_gc_running_p;
return free_count; return free_count;
} }
#undef FUNC_NAME #undef FUNC_NAME
@ -301,6 +304,7 @@ scm_init_card_freelist (scm_t_cell * card, SCM *free_list, int span)
} }
#if 0 #if 0
/* /*
These functions are meant to be called from GDB as a debug aid. These functions are meant to be called from GDB as a debug aid.
@ -318,6 +322,16 @@ typedef struct scm_t_list_cell_struct {
struct scm_t_list_cell_struct * cdr; struct scm_t_list_cell_struct * cdr;
} scm_t_list_cell; } scm_t_list_cell;
typedef struct scm_t_double_cell
{
scm_t_bits word_0;
scm_t_bits word_1;
scm_t_bits word_2;
scm_t_bits word_3;
} scm_t_double_cell;
int int
scm_gc_marked_p (SCM obj) scm_gc_marked_p (SCM obj)
{ {

View file

@ -83,9 +83,6 @@ extern unsigned long * __libc_ia64_register_backing_store_base;
#include <unistd.h> #include <unistd.h>
#endif #endif
#ifdef __ia64__ #ifdef __ia64__
# define SCM_MARK_BACKING_STORE() do { \ # define SCM_MARK_BACKING_STORE() do { \
ucontext_t ctx; \ ucontext_t ctx; \
@ -101,6 +98,7 @@ extern unsigned long * __libc_ia64_register_backing_store_base;
# define SCM_MARK_BACKING_STORE() # define SCM_MARK_BACKING_STORE()
#endif #endif
/* /*
Entry point for this file. Entry point for this file.
*/ */
@ -108,10 +106,10 @@ void
scm_mark_all (void) scm_mark_all (void)
{ {
long j; long j;
scm_i_clear_mark_space (); scm_i_clear_mark_space ();
#ifndef USE_THREADS #ifndef USE_THREADS
/* Mark objects on the C stack. */ /* Mark objects on the C stack. */
@ -157,12 +155,14 @@ scm_mark_all (void)
} }
} }
} }
/* FIXME: we should have a means to register C functions to be run /* FIXME: we should have a means to register C functions to be run
* in different phases of GC * in different phases of GC
*/ */
scm_mark_subr_table (); scm_mark_subr_table ();
#ifndef USE_THREADS #ifndef USE_THREADS
scm_gc_mark (scm_root->handle); scm_gc_mark (scm_root->handle);
#endif #endif
@ -171,7 +171,6 @@ scm_mark_all (void)
/* {Mark/Sweep} /* {Mark/Sweep}
*/ */
/* /*
Mark an object precisely, then recurse. Mark an object precisely, then recurse.
*/ */
@ -182,7 +181,9 @@ scm_gc_mark (SCM ptr)
return ; return ;
if (SCM_GC_MARK_P (ptr)) if (SCM_GC_MARK_P (ptr))
return; {
return;
}
SCM_SET_GC_MARK (ptr); SCM_SET_GC_MARK (ptr);
scm_gc_mark_dependencies (ptr); scm_gc_mark_dependencies (ptr);
@ -475,9 +476,12 @@ gc_mark_loop:
} }
if (SCM_GC_MARK_P (ptr)) if (SCM_GC_MARK_P (ptr))
{
return; return;
}
SCM_SET_GC_MARK (ptr); SCM_SET_GC_MARK (ptr);
goto scm_mark_dependencies_again; goto scm_mark_dependencies_again;
} }
@ -485,6 +489,7 @@ gc_mark_loop:
/* Mark a region conservatively */ /* Mark a region conservatively */
void void
scm_mark_locations (SCM_STACKITEM x[], unsigned long n) scm_mark_locations (SCM_STACKITEM x[], unsigned long n)
@ -570,4 +575,3 @@ scm_gc_init_mark(void)
#endif #endif
} }

View file

@ -546,6 +546,7 @@ scm_i_get_new_heap_segment (scm_t_cell_type_statistics *freelist, policy_on_erro
} }
void void
scm_i_make_initial_segment (size_t init_heap_size, scm_t_cell_type_statistics *freelist) scm_i_make_initial_segment (size_t init_heap_size, scm_t_cell_type_statistics *freelist)
{ {
@ -568,4 +569,3 @@ scm_i_make_initial_segment (size_t init_heap_size, scm_t_cell_type_statistics *f
freelist->min_yield = (freelist->heap_size * freelist->min_yield_fraction freelist->min_yield = (freelist->heap_size * freelist->min_yield_fraction
/ 100); / 100);
} }

View file

@ -90,53 +90,87 @@ extern unsigned long * __libc_ia64_register_backing_store_base;
unsigned int scm_gc_running_p = 0; unsigned int scm_gc_running_p = 0;
#if (SCM_DEBUG_CELL_ACCESSES == 1)
/* Set this to != 0 if every cell that is accessed shall be checked: /* Set this to != 0 if every cell that is accessed shall be checked:
*/ */
unsigned int scm_debug_cell_accesses_p = 1; int scm_debug_cell_accesses_p = 0;
int scm_expensive_debug_cell_accesses_p = 0;
/* Set this to 0 if no additional gc's shall be performed, otherwise set it to /* Set this to 0 if no additional gc's shall be performed, otherwise set it to
* the number of cell accesses after which a gc shall be called. * the number of cell accesses after which a gc shall be called.
*/ */
static unsigned int debug_cells_gc_interval = 0; int scm_debug_cells_gc_interval = 0;
/*
/* Assert that the given object is a valid reference to a valid cell. This Global variable, so you can switch it off at runtime by setting
* test involves to determine whether the object is a cell pointer, whether scm_i_cell_validation_already_running.
* this pointer actually points into a heap segment and whether the cell
* pointed to is not a free cell. Further, additional garbage collections may
* get executed after a user defined number of cell accesses. This helps to
* find places in the C code where references are dropped for extremely short
* periods.
*/ */
int scm_i_cell_validation_already_running ;
#if (SCM_DEBUG_CELL_ACCESSES == 1)
/*
Assert that the given object is a valid reference to a valid cell. This
test involves to determine whether the object is a cell pointer, whether
this pointer actually points into a heap segment and whether the cell
pointed to is not a free cell. Further, additional garbage collections may
get executed after a user defined number of cell accesses. This helps to
find places in the C code where references are dropped for extremely short
periods.
*/
void
scm_i_expensive_validation_check (SCM cell)
{
if (!scm_in_heap_p (cell))
{
fprintf (stderr, "scm_assert_cell_valid: this object does not live in the heap: %lux\n",
(unsigned long) SCM_UNPACK (cell));
abort ();
}
/* If desired, perform additional garbage collections after a user
* defined number of cell accesses.
*/
if (scm_debug_cells_gc_interval)
{
static unsigned int counter = 0;
if (counter != 0)
{
--counter;
}
else
{
counter = scm_debug_cells_gc_interval;
scm_igc ("scm_assert_cell_valid");
}
}
}
void void
scm_assert_cell_valid (SCM cell) scm_assert_cell_valid (SCM cell)
{ {
static unsigned int already_running = 0; if (!scm_i_cell_validation_already_running && scm_debug_cell_accesses_p)
if (!already_running)
{ {
already_running = 1; /* set to avoid recursion */ scm_i_cell_validation_already_running = 1; /* set to avoid recursion */
/* /*
During GC, no user-code should be run, and the guile core should During GC, no user-code should be run, and the guile core
use non-protected accessors. should use non-protected accessors.
*/ */
if (scm_gc_running_p) if (scm_gc_running_p)
abort(); return;
/* /*
Only scm_in_heap_p is wildly expensive. Only scm_in_heap_p and rescanning the heap is wildly
*/ expensive.
if (scm_debug_cell_accesses_p) */
if (!scm_in_heap_p (cell)) if (scm_expensive_debug_cell_accesses_p)
{ scm_i_expensive_validation_check (cell);
fprintf (stderr, "scm_assert_cell_valid: this object does not live in the heap: %lux\n",
(unsigned long) SCM_UNPACK (cell));
abort ();
}
if (!SCM_GC_MARK_P (cell)) if (!SCM_GC_MARK_P (cell))
{ {
@ -148,54 +182,47 @@ scm_assert_cell_valid (SCM cell)
abort (); abort ();
} }
scm_i_cell_validation_already_running = 0; /* re-enable */
/* If desired, perform additional garbage collections after a user
* defined number of cell accesses.
*/
if (scm_debug_cell_accesses_p && debug_cells_gc_interval)
{
static unsigned int counter = 0;
if (counter != 0)
{
--counter;
}
else
{
counter = debug_cells_gc_interval;
scm_igc ("scm_assert_cell_valid");
}
}
already_running = 0; /* re-enable */
} }
} }
SCM_DEFINE (scm_set_debug_cell_accesses_x, "set-debug-cell-accesses!", 1, 0, 0, SCM_DEFINE (scm_set_debug_cell_accesses_x, "set-debug-cell-accesses!", 1, 0, 0,
(SCM flag), (SCM flag),
"If @var{flag} is @code{#f}, cell access checking is disabled.\n" "If @var{flag} is @code{#f}, cell access checking is disabled.\n"
"If @var{flag} is @code{#t}, cell access checking is enabled,\n" "If @var{flag} is @code{#t}, cheap cell access checking is enabled,\n"
"but no additional calls to garbage collection are issued.\n" "but no additional calls to garbage collection are issued.\n"
"If @var{flag} is a number, cell access checking is enabled,\n" "If @var{flag} is a number, strict cell access checking is enabled,\n"
"with an additional garbage collection after the given\n" "with an additional garbage collection after the given\n"
"number of cell accesses.\n" "number of cell accesses.\n"
"This procedure only exists when the compile-time flag\n" "This procedure only exists when the compile-time flag\n"
"@code{SCM_DEBUG_CELL_ACCESSES} was set to 1.") "@code{SCM_DEBUG_CELL_ACCESSES} was set to 1.")
#define FUNC_NAME s_scm_set_debug_cell_accesses_x #define FUNC_NAME s_scm_set_debug_cell_accesses_x
{ {
if (SCM_FALSEP (flag)) { if (SCM_FALSEP (flag))
scm_debug_cell_accesses_p = 0; {
} else if (SCM_EQ_P (flag, SCM_BOOL_T)) { scm_debug_cell_accesses_p = 0;
debug_cells_gc_interval = 0; }
scm_debug_cell_accesses_p = 1; else if (SCM_EQ_P (flag, SCM_BOOL_T))
} else if (SCM_INUMP (flag)) { {
long int f = SCM_INUM (flag); scm_debug_cells_gc_interval = 0;
if (f <= 0) SCM_OUT_OF_RANGE (1, flag); scm_debug_cell_accesses_p = 1;
debug_cells_gc_interval = f; scm_expensive_debug_cell_accesses_p = 0;
scm_debug_cell_accesses_p = 1; }
} else { else if (SCM_INUMP (flag))
SCM_WRONG_TYPE_ARG (1, flag); {
} long int f = SCM_INUM (flag);
if (f <= 0)
SCM_OUT_OF_RANGE (1, flag);
scm_debug_cells_gc_interval = f;
scm_debug_cell_accesses_p = 1;
scm_expensive_debug_cell_accesses_p = 1;
}
else
{
SCM_WRONG_TYPE_ARG (1, flag);
}
return SCM_UNSPECIFIED; return SCM_UNSPECIFIED;
} }
#undef FUNC_NAME #undef FUNC_NAME
@ -497,6 +524,8 @@ scm_gc_for_newcell (scm_t_cell_type_statistics *freelist, SCM *free_cells)
--scm_ints_disabled; --scm_ints_disabled;
*free_cells = SCM_FREE_CELL_CDR (cell); *free_cells = SCM_FREE_CELL_CDR (cell);
return cell; return cell;
} }
@ -525,7 +554,7 @@ scm_igc (const char *what)
/* During the critical section, only the current thread may run. */ /* During the critical section, only the current thread may run. */
SCM_CRITICAL_SECTION_START; SCM_CRITICAL_SECTION_START;
if (!scm_stack_base || scm_block_gc) if (!scm_root || !scm_stack_base || scm_block_gc)
{ {
--scm_gc_running_p; --scm_gc_running_p;
return; return;
@ -585,17 +614,15 @@ scm_igc (const char *what)
SCM_CRITICAL_SECTION_END; SCM_CRITICAL_SECTION_END;
scm_c_hook_run (&scm_after_gc_c_hook, 0); scm_c_hook_run (&scm_after_gc_c_hook, 0);
--scm_gc_running_p; --scm_gc_running_p;
/*
For debugging purposes, you could do
scm_i_sweep_all_segments("debug"), but then the remains of the
cell aren't left to analyse.
*/
} }
/* {GC Protection Helper Functions} /* {GC Protection Helper Functions}
*/ */
@ -939,7 +966,7 @@ mark_gc_async (void * hook_data SCM_UNUSED,
* after-gc-hook. * after-gc-hook.
*/ */
#if (SCM_DEBUG_CELL_ACCESSES == 1) #if (SCM_DEBUG_CELL_ACCESSES == 1)
if (debug_cells_gc_interval == 0) if (scm_debug_cells_gc_interval == 0)
scm_system_async_mark (gc_async); scm_system_async_mark (gc_async);
#else #else
scm_system_async_mark (gc_async); scm_system_async_mark (gc_async);

View file

@ -218,6 +218,7 @@ typedef unsigned long scm_t_c_bvec_long;
#define SCM_SET_CELL_TYPE(x, t) SCM_SET_CELL_WORD_0 (x, t) #define SCM_SET_CELL_TYPE(x, t) SCM_SET_CELL_WORD_0 (x, t)
/* Freelists consist of linked cells where the type entry holds the value /* Freelists consist of linked cells where the type entry holds the value
* scm_tc_free_cell and the second entry holds a pointer to the next cell of * scm_tc_free_cell and the second entry holds a pointer to the next cell of
* the freelist. Due to this structure, freelist cells are not cons cells * the freelist. Due to this structure, freelist cells are not cons cells
@ -245,7 +246,11 @@ typedef unsigned long scm_t_c_bvec_long;
#if (SCM_DEBUG_CELL_ACCESSES == 1) #if (SCM_DEBUG_CELL_ACCESSES == 1)
SCM_API unsigned int scm_debug_cell_accesses_p; /* Set this to != 0 if every cell that is accessed shall be checked:
*/
SCM_API int scm_debug_cell_accesses_p;
SCM_API int scm_expensive_debug_cell_accesses_p;
SCM_API int scm_debug_cells_gc_interval ;
#endif #endif
SCM_API int scm_block_gc; SCM_API int scm_block_gc;
@ -274,10 +279,11 @@ SCM_API size_t scm_max_segment_size;
Deprecated scm_freelist, scm_master_freelist. Deprecated scm_freelist, scm_master_freelist.
No warning; this is not a user serviceable part. No warning; this is not a user serviceable part.
*/ */
SCM_API SCM scm_i_freelist; extern SCM scm_i_freelist;
SCM_API struct scm_t_cell_type_statistics scm_i_master_freelist; extern struct scm_t_cell_type_statistics scm_i_master_freelist;
SCM_API SCM scm_i_freelist2; extern SCM scm_i_freelist2;
SCM_API struct scm_t_cell_type_statistics scm_i_master_freelist2; extern struct scm_t_cell_type_statistics scm_i_master_freelist2;
SCM_API unsigned long scm_gc_cells_swept; SCM_API unsigned long scm_gc_cells_swept;
SCM_API unsigned long scm_gc_cells_collected; SCM_API unsigned long scm_gc_cells_collected;

View file

@ -39,10 +39,13 @@
* whether to permit this exception to apply to your modifications. * whether to permit this exception to apply to your modifications.
* If you do not wish that, delete this exception notice. */ * If you do not wish that, delete this exception notice. */
#include <stdio.h>
#include "libguile/scmconfig.h" #include "libguile/scmconfig.h"
#ifndef HAVE_INLINE
#define HAVE_INLINE #define HAVE_INLINE
#endif
#define EXTERN_INLINE #define EXTERN_INLINE
#undef SCM_INLINE_H #undef SCM_INLINE_H

View file

@ -50,10 +50,6 @@
*/ */
#if (SCM_DEBUG_CELL_ACCESSES == 1)
#include <stdio.h>
#endif
#include "libguile/pairs.h" #include "libguile/pairs.h"
#include "libguile/gc.h" #include "libguile/gc.h"
@ -64,8 +60,6 @@ SCM_API SCM scm_double_cell (scm_t_bits car, scm_t_bits cbr,
#ifdef HAVE_INLINE #ifdef HAVE_INLINE
#ifndef EXTERN_INLINE #ifndef EXTERN_INLINE
#define EXTERN_INLINE extern inline #define EXTERN_INLINE extern inline
#endif #endif
@ -74,6 +68,7 @@ extern unsigned scm_newcell2_count;
extern unsigned scm_newcell_count; extern unsigned scm_newcell_count;
EXTERN_INLINE EXTERN_INLINE
SCM SCM
scm_cell (scm_t_bits car, scm_t_bits cdr) scm_cell (scm_t_bits car, scm_t_bits cdr)
@ -137,6 +132,10 @@ scm_cell (scm_t_bits car, scm_t_bits cdr)
#endif #endif
#if (SCM_DEBUG_CELL_ACCESSES == 1)
if (scm_expensive_debug_cell_accesses_p )
scm_i_expensive_validation_check (z);
#endif
return z; return z;
} }
@ -201,5 +200,7 @@ scm_double_cell (scm_t_bits car, scm_t_bits cbr,
return z; return z;
} }
#endif #endif
#endif #endif

View file

@ -236,5 +236,6 @@ void scm_gc_init_malloc (void);
void scm_gc_init_freelist (void); void scm_gc_init_freelist (void);
void scm_gc_init_segments (void); void scm_gc_init_segments (void);
void scm_gc_init_mark (void); void scm_gc_init_mark (void);
#endif #endif

View file

@ -139,7 +139,7 @@ scm_mark_subr_table ()
long i; long i;
for (i = 0; i < scm_subr_table_size; ++i) for (i = 0; i < scm_subr_table_size; ++i)
{ {
SCM_SET_GC_MARK (scm_subr_table[i].name); scm_gc_mark (scm_subr_table[i].name);
if (scm_subr_table[i].generic && *scm_subr_table[i].generic) if (scm_subr_table[i].generic && *scm_subr_table[i].generic)
scm_gc_mark (*scm_subr_table[i].generic); scm_gc_mark (*scm_subr_table[i].generic);
if (SCM_NIMP (scm_subr_table[i].properties)) if (SCM_NIMP (scm_subr_table[i].properties))