1995-08-09 08:06:35 -07:00
|
|
|
/***********************************************************************/
|
|
|
|
/* */
|
1996-04-30 07:53:58 -07:00
|
|
|
/* Objective Caml */
|
1995-08-09 08:06:35 -07:00
|
|
|
/* */
|
|
|
|
/* Xavier Leroy, projet Cristal, INRIA Rocquencourt */
|
|
|
|
/* */
|
1996-04-30 07:53:58 -07:00
|
|
|
/* Copyright 1996 Institut National de Recherche en Informatique et */
|
1999-11-17 10:59:06 -08:00
|
|
|
/* en Automatique. All rights reserved. This file is distributed */
|
2001-12-07 05:41:02 -08:00
|
|
|
/* under the terms of the GNU Library General Public License, with */
|
|
|
|
/* the special exception on linking described in file ../LICENSE. */
|
1995-08-09 08:06:35 -07:00
|
|
|
/* */
|
|
|
|
/***********************************************************************/
|
|
|
|
|
|
|
|
/* $Id$ */
|
|
|
|
|
1995-07-10 02:48:27 -07:00
|
|
|
/* To walk the memory roots for garbage collection */
|
|
|
|
|
2000-03-10 12:30:38 -08:00
|
|
|
#include "finalise.h"
|
2001-08-11 10:36:38 -07:00
|
|
|
#include "globroots.h"
|
1995-07-10 02:48:27 -07:00
|
|
|
#include "memory.h"
|
|
|
|
#include "major_gc.h"
|
|
|
|
#include "minor_gc.h"
|
|
|
|
#include "misc.h"
|
|
|
|
#include "mlvalues.h"
|
1995-12-20 02:40:34 -08:00
|
|
|
#include "stack.h"
|
1997-11-21 05:46:23 -08:00
|
|
|
#include "roots.h"
|
2007-11-06 07:16:56 -08:00
|
|
|
#include <string.h>
|
|
|
|
#include <stdio.h>
|
1995-11-26 06:38:29 -08:00
|
|
|
|
1995-07-10 02:48:27 -07:00
|
|
|
/* Roots registered from C functions */
|
|
|
|
|
2004-01-01 08:42:43 -08:00
|
|
|
struct caml__roots_block *caml_local_roots = NULL;
|
1995-07-10 02:48:27 -07:00
|
|
|
|
2004-01-01 08:42:43 -08:00
|
|
|
void (*caml_scan_roots_hook) (scanning_action) = NULL;
|
1995-10-30 02:20:08 -08:00
|
|
|
|
1995-07-10 02:48:27 -07:00
|
|
|
/* The hashtable of frame descriptors */
|
|
|
|
|
2007-01-29 04:11:18 -08:00
|
|
|
frame_descr ** caml_frame_descriptors = NULL;
|
|
|
|
int caml_frame_descriptors_mask;
|
1995-07-10 02:48:27 -07:00
|
|
|
|
2007-11-06 07:16:56 -08:00
|
|
|
/* Linked-list */
|
|
|
|
|
|
|
|
typedef struct link {
|
|
|
|
void *data;
|
|
|
|
struct link *next;
|
2008-01-11 08:13:18 -08:00
|
|
|
} link;
|
2007-11-06 07:16:56 -08:00
|
|
|
|
|
|
|
static link *cons(void *data, link *tl) {
|
|
|
|
link *lnk = caml_stat_alloc(sizeof(link));
|
|
|
|
lnk->data = data;
|
|
|
|
lnk->next = tl;
|
|
|
|
return lnk;
|
|
|
|
}
|
|
|
|
|
|
|
|
#define iter_list(list,lnk) \
|
|
|
|
for (lnk = list; lnk != NULL; lnk = lnk->next)
|
|
|
|
|
|
|
|
/* Linked-list of frametables */
|
|
|
|
|
|
|
|
static link *frametables = NULL;
|
|
|
|
|
|
|
|
void caml_register_frametable(intnat *table) {
|
|
|
|
frametables = cons(table,frametables);
|
|
|
|
|
|
|
|
if (NULL != caml_frame_descriptors) {
|
|
|
|
caml_stat_free(caml_frame_descriptors);
|
|
|
|
caml_frame_descriptors = NULL;
|
|
|
|
/* force caml_init_frame_descriptors to be called */
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-01-29 04:11:18 -08:00
|
|
|
void caml_init_frame_descriptors(void)
|
1995-07-10 02:48:27 -07:00
|
|
|
{
|
2005-09-22 07:21:50 -07:00
|
|
|
intnat num_descr, tblsize, i, j, len;
|
|
|
|
intnat * tbl;
|
1995-07-10 02:48:27 -07:00
|
|
|
frame_descr * d;
|
2007-01-29 04:11:18 -08:00
|
|
|
uintnat nextd;
|
2005-09-22 07:21:50 -07:00
|
|
|
uintnat h;
|
2007-11-06 07:16:56 -08:00
|
|
|
link *lnk;
|
1995-07-10 02:48:27 -07:00
|
|
|
|
2007-11-06 07:16:56 -08:00
|
|
|
static int inited = 0;
|
2008-01-11 08:13:18 -08:00
|
|
|
|
2007-11-06 07:16:56 -08:00
|
|
|
if (!inited) {
|
|
|
|
for (i = 0; caml_frametable[i] != 0; i++)
|
|
|
|
caml_register_frametable(caml_frametable[i]);
|
|
|
|
inited = 1;
|
|
|
|
}
|
2008-01-11 08:13:18 -08:00
|
|
|
|
1995-07-10 02:48:27 -07:00
|
|
|
/* Count the frame descriptors */
|
|
|
|
num_descr = 0;
|
2007-11-06 07:16:56 -08:00
|
|
|
iter_list(frametables,lnk) {
|
|
|
|
num_descr += *((intnat*) lnk->data);
|
|
|
|
}
|
1995-07-10 02:48:27 -07:00
|
|
|
|
|
|
|
/* The size of the hashtable is a power of 2 greater or equal to
|
|
|
|
2 times the number of descriptors */
|
|
|
|
tblsize = 4;
|
|
|
|
while (tblsize < 2 * num_descr) tblsize *= 2;
|
|
|
|
|
|
|
|
/* Allocate the hash table */
|
2007-01-29 04:11:18 -08:00
|
|
|
caml_frame_descriptors =
|
2003-12-31 06:20:40 -08:00
|
|
|
(frame_descr **) caml_stat_alloc(tblsize * sizeof(frame_descr *));
|
2007-01-29 04:11:18 -08:00
|
|
|
for (i = 0; i < tblsize; i++) caml_frame_descriptors[i] = NULL;
|
|
|
|
caml_frame_descriptors_mask = tblsize - 1;
|
1995-07-10 02:48:27 -07:00
|
|
|
|
|
|
|
/* Fill the hash table */
|
2007-11-06 07:16:56 -08:00
|
|
|
iter_list(frametables,lnk) {
|
|
|
|
tbl = (intnat*) lnk->data;
|
1995-07-10 02:48:27 -07:00
|
|
|
len = *tbl;
|
|
|
|
d = (frame_descr *)(tbl + 1);
|
|
|
|
for (j = 0; j < len; j++) {
|
|
|
|
h = Hash_retaddr(d->retaddr);
|
2007-01-29 04:11:18 -08:00
|
|
|
while (caml_frame_descriptors[h] != NULL) {
|
2008-01-11 08:13:18 -08:00
|
|
|
h = (h+1) & caml_frame_descriptors_mask;
|
1995-07-10 02:48:27 -07:00
|
|
|
}
|
2007-01-29 04:11:18 -08:00
|
|
|
caml_frame_descriptors[h] = d;
|
|
|
|
nextd =
|
2008-01-11 08:13:18 -08:00
|
|
|
((uintnat)d +
|
|
|
|
sizeof(char *) + sizeof(short) + sizeof(short) +
|
|
|
|
sizeof(short) * d->num_live + sizeof(frame_descr *) - 1)
|
|
|
|
& -sizeof(frame_descr *);
|
2007-01-29 04:11:18 -08:00
|
|
|
if (d->frame_size & 1) nextd += 8;
|
|
|
|
d = (frame_descr *) nextd;
|
1995-07-10 02:48:27 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Communication with [caml_start_program] and [caml_call_gc]. */
|
|
|
|
|
1998-07-29 04:52:59 -07:00
|
|
|
char * caml_bottom_of_stack = NULL; /* no stack initially */
|
2005-09-22 07:21:50 -07:00
|
|
|
uintnat caml_last_return_address = 1; /* not in Caml code initially */
|
1997-11-27 08:28:40 -08:00
|
|
|
value * caml_gc_regs;
|
2005-09-22 07:21:50 -07:00
|
|
|
intnat caml_globals_inited = 0;
|
|
|
|
static intnat caml_globals_scanned = 0;
|
2007-11-06 07:16:56 -08:00
|
|
|
static link * caml_dyn_globals = NULL;
|
|
|
|
|
|
|
|
void caml_register_dyn_global(void *v) {
|
|
|
|
caml_dyn_globals = cons((void*) v,caml_dyn_globals);
|
|
|
|
}
|
1995-12-19 07:09:33 -08:00
|
|
|
|
2003-12-31 06:20:40 -08:00
|
|
|
/* Call [caml_oldify_one] on (at least) all the roots that point to the minor
|
2002-01-18 07:13:26 -08:00
|
|
|
heap. */
|
2004-01-01 08:42:43 -08:00
|
|
|
void caml_oldify_local_roots (void)
|
1995-07-10 02:48:27 -07:00
|
|
|
{
|
|
|
|
char * sp;
|
2005-09-22 07:21:50 -07:00
|
|
|
uintnat retaddr;
|
1997-11-21 05:46:23 -08:00
|
|
|
value * regs;
|
1995-07-10 02:48:27 -07:00
|
|
|
frame_descr * d;
|
2005-09-22 07:21:50 -07:00
|
|
|
uintnat h;
|
1995-07-17 09:10:45 -07:00
|
|
|
int i, j, n, ofs;
|
2008-01-11 08:13:18 -08:00
|
|
|
#ifdef Stack_grows_upwards
|
|
|
|
short * p; /* PR#4339: stack offsets are negative in this case */
|
|
|
|
#else
|
2007-01-29 04:11:18 -08:00
|
|
|
unsigned short * p;
|
2008-01-11 08:13:18 -08:00
|
|
|
#endif
|
1995-07-17 09:10:45 -07:00
|
|
|
value glob;
|
1997-06-02 05:51:25 -07:00
|
|
|
value * root;
|
1997-05-26 10:16:31 -07:00
|
|
|
struct caml__roots_block *lr;
|
2007-11-06 07:16:56 -08:00
|
|
|
link *lnk;
|
1995-07-10 02:48:27 -07:00
|
|
|
|
|
|
|
/* The global roots */
|
2000-01-05 05:16:31 -08:00
|
|
|
for (i = caml_globals_scanned;
|
|
|
|
i <= caml_globals_inited && caml_globals[i] != 0;
|
|
|
|
i++) {
|
1995-07-17 09:10:45 -07:00
|
|
|
glob = caml_globals[i];
|
2000-01-05 05:16:31 -08:00
|
|
|
for (j = 0; j < Wosize_val(glob); j++){
|
2002-01-20 14:20:58 -08:00
|
|
|
Oldify (&Field (glob, j));
|
2000-01-05 05:16:31 -08:00
|
|
|
}
|
1995-07-17 09:10:45 -07:00
|
|
|
}
|
2000-01-05 05:16:31 -08:00
|
|
|
caml_globals_scanned = caml_globals_inited;
|
1995-07-10 02:48:27 -07:00
|
|
|
|
2007-11-06 07:16:56 -08:00
|
|
|
/* Dynamic global roots */
|
|
|
|
iter_list(caml_dyn_globals, lnk) {
|
|
|
|
glob = (value) lnk->data;
|
|
|
|
for (j = 0; j < Wosize_val(glob); j++){
|
|
|
|
Oldify (&Field (glob, j));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
1997-08-29 08:37:22 -07:00
|
|
|
/* The stack and local roots */
|
2007-01-29 04:11:18 -08:00
|
|
|
if (caml_frame_descriptors == NULL) caml_init_frame_descriptors();
|
1997-11-27 08:28:40 -08:00
|
|
|
sp = caml_bottom_of_stack;
|
|
|
|
retaddr = caml_last_return_address;
|
|
|
|
regs = caml_gc_regs;
|
1998-07-29 04:52:59 -07:00
|
|
|
if (sp != NULL) {
|
|
|
|
while (1) {
|
|
|
|
/* Find the descriptor corresponding to the return address */
|
|
|
|
h = Hash_retaddr(retaddr);
|
|
|
|
while(1) {
|
2007-01-29 04:11:18 -08:00
|
|
|
d = caml_frame_descriptors[h];
|
1998-07-29 04:52:59 -07:00
|
|
|
if (d->retaddr == retaddr) break;
|
2007-01-29 04:11:18 -08:00
|
|
|
h = (h+1) & caml_frame_descriptors_mask;
|
1995-07-10 02:48:27 -07:00
|
|
|
}
|
2007-01-29 04:11:18 -08:00
|
|
|
if (d->frame_size != 0xFFFF) {
|
1998-07-29 04:52:59 -07:00
|
|
|
/* Scan the roots in this frame */
|
|
|
|
for (p = d->live_ofs, n = d->num_live; n > 0; n--, p++) {
|
|
|
|
ofs = *p;
|
|
|
|
if (ofs & 1) {
|
|
|
|
root = regs + (ofs >> 1);
|
|
|
|
} else {
|
|
|
|
root = (value *)(sp + ofs);
|
|
|
|
}
|
2002-01-20 14:20:58 -08:00
|
|
|
Oldify (root);
|
1998-07-29 04:52:59 -07:00
|
|
|
}
|
|
|
|
/* Move to next frame */
|
1995-11-26 06:38:29 -08:00
|
|
|
#ifndef Stack_grows_upwards
|
2007-01-29 04:11:18 -08:00
|
|
|
sp += (d->frame_size & 0xFFFC);
|
1995-11-26 06:38:29 -08:00
|
|
|
#else
|
2007-01-29 04:11:18 -08:00
|
|
|
sp -= (d->frame_size & 0xFFFC);
|
1995-11-26 06:38:29 -08:00
|
|
|
#endif
|
1998-07-29 04:52:59 -07:00
|
|
|
retaddr = Saved_return_address(sp);
|
1995-07-10 02:48:27 -07:00
|
|
|
#ifdef Already_scanned
|
1998-07-29 04:52:59 -07:00
|
|
|
/* Stop here if the frame has been scanned during earlier GCs */
|
|
|
|
if (Already_scanned(sp, retaddr)) break;
|
|
|
|
/* Mark frame as already scanned */
|
|
|
|
Mark_scanned(sp, retaddr);
|
1995-07-10 02:48:27 -07:00
|
|
|
#endif
|
1998-07-29 04:52:59 -07:00
|
|
|
} else {
|
|
|
|
/* This marks the top of a stack chunk for an ML callback.
|
|
|
|
Skip C portion of stack and continue with next ML stack chunk. */
|
|
|
|
struct caml_context * next_context = Callback_link(sp);
|
|
|
|
sp = next_context->bottom_of_stack;
|
|
|
|
retaddr = next_context->last_retaddr;
|
|
|
|
regs = next_context->gc_regs;
|
|
|
|
/* A null sp means no more ML stack chunks; stop here. */
|
|
|
|
if (sp == NULL) break;
|
|
|
|
}
|
1995-12-19 07:09:33 -08:00
|
|
|
}
|
1995-07-10 02:48:27 -07:00
|
|
|
}
|
|
|
|
/* Local C roots */
|
2004-01-01 08:42:43 -08:00
|
|
|
for (lr = caml_local_roots; lr != NULL; lr = lr->next) {
|
1997-05-26 10:16:31 -07:00
|
|
|
for (i = 0; i < lr->ntables; i++){
|
|
|
|
for (j = 0; j < lr->nitems; j++){
|
|
|
|
root = &(lr->tables[i][j]);
|
2002-01-20 14:20:58 -08:00
|
|
|
Oldify (root);
|
1997-05-26 10:16:31 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
1995-07-10 02:48:27 -07:00
|
|
|
/* Global C roots */
|
2008-03-10 12:56:39 -07:00
|
|
|
caml_scan_global_young_roots(&caml_oldify_one);
|
2000-01-07 08:51:58 -08:00
|
|
|
/* Finalised values */
|
2004-01-02 11:23:29 -08:00
|
|
|
caml_final_do_young_roots (&caml_oldify_one);
|
1995-10-30 02:20:08 -08:00
|
|
|
/* Hook */
|
2008-03-10 12:56:39 -07:00
|
|
|
if (caml_scan_roots_hook != NULL) (*caml_scan_roots_hook)(&caml_oldify_one);
|
1995-07-10 02:48:27 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Call [darken] on all roots */
|
|
|
|
|
2004-01-01 08:42:43 -08:00
|
|
|
void caml_darken_all_roots (void)
|
1997-05-13 07:45:38 -07:00
|
|
|
{
|
2004-01-01 08:42:43 -08:00
|
|
|
caml_do_roots (caml_darken);
|
1997-05-13 07:45:38 -07:00
|
|
|
}
|
|
|
|
|
2004-01-01 08:42:43 -08:00
|
|
|
void caml_do_roots (scanning_action f)
|
1995-07-10 02:48:27 -07:00
|
|
|
{
|
1997-08-29 08:37:22 -07:00
|
|
|
int i, j;
|
1995-07-17 09:10:45 -07:00
|
|
|
value glob;
|
2007-11-06 07:16:56 -08:00
|
|
|
link *lnk;
|
1995-07-10 02:48:27 -07:00
|
|
|
|
|
|
|
/* The global roots */
|
1995-07-17 09:10:45 -07:00
|
|
|
for (i = 0; caml_globals[i] != 0; i++) {
|
|
|
|
glob = caml_globals[i];
|
|
|
|
for (j = 0; j < Wosize_val(glob); j++)
|
1997-05-13 07:45:38 -07:00
|
|
|
f (Field (glob, j), &Field (glob, j));
|
1995-07-17 09:10:45 -07:00
|
|
|
}
|
2007-11-06 07:16:56 -08:00
|
|
|
|
|
|
|
/* Dynamic global roots */
|
|
|
|
iter_list(caml_dyn_globals, lnk) {
|
|
|
|
glob = (value) lnk->data;
|
|
|
|
for (j = 0; j < Wosize_val(glob); j++){
|
|
|
|
f (Field (glob, j), &Field (glob, j));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
1997-08-29 08:37:22 -07:00
|
|
|
/* The stack and local roots */
|
2007-01-29 04:11:18 -08:00
|
|
|
if (caml_frame_descriptors == NULL) caml_init_frame_descriptors();
|
2004-01-01 08:42:43 -08:00
|
|
|
caml_do_local_roots(f, caml_bottom_of_stack, caml_last_return_address,
|
|
|
|
caml_gc_regs, caml_local_roots);
|
1997-08-29 08:37:22 -07:00
|
|
|
/* Global C roots */
|
2008-03-10 12:56:39 -07:00
|
|
|
caml_scan_global_roots(f);
|
2000-01-07 08:51:58 -08:00
|
|
|
/* Finalised values */
|
2004-01-02 11:23:29 -08:00
|
|
|
caml_final_do_strong_roots (f);
|
1997-08-29 08:37:22 -07:00
|
|
|
/* Hook */
|
2004-01-01 08:42:43 -08:00
|
|
|
if (caml_scan_roots_hook != NULL) (*caml_scan_roots_hook)(f);
|
1997-08-29 08:37:22 -07:00
|
|
|
}
|
|
|
|
|
2004-01-01 08:42:43 -08:00
|
|
|
void caml_do_local_roots(scanning_action f, char * bottom_of_stack,
|
2005-09-22 07:21:50 -07:00
|
|
|
uintnat last_retaddr, value * gc_regs,
|
2004-01-01 08:42:43 -08:00
|
|
|
struct caml__roots_block * local_roots)
|
1997-08-29 08:37:22 -07:00
|
|
|
{
|
|
|
|
char * sp;
|
2005-09-22 07:21:50 -07:00
|
|
|
uintnat retaddr;
|
1997-11-21 05:46:23 -08:00
|
|
|
value * regs;
|
1997-08-29 08:37:22 -07:00
|
|
|
frame_descr * d;
|
2005-09-22 07:21:50 -07:00
|
|
|
uintnat h;
|
1997-08-29 08:37:22 -07:00
|
|
|
int i, j, n, ofs;
|
2008-02-29 06:21:22 -08:00
|
|
|
#ifdef Stack_grows_upwards
|
|
|
|
short * p; /* PR#4339: stack offsets are negative in this case */
|
|
|
|
#else
|
2007-01-29 04:11:18 -08:00
|
|
|
unsigned short * p;
|
2008-02-29 06:21:22 -08:00
|
|
|
#endif
|
1997-08-29 08:37:22 -07:00
|
|
|
value * root;
|
|
|
|
struct caml__roots_block *lr;
|
|
|
|
|
1997-11-27 08:28:40 -08:00
|
|
|
sp = bottom_of_stack;
|
|
|
|
retaddr = last_retaddr;
|
|
|
|
regs = gc_regs;
|
1998-07-29 04:52:59 -07:00
|
|
|
if (sp != NULL) {
|
|
|
|
while (1) {
|
|
|
|
/* Find the descriptor corresponding to the return address */
|
|
|
|
h = Hash_retaddr(retaddr);
|
|
|
|
while(1) {
|
2007-01-29 04:11:18 -08:00
|
|
|
d = caml_frame_descriptors[h];
|
1998-07-29 04:52:59 -07:00
|
|
|
if (d->retaddr == retaddr) break;
|
2007-01-29 04:11:18 -08:00
|
|
|
h = (h+1) & caml_frame_descriptors_mask;
|
1995-07-10 02:48:27 -07:00
|
|
|
}
|
2007-01-29 04:11:18 -08:00
|
|
|
if (d->frame_size != 0xFFFF) {
|
1998-07-29 04:52:59 -07:00
|
|
|
/* Scan the roots in this frame */
|
|
|
|
for (p = d->live_ofs, n = d->num_live; n > 0; n--, p++) {
|
|
|
|
ofs = *p;
|
|
|
|
if (ofs & 1) {
|
|
|
|
root = regs + (ofs >> 1);
|
|
|
|
} else {
|
|
|
|
root = (value *)(sp + ofs);
|
|
|
|
}
|
|
|
|
f (*root, root);
|
|
|
|
}
|
|
|
|
/* Move to next frame */
|
1995-11-26 06:38:29 -08:00
|
|
|
#ifndef Stack_grows_upwards
|
2007-01-29 04:11:18 -08:00
|
|
|
sp += (d->frame_size & 0xFFFC);
|
1995-11-26 06:38:29 -08:00
|
|
|
#else
|
2007-01-29 04:11:18 -08:00
|
|
|
sp -= (d->frame_size & 0xFFFC);
|
1995-11-26 06:38:29 -08:00
|
|
|
#endif
|
1998-07-29 04:52:59 -07:00
|
|
|
retaddr = Saved_return_address(sp);
|
1995-07-10 02:48:27 -07:00
|
|
|
#ifdef Mask_already_scanned
|
1998-07-29 04:52:59 -07:00
|
|
|
retaddr = Mask_already_scanned(retaddr);
|
1995-07-10 02:48:27 -07:00
|
|
|
#endif
|
1998-07-29 04:52:59 -07:00
|
|
|
} else {
|
|
|
|
/* This marks the top of a stack chunk for an ML callback.
|
|
|
|
Skip C portion of stack and continue with next ML stack chunk. */
|
|
|
|
struct caml_context * next_context = Callback_link(sp);
|
|
|
|
sp = next_context->bottom_of_stack;
|
|
|
|
retaddr = next_context->last_retaddr;
|
|
|
|
regs = next_context->gc_regs;
|
|
|
|
/* A null sp means no more ML stack chunks; stop here. */
|
|
|
|
if (sp == NULL) break;
|
|
|
|
}
|
1995-12-19 07:09:33 -08:00
|
|
|
}
|
1995-07-10 02:48:27 -07:00
|
|
|
}
|
|
|
|
/* Local C roots */
|
1997-06-01 10:15:19 -07:00
|
|
|
for (lr = local_roots; lr != NULL; lr = lr->next) {
|
1997-05-26 10:16:31 -07:00
|
|
|
for (i = 0; i < lr->ntables; i++){
|
|
|
|
for (j = 0; j < lr->nitems; j++){
|
|
|
|
root = &(lr->tables[i][j]);
|
|
|
|
f (*root, root);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
1995-07-10 02:48:27 -07:00
|
|
|
}
|