diff --git a/Makefile.am b/Makefile.am index e632864..9b33b50 100644 --- a/Makefile.am +++ b/Makefile.am @@ -71,6 +71,10 @@ if WIN32_THREADS libgc_la_SOURCES += win32_threads.c endif +if ENABLE_DISCLAIM +libgc_la_SOURCES += disclaim.c +endif + if USE_INTERNAL_LIBATOMIC_OPS nodist_libgc_la_SOURCES = atomic_ops.c endif diff --git a/Makefile.direct b/Makefile.direct index 40195a5..3df6edc 100644 --- a/Makefile.direct +++ b/Makefile.direct @@ -36,7 +36,7 @@ AO_VERSION=1.2 AO_SRC_DIR=$(srcdir)/libatomic_ops-$(AO_VERSION) AO_INSTALL_DIR=$(srcdir)/libatomic_ops-install -CFLAGS= -O -I$(srcdir)/include -I$(AO_INSTALL_DIR)/include -DATOMIC_UNCOLLECTABLE -DNO_EXECUTE_PERMISSION -DALL_INTERIOR_POINTERS +CFLAGS= -O -I$(srcdir)/include -I$(AO_INSTALL_DIR)/include -DATOMIC_UNCOLLECTABLE -DNO_EXECUTE_PERMISSION -DALL_INTERIOR_POINTERS -DENABLE_DISCLAIM # To build the parallel collector on Linux, add to the above: # -DGC_LINUX_THREADS -DPARALLEL_MARK -DTHREAD_LOCAL_ALLOC @@ -330,14 +330,14 @@ OBJS= alloc.o reclaim.o allchblk.o misc.o mach_dep.o os_dep.o mark_rts.o \ headers.o mark.o obj_map.o blacklst.o finalize.o new_hblk.o dbg_mlc.o \ malloc.o stubborn.o checksums.o pthread_support.o pthread_stop_world.o \ darwin_stop_world.o typd_mlc.o ptr_chck.o mallocx.o gcj_mlc.o specific.o \ - gc_dlopen.o backgraph.o win32_threads.o thread_local_alloc.o + gc_dlopen.o backgraph.o win32_threads.o thread_local_alloc.o disclaim.o CSRCS= reclaim.c allchblk.c misc.c alloc.c mach_dep.c os_dep.c mark_rts.c \ headers.c mark.c obj_map.c pcr_interface.c blacklst.c finalize.c \ new_hblk.c real_malloc.c dyn_load.c dbg_mlc.c malloc.c stubborn.c \ checksums.c pthread_support.c pthread_stop_world.c darwin_stop_world.c \ typd_mlc.c ptr_chck.c mallocx.c gcj_mlc.c specific.c gc_dlopen.c \ - backgraph.c win32_threads.c thread_local_alloc.c + backgraph.c win32_threads.c thread_local_alloc.c disclaim.c CORD_SRCS= cord/cordbscs.c cord/cordxtra.c cord/cordprnt.c cord/de.c cord/cordtest.c include/cord.h include/ec.h include/private/cord_pos.h cord/de_win.c cord/de_win.h cord/de_cmds.h cord/de_win.ICO cord/de_win.RC @@ -354,7 +354,7 @@ SRCS= $(CSRCS) mips_sgi_mach_dep.s rs6000_mach_dep.s alpha_mach_dep.S \ include/new_gc_alloc.h include/gc_allocator.h \ include/javaxfc.h sparc_sunos4_mach_dep.s sparc_netbsd_mach_dep.s \ include/gc_backptr.h \ - hpux_test_and_clear.s include/gc_gcj.h \ + hpux_test_and_clear.s include/gc_gcj.h include/gc_disclaim.h \ include/private/dbg_mlc.h \ include/private/specific.h \ include/leak_detector.h include/gc_amiga_redirects.h \ diff --git a/allchblk.c b/allchblk.c index 0cec29d..d2ea8a9 100644 --- a/allchblk.c +++ b/allchblk.c @@ -222,6 +222,12 @@ static GC_bool setup_header(hdr * hhdr, struct hblk *block, size_t byte_sz, { word descr; size_t granules; +# ifdef ENABLE_DISCLAIM + if (GC_obj_kinds[kind].ok_disclaim_proc) + flags |= HAS_DISCLAIM; + if (GC_obj_kinds[kind].ok_mark_unconditionally) + flags |= MARK_UNCONDITIONALLY; +# endif /* Set size, kind and mark proc fields */ hhdr -> hb_sz = byte_sz; diff --git a/configure.ac b/configure.ac index adfb070..e125ea8 100644 --- a/configure.ac +++ b/configure.ac @@ -529,6 +529,16 @@ if test "${enable_redirect_malloc}" = yes; then fi fi +AC_ARG_ENABLE(disclaim, + [AC_HELP_STRING([--disable-disclaim], + [Disable alternative (more efficient) finalization interface.])]) +if test x"$enable_disclaim" != xno; then + AC_DEFINE(ENABLE_DISCLAIM, 1, + [Define to enable alternative finalization interface.]) +fi +AM_CONDITIONAL(ENABLE_DISCLAIM, + [test x"$enable_disclaim" != xno]) + AC_ARG_ENABLE(large-config, [AC_HELP_STRING([--enable-large-config], [Optimize for large (> 100 MB) heap or root set])]) diff --git a/disclaim.c b/disclaim.c new file mode 100644 index 0000000..14f0242 --- /dev/null +++ b/disclaim.c @@ -0,0 +1,144 @@ +#ifdef ENABLE_DISCLAIM + +#include "gc_disclaim.h" +#include "private/gc_priv.h" +#include "private/thread_local_alloc.h" + + +/* Low level interface for reclaim callbacks. */ + +int GC_register_disclaim_proc(int kind, + int (*proc)(void *obj, void *cd), void *cd, + int mark_unconditionally) +{ + GC_obj_kinds[kind].ok_disclaim_proc = proc; + GC_obj_kinds[kind].ok_disclaim_cd = cd; + GC_obj_kinds[kind].ok_mark_unconditionally = mark_unconditionally; +} + + +/* High level interface for finalization. */ + +int GC_finalized_kind; + +int GC_finalized_debug_kind; + +ptr_t * GC_finalized_objfreelist; +ptr_t * GC_finalized_debugobjfreelist; + +static int GC_finalized_disclaim(void *obj, void *cd) +{ + struct GC_finalizer_closure *fc = *(void **)obj; + if ((word)fc & 1) { /* See [1] */ + fc = (void *)((word)fc & ~(word)1); + (*fc->proc)((void **)obj + 1, fc->cd); + } + return 0; +} + +static int done_init = 0; +void GC_init_finalized_malloc() +{ + DCL_LOCK_STATE; + if (done_init) + return; + GC_init(); + LOCK(); + if (done_init) + goto done; + done_init = 1; + + GC_finalized_objfreelist = (ptr_t *)GC_new_free_list_inner(); + GC_finalized_kind = + GC_new_kind_inner((void **)GC_finalized_objfreelist, + 0 | GC_DS_LENGTH, + TRUE, TRUE); + GC_register_disclaim_proc(GC_finalized_kind, GC_finalized_disclaim, 0, 1); + +done: + UNLOCK(); +} + +ptr_t GC_clear_stack(); + +#ifdef THREAD_LOCAL_ALLOC + void * GC_core_finalized_malloc(size_t lb, struct GC_finalizer_closure *fclos) +#else + void * GC_finalized_malloc(size_t lb, struct GC_finalizer_closure *fclos) +#endif +{ + register ptr_t op; + register ptr_t *opp; + DCL_LOCK_STATE; + + lb += sizeof(void *); + if (!done_init) + ABORT("You must call GC_init_finalize_malloc before using " + "GC_malloc_with_finalizer."); + if (EXPECT(SMALL_OBJ(lb), 1)) { + register word lg; + lg = GC_size_map[lb]; + opp = &GC_finalized_objfreelist[lg]; + LOCK(); + if (EXPECT((op = *opp) == 0, 0)) { + UNLOCK(); + op = GC_generic_malloc((word)lb, GC_finalized_kind); + } else { + *opp = obj_link(op); + obj_link(op) = 0; + GC_bytes_allocd += GRANULES_TO_BYTES(lg); + UNLOCK(); + } + } else + op = GC_generic_malloc((word)lb, GC_finalized_kind); + *(void **)op = (ptr_t)fclos + 1; /* See [1] */ + return GC_clear_stack(op + sizeof(void *)); +} + +#ifdef THREAD_LOCAL_ALLOC +void * GC_finalized_malloc(size_t client_lb, struct GC_finalizer_closure *fclos) +{ + size_t lb = client_lb + sizeof(void *); + size_t lg = ROUNDED_UP_GRANULES(lb); + GC_tlfs tsd; + void *result; + void **tiny_fl, **my_fl, *my_entry; + void *next; + if (GC_EXPECT(lg >= GC_TINY_FREELISTS, 0)) + return GC_core_finalized_malloc(client_lb, fclos); + + tsd = GC_getspecific(GC_thread_key); + tiny_fl = tsd->finalized_freelists; + my_fl = tiny_fl + lg; + my_entry = *my_fl; + while (GC_EXPECT((word)my_entry + <= DIRECT_GRANULES + GC_TINY_FREELISTS + 1, 0)) { + if ((word)my_entry - 1 < DIRECT_GRANULES) { + *my_fl = (ptr_t)my_entry + lg + 1; + return GC_core_finalized_malloc(client_lb, fclos); + } else { + GC_generic_malloc_many(RAW_BYTES_FROM_INDEX(lg), + GC_finalized_kind, my_fl); + my_entry = *my_fl; + if (my_entry == 0) + return GC_oom_fn(lb); + } + } + next = obj_link(my_entry); + result = (void *)my_entry; + *my_fl = next; + *(void **)result = (ptr_t)fclos + 1; + PREFETCH_FOR_WRITE(next); + return (void **)result + 1; +} +#endif + +#endif /* ENABLE_DISCLAIM */ + +/* [1] The disclaim function may be passed fragments from the free-list, on + * which it should not run finalization. To recognize this case, we use + * the fact that the first word on such fragments are always even (a link + * to the next fragment, or NULL). If it is desirable to have a finalizer + * which does not use the first word for storing finalization info, + * GC_reclaim_with_finalization must be extended to clear fragments so + * that the assumption holds for the selected word. */ diff --git a/include/gc_disclaim.h b/include/gc_disclaim.h new file mode 100644 index 0000000..c67a9ce --- /dev/null +++ b/include/gc_disclaim.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2007 by Hewlett-Packard Company. 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. + * + */ + +#ifndef _GC_DISCLAIM_H +#define _GC_DISCLAIM_H + +#include "gc.h" + +/* Register a function notifier_proc which will be called on each */ +/* object of the given kind before it is reclaimed. If notifier_proc */ +/* returns non-zero, the collector will not reclaim the object on this */ +/* GC cycle. Objects reachable from proc will be protected from */ +/* collection if mark_from_all=1, but at the expense that long chains */ +/* of objects will take many cycles to reclaim. */ +/* Not available if configured with --disable-disclaim. */ +int GC_register_disclaim_proc(int kind, + int (*proc)(void *obj, void *cd), void *cd, + int mark_from_all); + +/* The finalizer closure used by GC_finalized_malloc. */ +struct GC_finalizer_closure { + void (*proc)(void *obj, void *cd); + void *cd; +}; + +/* Allocate size bytes which is finalized with fc. This uses a */ +/* dedicated object kind with the disclaim mechanism for maximum. It */ +/* is more efficient than GC_register_finalizer and friends. You need */ +/* to call GC_init_finalized_malloc before using this. */ +GC_API void *GC_finalized_malloc(size_t size, struct GC_finalizer_closure *fc); + +/* Prepare the object kind used for GC_finalized_malloc. */ +GC_API void GC_init_finalized_malloc(void); + +#endif diff --git a/include/include.am b/include/include.am index 78c57c3..8b4e305 100644 --- a/include/include.am +++ b/include/include.am @@ -33,6 +33,11 @@ pkginclude_HEADERS += \ include/gc_config_macros.h \ include/gc_tiny_fl.h +if ENABLE_DISCLAIM +pkginclude_HEADERS += include/gc_disclaim.h +endif +EXTRA_DIST += include/gc_disclaim.h + # headers which are not installed # dist_noinst_HEADERS += \ diff --git a/include/private/gc_priv.h b/include/private/gc_priv.h index ec93ffe..6991eb9 100644 --- a/include/private/gc_priv.h +++ b/include/private/gc_priv.h @@ -684,6 +684,14 @@ struct hblkhdr { /* before it can be reallocated. */ /* Only set with USE_MUNMAP. */ # define FREE_BLK 4 /* Block is free, i.e. not in use. */ +# ifdef ENABLE_DISCLAIM +# define HAS_DISCLAIM 8 + /* This kind has a callback on reclaim. */ +# define MARK_UNCONDITIONALLY 16 + /* Mark from all objects, marked or */ + /* not. Used to mark objects needed by */ + /* reclaim notifier. */ +# endif unsigned short hb_last_reclaimed; /* Value of GC_gc_no when block was */ /* last allocated or swept. May wrap. */ @@ -1095,6 +1103,18 @@ extern struct obj_kind { /* template to obtain descriptor. Otherwise */ /* template is used as is. */ GC_bool ok_init; /* Clear objects before putting them on the free list. */ +# ifdef ENABLE_DISCLAIM + GC_bool ok_mark_unconditionally; + /* Mark from all, including unmarked, objects */ + /* in block. Used to protect objects reachable */ + /* from reclaim notifiers. */ + int (*ok_disclaim_proc)(void *obj, void *cd); + void *ok_disclaim_cd; + /* The disclaim procedure is called before obj */ + /* is reclaimed, but must also tolerate being */ + /* called with object from freelist. Non-zero */ + /* exit prevents object from being reclaimed. */ +# endif } GC_obj_kinds[MAXOBJKINDS]; # define beginGC_obj_kinds ((ptr_t)(&GC_obj_kinds)) diff --git a/include/private/thread_local_alloc.h b/include/private/thread_local_alloc.h index 4c2c536..0275cda 100644 --- a/include/private/thread_local_alloc.h +++ b/include/private/thread_local_alloc.h @@ -87,6 +87,9 @@ typedef struct thread_local_freelists { /* Don't use local free lists for up to this much */ /* allocation. */ +# ifdef ENABLE_DISCLAIM + void * finalized_freelists[TINY_FREELISTS]; +# endif # endif } *GC_tlfs; diff --git a/mark.c b/mark.c index 3e5c46d..2dc89a7 100644 --- a/mark.c +++ b/mark.c @@ -1779,6 +1779,36 @@ void GC_push_marked(struct hblk *h, hdr *hhdr) } } +#ifdef MARK_UNCONDITIONALLY +/* Mark all objects which have not been reclaimed according to the convension + * that the first word is odd for live objects. This is used optionally to + * guard the contents of objects passed to reclaim notifiers. */ +void GC_push_unconditionally(struct hblk *h, hdr *hhdr) +{ + int sz = hhdr -> hb_sz; + int descr = hhdr -> hb_descr; + ptr_t p; + ptr_t lim; + mse * GC_mark_stack_top_reg; + mse * mark_stack_limit = GC_mark_stack_limit; + + /* Shortcut */ + if ((0 | GC_DS_LENGTH) == descr) return; + GC_n_rescuing_pages++; + GC_objects_are_marked = TRUE; + if (sz > MAXOBJBYTES) + lim = h -> hb_body; + else + lim = (h + 1)->hb_body - sz; + + GC_mark_stack_top_reg = GC_mark_stack_top; + for (p = h -> hb_body; p <= lim; p += sz) + if ((*(GC_word *)p & 0x3) != 0) + PUSH_OBJ(p, hhdr, GC_mark_stack_top_reg, mark_stack_limit); + GC_mark_stack_top = GC_mark_stack_top_reg; +} +#endif + #ifndef SMALL_CONFIG /* Test whether any page in the given block is dirty */ GC_bool GC_block_was_dirty(struct hblk *h, hdr *hhdr) @@ -1856,11 +1886,19 @@ struct hblk * GC_push_next_marked_uncollectable(struct hblk *h) if (h == 0) return(0); hhdr = GC_find_header((ptr_t)h); } - if (hhdr -> hb_obj_kind == UNCOLLECTABLE) break; + if (hhdr -> hb_obj_kind == UNCOLLECTABLE) { + GC_push_marked(h, hhdr); + break; + } +# ifdef MARK_UNCONDITIONALLY + if (hhdr -> hb_flags & MARK_UNCONDITIONALLY) { + GC_push_unconditionally(h, hhdr); + break; + } +# endif h += OBJ_SZ_TO_BLOCKS(hhdr -> hb_sz); hhdr = HDR(h); } - GC_push_marked(h, hhdr); return(h + OBJ_SZ_TO_BLOCKS(hhdr -> hb_sz)); } diff --git a/misc.c b/misc.c index 37e5093..30a7b86 100644 --- a/misc.c +++ b/misc.c @@ -1114,6 +1114,10 @@ unsigned GC_new_kind_inner(void **fl, GC_word descr, int adjust, int clear) GC_obj_kinds[result].ok_descriptor = descr; GC_obj_kinds[result].ok_relocate_descr = adjust; GC_obj_kinds[result].ok_init = clear; +# ifdef ENABLE_DISCLAIM + GC_obj_kinds[result].ok_disclaim_proc = 0; + GC_obj_kinds[result].ok_disclaim_cd = 0; +# endif return result; } diff --git a/reclaim.c b/reclaim.c index cd50fa4..e52bd1d 100644 --- a/reclaim.c +++ b/reclaim.c @@ -16,6 +16,7 @@ #include #include "private/gc_priv.h" +#include "gc_disclaim.h" signed_word GC_bytes_found = 0; /* Number of bytes of memory reclaimed */ @@ -92,6 +93,11 @@ void GC_print_all_errors () GC_bool GC_block_empty(hdr *hhdr) { + /* XXX: Only if reclaim notifiers have not been run. */ +# ifdef ENABLE_DISCLAIM + if (hhdr -> hb_flags & HAS_DISCLAIM) + return FALSE; +# endif return (hhdr -> hb_n_marks == 0); } @@ -183,6 +189,56 @@ ptr_t GC_reclaim_uninit(struct hblk *hbp, hdr *hhdr, size_t sz, return(list); } +#ifdef ENABLE_DISCLAIM +/* Call reclaim notifier for block's kind on each unmarked object in */ +/* block, all within a pair of corresponding enter/leave callbacks. */ +ptr_t GC_disclaim_and_reclaim(struct hblk *hbp, hdr *hhdr, size_t sz, + ptr_t list, signed_word *count) +{ + register int bit_no = 0; + register word *p, *q, *plim; + signed_word n_bytes_found = 0; + struct obj_kind *ok = &GC_obj_kinds[hhdr->hb_obj_kind]; + int (*proc)(void *, void *) = ok -> ok_disclaim_proc; + void *cd = ok -> ok_disclaim_cd; + + GC_ASSERT(sz == hhdr -> hb_sz); + p = (word *)(hbp -> hb_body); + plim = (word *)((ptr_t)p + HBLKSIZE - sz); + + while (p <= plim) { + if (mark_bit_from_hdr(hhdr, bit_no) || (*proc)(p, cd)) + p = (word *)((ptr_t)p + sz); + else { + n_bytes_found += sz; + /* object is available - put on list */ + obj_link(p) = list; + list = ((ptr_t)p); + /* Clear object, advance p to next object in the process */ + q = (word *)((ptr_t)p + sz); +# ifdef USE_MARK_BYTES + GC_ASSERT(!(sz & 1) + && !((word)p & (2 * sizeof(word) - 1))); + p[1] = 0; + p += 2; + while (p < q) { + CLEAR_DOUBLE(p); + p += 2; + } +# else + p++; /* Skip link field */ + while (p < q) { + *p++ = 0; + } +# endif + } + bit_no += MARK_BIT_OFFSET(sz); + } + *count += n_bytes_found; + return list; +} +#endif /* ENABLE_DISCLAIM */ + /* Don't really reclaim objects, just check for unmarked ones: */ /*ARGSUSED*/ void GC_reclaim_check(struct hblk *hbp, hdr *hhdr, word sz) @@ -217,6 +273,11 @@ ptr_t GC_reclaim_generic(struct hblk * hbp, hdr *hhdr, size_t sz, GC_ASSERT(GC_find_header((ptr_t)hbp) == hhdr); GC_remove_protection(hbp, 1, (hhdr)->hb_descr == 0 /* Pointer-free? */); +# ifdef ENABLE_DISCLAIM + if (hhdr -> hb_flags & HAS_DISCLAIM) + result = GC_disclaim_and_reclaim(hbp, hhdr, sz, list, count); + else +# endif if (init) { result = GC_reclaim_clear(hbp, hhdr, sz, list, count); } else { @@ -274,6 +335,17 @@ void GC_reclaim_block(struct hblk *hbp, word report_if_found) GC_add_leaked((ptr_t)hbp); } else { size_t blocks = OBJ_SZ_TO_BLOCKS(sz); +# ifdef ENABLE_DISCLAIM + if (EXPECT(hhdr->hb_flags & HAS_DISCLAIM, 0)) { + struct obj_kind *ok = &GC_obj_kinds[hhdr->hb_obj_kind]; + int resurrect; + resurrect = (*ok->ok_disclaim_proc)(hbp, ok->ok_disclaim_cd); + if (resurrect) { + set_mark_bit_from_hdr(hhdr, 0); + /* excuse me, */ goto in_use; + } + } +# endif if (blocks > 1) { GC_large_allocd_bytes -= blocks * HBLKSIZE; } @@ -281,6 +353,7 @@ void GC_reclaim_block(struct hblk *hbp, word report_if_found) GC_freehblk(hbp); } } else { + in_use: if (hhdr -> hb_descr != 0) { GC_composite_in_use += sz; } else { diff --git a/tests/disclaim_bench.c b/tests/disclaim_bench.c new file mode 100644 index 0000000..af2375e --- /dev/null +++ b/tests/disclaim_bench.c @@ -0,0 +1,115 @@ +#include "gc_disclaim.h" +#include +#include +#include +#include +#include + +static int nf = 0; + +typedef struct testobj_s *testobj_t; +struct testobj_s { + testobj_t keep_link; + int i; +}; + +void testobj_finalize(void *obj, void *carg) +{ +#define obj ((testobj_t)obj) + ++*(int *)carg; + assert(obj->i++ == 109); +#undef obj +} +static struct GC_finalizer_closure fclos = { + testobj_finalize, + &nf +}; + +testobj_t testobj_new(int model) +{ + testobj_t obj; + switch (model) { + case 0: + obj = GC_malloc(sizeof(struct testobj_s)); + GC_register_finalizer_no_order(obj, testobj_finalize, &nf, + NULL, NULL); + break; + case 1: + obj = GC_finalized_malloc(sizeof(struct testobj_s), &fclos); + break; + case 2: + obj = GC_malloc(sizeof(struct testobj_s)); + break; + default: + abort(); + } + obj->i = 109; + obj->keep_link = NULL; + return obj; +} + + +#define ALLOC_CNT (4*1024*1024) +#define KEEP_CNT (32*1024) + +int main(int argc, char **argv) +{ + int i; + int repeat; + int model; + testobj_t *keep_arr; + double t; + static char const *model_str[3] = { + "regular finalization", + "finalize on reclaim", + "no finalization" + }; + + GC_init(); + GC_init_finalized_malloc(); + + /* Seed with time for distict usage patters over repeated runs. */ + srand48(time(NULL)); + + keep_arr = GC_malloc(sizeof(void *)*KEEP_CNT); + + if (argc == 1) { + char *buf = GC_malloc(strlen(argv[0]) + 3); + printf("\t\t\tfin. ratio time/s time/fin.\n"); + for (i = 0; i < 3; ++i) { + int st; + sprintf(buf, "%s %d", argv[0], i); + st = system(buf); + if (st != 0) + return st; + } + return 0; + } + if (argc == 2 && strcmp(argv[1], "--help") == 0) { + fprintf(stderr, + "Usage: %s FINALIZATION_MODEL\n" + "\t0 -- original finalization\n" + "\t1 -- finalization on reclaim\n" + "\t2 -- no finalization\n", argv[0]); + return 1; + } + model = atoi(argv[1]); + if (model < 0 || model > 2) + exit(2); + t = -clock(); + for (i = 0; i < ALLOC_CNT; ++i) { + int k = lrand48() % KEEP_CNT; + keep_arr[k] = testobj_new(model); + } + GC_gcollect(); + t += clock(); + t /= CLOCKS_PER_SEC; + if (model < 2) + printf("%20s: %12.4lf %12lg %12lg\n", model_str[model], + nf/(double)ALLOC_CNT, t, t/nf); + else + printf("%20s: 0 %12lg N/A\n", + model_str[model], t); + + return 0; +} diff --git a/tests/disclaim_test.c b/tests/disclaim_test.c new file mode 100644 index 0000000..bc8462a --- /dev/null +++ b/tests/disclaim_test.c @@ -0,0 +1,138 @@ +/* Test that objects reachable from an object allocated with + * GC_malloc_with_finalizer is not reclaimable before the finalizer + * is called. */ + +#include +#include +#include +#include +#include + +typedef struct pair_s *pair_t; + +struct pair_s { + int is_valid; + int checksum; + pair_t car; + pair_t cdr; +}; + +void +pair_dct(void *obj, void *cd) +{ + pair_t p = obj; + int checksum; + + /* Check that obj and its car and cdr are not trashed. */ + //printf("Destruct %p = (%p, %p)\n", p, p->car, p->cdr); + assert(GC_base(obj)); + assert(p->is_valid); + assert(!p->car || p->car->is_valid); + assert(!p->cdr || p->cdr->is_valid); + checksum = 782; + if (p->car) checksum += p->car->checksum; + if (p->cdr) checksum += p->cdr->checksum; + assert(p->checksum == checksum); + + /* Invalidate it. */ + p->is_valid = 0; + p->checksum = 0; + p->car = NULL; + p->cdr = NULL; +} + +pair_t +pair_new(pair_t car, pair_t cdr) +{ + pair_t p; + static struct GC_finalizer_closure fc = { pair_dct, NULL }; + p = GC_finalized_malloc(sizeof(struct pair_s), &fc); + p->is_valid = 1; + p->checksum = 782 + (car? car->checksum : 0) + (cdr? cdr->checksum : 0); + p->car = car; + p->cdr = cdr; + //printf("Construct %p = (%p, %p)\n", p, p->car, p->cdr); + return p; +} + +void +pair_check_rec(pair_t p) +{ + while (p) { + int checksum = 782; + if (p->car) checksum += p->car->checksum; + if (p->cdr) checksum += p->cdr->checksum; + assert(p->checksum == checksum); + if (rand() % 2) + p = p->car; + else + p = p->cdr; + } +} + +#ifdef GC_PTHREADS +# define THREAD_CNT 6 +#else +# define THREAD_CNT 1 +#endif + +#define POP_SIZE 1000 +#if THREAD_CNT > 1 +# define MUTATE_CNT 2000000/THREAD_CNT +#else +# define MUTATE_CNT 10000000 +#endif +#define GROW_LIMIT 10000000 + +void *test(void *data) +{ + int i; + pair_t pop[POP_SIZE]; + memset(pop, 0, sizeof(pop)); + for (i = 0; i < MUTATE_CNT; ++i) { + int t = rand() % POP_SIZE; + switch (rand() % (i > GROW_LIMIT? 5 : 3)) { + case 0: case 3: + if (pop[t]) + pop[t] = pop[t]->car; + break; + case 1: case 4: + if (pop[t]) + pop[t] = pop[t]->cdr; + break; + case 2: + pop[t] = pair_new(pop[rand() % POP_SIZE], + pop[rand() % POP_SIZE]); + break; + } + if (rand() % 8 == 1) + pair_check_rec(pop[rand() % POP_SIZE]); + } + return 0; +} + +int main() +{ + pthread_t th[THREAD_CNT]; + int i; + GC_init(); + GC_init_finalized_malloc(); +#if THREAD_CNT > 1 + printf("Threaded disclaim test.\n"); + for (i = 0; i < THREAD_CNT; ++i) { + int err = pthread_create(&th[i], NULL, test, NULL); + if (err) { + fprintf(stderr, "Failed to create thread # %d: %s\n", i, + strerror(err)); + exit(1); + } + } + for (i = 0; i < THREAD_CNT; ++i) + pthread_join(th[i], NULL); +#else + printf("Unthreaded disclaim test.\n"); + test(NULL); +#endif + return 0; +} + diff --git a/tests/tests.am b/tests/tests.am index aedac1f..8ad12f5 100644 --- a/tests/tests.am +++ b/tests/tests.am @@ -19,6 +19,8 @@ # Common libs to _LDADD for all tests. test_ldadd = $(top_builddir)/libgc.la $(EXTRA_TEST_LIBS) +# Details on each test +# -------------------- TESTS += gctest$(EXEEXT) check_PROGRAMS += gctest @@ -55,3 +57,14 @@ test_cpp_SOURCES = tests/test_cpp.cc test_cpp_LDADD = libgccpp.la $(test_ldadd) endif +if ENABLE_DISCLAIM +TESTS += disclaim_test +check_PROGRAMS += disclaim_test +disclaim_test_SOURCES = tests/disclaim_test.c +disclaim_test_LDADD = $(test_ldadd) + +TESTS += disclaim_bench +check_PROGRAMS += disclaim_bench +disclaim_bench_SOURCES = tests/disclaim_bench.c +disclaim_bench_LDADD = $(test_ldadd) +endif diff --git a/thread_local_alloc.c b/thread_local_alloc.c index f747b35..4dd1cf2 100644 --- a/thread_local_alloc.c +++ b/thread_local_alloc.c @@ -93,6 +93,9 @@ void GC_init_thread_local(GC_tlfs p) # ifdef GC_GCJ_SUPPORT p -> gcj_freelists[i] = (void *)1; # endif +# ifdef ENABLE_DISCLAIM + p -> finalized_freelists[i] = (void *)1; +# endif } /* Set up the size 0 free lists. */ /* We now handle most of them like regular free lists, to ensure */ @@ -103,6 +106,9 @@ void GC_init_thread_local(GC_tlfs p) # ifdef GC_GCJ_SUPPORT p -> gcj_freelists[0] = ERROR_FL; # endif +# ifdef ENABLE_DISCLAIM + p -> finalized_freelists[0] = (void *)1; +# endif } #ifdef GC_GCJ_SUPPORT @@ -279,6 +285,10 @@ void GC_mark_thread_local_fls_for(GC_tlfs p) q = p -> gcj_freelists[j]; if ((word)q > HBLKSIZE) GC_set_fl_marks(q); # endif /* GC_GCJ_SUPPORT */ +# ifdef ENABLE_DISCLAIM + q = p -> finalized_freelists[j]; + if ((word)q > HBLKSIZE) GC_set_fl_marks(q); +# endif } } @@ -298,6 +308,10 @@ void GC_mark_thread_local_fls_for(GC_tlfs p) q = p -> gcj_freelists[j]; if ((word)q > HBLKSIZE) GC_check_fl_marks(q); # endif /* GC_GCJ_SUPPORT */ +# ifdef ENABLE_DISCLAIM + q = p -> finalized_freelists[j]; + if ((word)q > HBLKSIZE) GC_check_fl_marks(q); +# endif } } #endif /* GC_ASSERTIONS */