diff --git a/lib/modules/Pike.pmod/module.pmod b/lib/modules/Pike.pmod/module.pmod index b85b304f5c162863de90cef2d462e1c0859ab35d..5fcf4aa65cc33ba162dd08e99561e62a17d3bed6 100644 --- a/lib/modules/Pike.pmod/module.pmod +++ b/lib/modules/Pike.pmod/module.pmod @@ -74,6 +74,7 @@ constant DefaultBackend = __builtin.__backend; constant gc_parameters = __builtin.gc_parameters; constant implicit_gc_real_time = __builtin.implicit_gc_real_time; constant count_memory = __builtin.count_memory; +constant identify_cycle = __builtin.identify_cycle; constant get_runtime_info = __builtin.get_runtime_info; diff --git a/src/builtin_functions.c b/src/builtin_functions.c index f69d060cc16a14e0db65b9b05e5e92918dc297d8..47e14d40b18187ec9e74cb6393c7e322f5dcdd55 100644 --- a/src/builtin_functions.c +++ b/src/builtin_functions.c @@ -10340,6 +10340,11 @@ void init_builtin_efuns(void) tOr8(tArray,tMultiset,tMapping,tObj,tPrg(tObj), tString,tType(tMix),tInt), tInt), 0); + ADD_FUNCTION("identify_cycle", f_identify_cycle, + tFunc(tOr7(tArray,tMultiset,tMapping,tObj,tPrg(tObj), + tString,tType(tMix)), + tArr(tOr7(tArray,tMultiset,tMapping,tObj,tPrg(tObj), + tString,tType(tMix)))), 0); ADD_INT_CONSTANT ("NATIVE_INT_MAX", MAX_INT_TYPE, 0); ADD_INT_CONSTANT ("NATIVE_INT_MIN", MIN_INT_TYPE, 0); diff --git a/src/gc.c b/src/gc.c index a03b55592752fd2530bcc71c7892834fc065e562..1937138a8eae0a2184b84929aaa86addc0a26786 100644 --- a/src/gc.c +++ b/src/gc.c @@ -27,6 +27,7 @@ struct callback *gc_evaluator_callback=0; #include "pike_threadlib.h" #include "gc.h" #include "main.h" +#include "builtin_functions.h" #include "block_allocator.h" #include <math.h> @@ -6051,3 +6052,163 @@ void f_count_memory (INT32 args) pop_n_elems (args); push_ulongest (return_count ? count_internal : mc_counted_bytes); } + +static struct mapping *identify_loop_reverse = NULL; + +void identify_loop_visit_enter(void *thing, int type, void *extra) +{ + if (type < T_VOID) { + /* Valid svalue type. */ + SET_SVAL(*Pike_sp, type, 0, refs, thing); + add_ref(((struct array *)thing)); + Pike_sp++; + } +} + +void identify_loop_visit_ref(void *dst, int ref_type, + visit_thing_fn *visit_dst, + void *extra) +{ + int type = type_from_visit_fn(visit_dst); + struct mc_marker *ref_to = find_mc_marker(dst); + if (ref_to) { + /* Already visited. */ + return; + } + + if (type != PIKE_T_UNKNOWN) { + struct svalue s; + SET_SVAL(s, type, 0, refs, dst); + low_mapping_insert(identify_loop_reverse, &s, Pike_sp-1, 0); + } + + ref_to = my_make_mc_marker(dst, visit_dst, extra); + mc_wq_enqueue(ref_to); +} + +void identify_loop_visit_leave(void *thing, int type, void *extra) +{ + if (type < T_VOID) { + /* Valid svalue type. */ + pop_stack(); + } +} + +/*! @decl array(mixed) identify_cycle(mixed x) + *! + *! Identify reference cycles in Pike datastructures. + *! + *! @returns + *! Returns @expr{UNDEFINED@} if @[x] is not member of a reference cycle. + *! Otherwise returns an array identifying a cycle with @[x] as the first + *! element, and where the elements refer to each other in order, and the + *! last element refers to the first. + */ +void f_identify_cycle(INT32 args) +{ + struct svalue *s; + struct mc_marker *m; + struct svalue *k; + + if (args < 1) { + SIMPLE_TOO_FEW_ARGS_ERROR("identify_loops", 1); + } + + if (args > 1) pop_n_elems(args-1); + args = 1; + + s = Pike_sp - 1; + + if (!REFCOUNTED_TYPE(TYPEOF(*s))) { + SIMPLE_ARG_TYPE_ERROR("identify_loops", 1, + "array|multiset|mapping|object|program|string|type"); + } + if (TYPEOF(*s) == T_FUNCTION) { + if (SUBTYPEOF(*s) == FUNCTION_BUILTIN) { + SIMPLE_ARG_TYPE_ERROR("identify_loops", 1, + "array|multiset|mapping|object|program|string|type"); + } + SET_SVAL_TYPE(*s, T_OBJECT); + } + + init_mc_marker_hash(); + + if (TYPEOF(pike_cycle_depth_str) == PIKE_T_FREE) { + SET_SVAL_TYPE(pike_cycle_depth_str, T_STRING); + MAKE_CONST_STRING (pike_cycle_depth_str.u.string, "pike_cycle_depth"); + } + + assert (mc_work_queue == NULL); + mc_work_queue = malloc (MC_WQ_START_SIZE * sizeof (mc_work_queue[0])); + if (!mc_work_queue) { + exit_mc_marker_hash(); + SIMPLE_OUT_OF_MEMORY_ERROR ("Pike.count_memory", + MC_WQ_START_SIZE * sizeof (mc_work_queue[0])); + } + mc_work_queue--; + mc_wq_size = MC_WQ_START_SIZE; + mc_wq_used = 1; + mc_lookahead = -1; + + assert (!mc_pass); + assert (visit_enter == NULL); + assert (visit_ref == NULL); + assert (visit_leave == NULL); + + /* There's a fair chance of there being lots of stuff being referenced, + * so preallocate a reasonable initial size. + */ + identify_loop_reverse = allocate_mapping(1024); + + visit_enter = identify_loop_visit_enter; + visit_ref = identify_loop_visit_ref; + visit_leave = identify_loop_visit_leave; + + /* NB: This initial call will botstrap the wq_queue. */ + visit_fn_from_type[TYPEOF(*s)](s->u.ptr, VISIT_COMPLEX_ONLY, NULL); + + while ((mc_ref_from = mc_wq_dequeue())) { + if (mc_ref_from->flags & MC_FLAG_INT_VISITED) continue; + + mc_ref_from->flags |= MC_FLAG_INT_VISITED; + mc_ref_from->visit_fn(mc_ref_from->thing, VISIT_COMPLEX_ONLY, NULL); + } + + exit_mc_marker_hash(); + free (mc_work_queue + 1); + mc_work_queue = NULL; + + visit_enter = NULL; + visit_ref = NULL; + visit_leave = NULL; + +#ifdef PIKE_DEBUG + if (s != Pike_sp-1) { + Pike_fatal("Stack error in identify_loops.\n"); + } +#endif + + while ((k = low_mapping_lookup(identify_loop_reverse, Pike_sp-1))) { + /* NB: Since we entered this loop, we know that there's a + * reference loop involving s, as s otherwise wouldn't + * have been in the mapping. + */ + push_svalue(k); + if (k->u.refs == s->u.refs) { + /* Found! */ + break; + } + } + + free_mapping(identify_loop_reverse); + + if (!k) { + push_undefined(); + } else { + /* NB: We push s an extra time last above, to simplify the + * reversing below. + */ + f_aggregate(Pike_sp - (s + 1)); + f_reverse(1); + } +} diff --git a/src/gc.h b/src/gc.h index f9786cfd3acc816972ddc4bf3f05ff5561569d2f..00bbd5b2ed5b0edcac15996dcc87a1ecab75d3e7 100644 --- a/src/gc.h +++ b/src/gc.h @@ -355,6 +355,7 @@ size_t do_gc(void *ignored, int explicit_call); void f__gc_status(INT32 args); void f_implicit_gc_real_time (INT32 args); void f_count_memory (INT32 args); +void f_identify_cycle(INT32 args); void cleanup_gc(void); #if defined (PIKE_DEBUG) && defined (DEBUG_MALLOC)