From: Petter Urkedal Date: Sun, 1 Jul 2007 11:48:00 +0000 (+0200) Subject: Add disclaim callbacks for efficient finalization. X-Git-Tag: gc7_3alpha2~320^2~23 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=6c1a924;p=gc Add disclaim callbacks for efficient finalization. Importing gc-20070403-disclaim-1.0.patch. * configure.ac: Add --disable-disclaim option and set ENABLE_DISCLAIM macro and conditional accordingly. * include/include.am, Makefile.am Add sources disclaim.c and include/gc_disclaim.h. * Makefile.direct: Ditto and define ENABLE_DISCLAIM. * include/private/gc_priv.h (obj_kind): Add ok_mark_unconditionally, ok_disclaim_proc and ok_disclaim_cd. * misc.c (GC_new_kind_inner): Initialize them. * include/private/gc_priv.h (hblkhdr): Add HAS_DISCLAIM and MARK_UNCONDITIONALLY flags. * allchblk.c (setup_header): Set HAS_DISCLAIM and MARK_UNCONDITIONALLY flags as indicated by the object kind. * reclaim.c: Main adjustments to support the disclaim callbacks. * mark.c (GC_push_unconditionally, GC_push_next_marked_uncollecable): Unconditionally mark from objects in blocks flagged MARK_UNCONDITIONALLY. This preserves links reachable from the finalizer when using the the finalized object kind. * disclaim.c: Add file with support functions for disclaim callbacks and implementation of the "finalized" object kind. * include/gc_disclaim.h: Add file providing the corresponding API. * include/private/thread_local_alloc.h (thread_local_freelists): Add finalized_freelists. * thread_local_alloc.c: Initialize them. * tests/tests.am, tests/disclaim_test.c, tests/disclaim_bench.c: Add disclaim-related tests. --- diff --git a/Makefile.am b/Makefile.am index 940b8bce..aac9d235 100644 --- a/Makefile.am +++ b/Makefile.am @@ -72,6 +72,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 = libatomic_ops/src/atomic_ops.c endif diff --git a/Makefile.direct b/Makefile.direct index 0cf9426c..85a1e239 100644 --- a/Makefile.direct +++ b/Makefile.direct @@ -36,7 +36,7 @@ VPATH= $(srcdir) AO_SRC_DIR=$(srcdir)/libatomic_ops 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 @@ -74,14 +74,16 @@ 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 pthread_start.o thread_local_alloc.o + gc_dlopen.o backgraph.o win32_threads.o pthread_start.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 pthread_start.c thread_local_alloc.c + backgraph.c win32_threads.c pthread_start.c thread_local_alloc.c \ + disclaim.c CORD_SRCS= cord/cordbscs.c cord/cordxtra.c cord/cordprnt.c cord/tests/de.c \ cord/tests/cordtest.c include/cord.h include/ec.h \ @@ -101,7 +103,7 @@ SRCS= $(CSRCS) \ include/javaxfc.h \ include/gc_backptr.h include/gc_gcj.h include/private/dbg_mlc.h \ include/private/specific.h include/leak_detector.h \ - include/gc_pthread_redirects.h \ + include/gc_pthread_redirects.h include/gc_disclaim.h \ include/gc_config_macros.h include/private/pthread_support.h \ include/private/pthread_stop_world.h include/private/darwin_semaphore.h \ include/private/darwin_stop_world.h include/private/thread_local_alloc.h \ diff --git a/allchblk.c b/allchblk.c index 77e036a9..fbe6dd7e 100644 --- a/allchblk.c +++ b/allchblk.c @@ -225,6 +225,12 @@ static GC_bool setup_header(hdr * hhdr, struct hblk *block, size_t byte_sz, # ifndef MARK_BIT_PER_OBJ size_t granules; # endif +# 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 6cd63ae0..6c70cd2a 100644 --- a/configure.ac +++ b/configure.ac @@ -696,6 +696,16 @@ if test "${enable_redirect_malloc}" = yes; then AC_DEFINE([GC_USE_DLOPEN_WRAP], 1, [See doc/README.macros.]) 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 00000000..e61ddb0d --- /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 00000000..c67a9ce1 --- /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 a793a6f4..a3c64d58 100644 --- a/include/include.am +++ b/include/include.am @@ -29,6 +29,11 @@ pkginclude_HEADERS += \ include/gc_tiny_fl.h \ include/gc_version.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 8e78cb8b..77020f16 100644 --- a/include/private/gc_priv.h +++ b/include/private/gc_priv.h @@ -814,6 +814,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. */ @@ -1224,6 +1232,18 @@ GC_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 68d6d88b..ad790a28 100644 --- a/include/private/thread_local_alloc.h +++ b/include/private/thread_local_alloc.h @@ -88,6 +88,9 @@ typedef struct thread_local_freelists { # define DIRECT_GRANULES (HBLKSIZE/GRANULE_BYTES) /* Don't use local free lists for up to this much */ /* allocation. */ +# ifdef ENABLE_DISCLAIM + void * finalized_freelists[TINY_FREELISTS]; +# endif } *GC_tlfs; #if defined(USE_PTHREAD_SPECIFIC) diff --git a/mark.c b/mark.c index f7b7437e..a8773610 100644 --- a/mark.c +++ b/mark.c @@ -1759,6 +1759,36 @@ STATIC 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 GC_DISABLE_INCREMENTAL /* Test whether any page in the given block is dirty. */ STATIC GC_bool GC_block_was_dirty(struct hblk *h, hdr *hhdr) @@ -1839,10 +1869,18 @@ STATIC 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 21554afe..25a5cde3 100644 --- a/misc.c +++ b/misc.c @@ -1474,6 +1474,10 @@ GC_API unsigned GC_CALL GC_new_kind_inner(void **fl, GC_word descr, 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 3d4a81d1..f4be205d 100644 --- a/reclaim.c +++ b/reclaim.c @@ -15,6 +15,7 @@ */ #include "private/gc_priv.h" +#include "gc_disclaim.h" #include @@ -117,6 +118,11 @@ GC_INNER void GC_print_all_errors(void) /* objects. This does not require the block to be in physical memory. */ GC_INNER 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); } @@ -206,6 +212,56 @@ STATIC 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: */ STATIC void GC_reclaim_check(struct hblk *hbp, hdr *hhdr, word sz) { @@ -237,6 +293,11 @@ GC_INNER ptr_t GC_reclaim_generic(struct hblk * hbp, hdr *hhdr, size_t sz, GC_ASSERT(GC_find_header((ptr_t)hbp) == hhdr); # ifndef GC_DISABLE_INCREMENTAL GC_remove_protection(hbp, 1, (hhdr)->hb_descr == 0 /* Pointer-free? */); +# endif +# ifdef ENABLE_DISCLAIM + if (hhdr -> hb_flags & HAS_DISCLAIM) + result = GC_disclaim_and_reclaim(hbp, hhdr, sz, list, count); + else # endif if (init || GC_debugging_started) { result = GC_reclaim_clear(hbp, hhdr, sz, list, count); @@ -293,6 +354,17 @@ STATIC 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; } @@ -300,6 +372,7 @@ STATIC 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 00000000..af2375eb --- /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 00000000..bc8462a5 --- /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 13647b3a..f7985c9a 100644 --- a/tests/tests.am +++ b/tests/tests.am @@ -91,3 +91,15 @@ else test_cpp_LDADD = libgccpp.la $(test_ldadd) endif 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 610db6fd..ac351734 100644 --- a/thread_local_alloc.c +++ b/thread_local_alloc.c @@ -97,6 +97,9 @@ GC_INNER void GC_init_thread_local(GC_tlfs p) p -> normal_freelists[i] = (void *)(word)1; # ifdef GC_GCJ_SUPPORT p -> gcj_freelists[i] = (void *)(word)1; +# endif +# ifdef ENABLE_DISCLAIM + p -> finalized_freelists[i] = (void *)1; # endif } /* Set up the size 0 free lists. */ @@ -108,6 +111,9 @@ GC_INNER 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 } /* We hold the allocator lock. */ @@ -280,6 +286,10 @@ GC_INNER void GC_mark_thread_local_fls_for(GC_tlfs p) 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 } } @@ -299,6 +309,10 @@ GC_INNER 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 */