diff src/eval.c @ 371:4b9fef49d7ff v7.0095

updated for version 7.0095
author vimboss
date Mon, 27 Jun 2005 22:48:21 +0000
parents 5332dd13733c
children 575dacb554d8
line wrap: on
line diff
--- a/src/eval.c
+++ b/src/eval.c
@@ -194,10 +194,14 @@ struct ufunc
 #define DEL_REFCOUNT	999999	/* list/dict is being deleted */
 
 /*
- * All user-defined functions are found in this hash table.
+ * All user-defined functions are found in this hashtable.
  */
 static hashtab_T	func_hashtab;
 
+/* list heads for garbage collection */
+static dict_T		*first_dict = NULL;	/* list of all dicts */
+static list_T		*first_list = NULL;	/* list of all lists */
+
 /* From user function to hashitem and back. */
 static ufunc_T dumuf;
 #define UF2HIKEY(fp) ((fp)->uf_name)
@@ -212,7 +216,9 @@ static ufunc_T dumuf;
 #define FIXVAR_CNT	12	/* number of fixed variables */
 
 /* structure to hold info for a function that is currently being executed. */
-typedef struct funccall_S
+typedef struct funccall_S funccall_T;
+
+struct funccall_S
 {
     ufunc_T	*func;		/* function being called */
     int		linenr;		/* next line to be executed */
@@ -235,7 +241,8 @@ typedef struct funccall_S
 #ifdef FEAT_PROFILE
     proftime_T	prof_child;	/* time spent in a child */
 #endif
-} funccall_T;
+    funccall_T	*caller;	/* calling function or NULL */
+};
 
 /*
  * Info used by a ":for" loop.
@@ -382,10 +389,9 @@ static void list_remove __ARGS((list_T *
 static char_u *list2string __ARGS((typval_T *tv));
 static int list_join __ARGS((garray_T *gap, list_T *l, char_u *sep, int echo));
 
-static int count_self_ref __ARGS((void *p, int type));
-static int count_ref_in_dict __ARGS((dict_T *d, void *rp, int copyID, garray_T *gap));
-static int count_ref_in_list __ARGS((list_T *l, void *rp, int copyID, garray_T *gap));
-static int count_ref_item __ARGS((typval_T *tv, void *rp, int copyID, garray_T *gap));
+static void set_ref_in_ht __ARGS((hashtab_T *ht, int copyID));
+static void set_ref_in_list __ARGS((list_T *l, int copyID));
+static void set_ref_in_item __ARGS((typval_T *tv, int copyID));
 
 static void dict_unref __ARGS((dict_T *d));
 static void dict_free __ARGS((dict_T *d));
@@ -460,6 +466,7 @@ static void f_foldtext __ARGS((typval_T 
 static void f_foldtextresult __ARGS((typval_T *argvars, typval_T *rettv));
 static void f_foreground __ARGS((typval_T *argvars, typval_T *rettv));
 static void f_function __ARGS((typval_T *argvars, typval_T *rettv));
+static void f_garbagecollect __ARGS((typval_T *argvars, typval_T *rettv));
 static void f_get __ARGS((typval_T *argvars, typval_T *rettv));
 static void f_getbufvar __ARGS((typval_T *argvars, typval_T *rettv));
 static void f_getchar __ARGS((typval_T *argvars, typval_T *rettv));
@@ -749,7 +756,12 @@ eval_clear()
     /* global variables */
     vars_clear(&globvarht);
 
+    /* functions */
     free_all_functions();
+    hash_clear(&func_hashtab);
+
+    /* unreferenced lists and dicts */
+    (void)garbage_collect();
 }
 #endif
 
@@ -4945,7 +4957,19 @@ failret:
     static list_T *
 list_alloc()
 {
-    return (list_T *)alloc_clear(sizeof(list_T));
+    list_T  *l;
+
+    l = (list_T *)alloc_clear(sizeof(list_T));
+    if (l != NULL)
+    {
+	/* Prepend the list to the list of lists for garbage collection. */
+	if (first_list != NULL)
+	    first_list->lv_used_prev = l;
+	l->lv_used_prev = NULL;
+	l->lv_used_next = first_list;
+	first_list = l;
+    }
+    return l;
 }
 
 /*
@@ -4956,23 +4980,8 @@ list_alloc()
 list_unref(l)
     list_T *l;
 {
-    int		selfref;
-
-    if (l != NULL && l->lv_refcount != DEL_REFCOUNT)
-    {
-	if (--l->lv_refcount > 0)
-	{
-	    /* Check if the dict contains references to itself.  These need to
-	     * be subtracted from the reference count to find out if we can
-	     * delete the dict. */
-	    selfref = count_self_ref(l, VAR_LIST);
-	}
-	else
-	    selfref = 0;
-	if (l->lv_refcount - selfref == 0)
-	    /* No references to the list now, free it. */
-	    list_free(l);
-    }
+    if (l != NULL && l->lv_refcount != DEL_REFCOUNT && --l->lv_refcount <= 0)
+	list_free(l);
 }
 
 /*
@@ -4988,6 +4997,14 @@ list_free(l)
     /* Avoid that recursive reference to the list frees us again. */
     l->lv_refcount = DEL_REFCOUNT;
 
+    /* Remove the list from the list of lists for garbage collection. */
+    if (l->lv_used_prev == NULL)
+	first_list = l->lv_used_next;
+    else
+	l->lv_used_prev->lv_used_next = l->lv_used_next;
+    if (l->lv_used_next != NULL)
+	l->lv_used_next->lv_used_prev = l->lv_used_prev;
+
     for (item = l->lv_first; item != NULL; item = l->lv_first)
     {
 	/* Remove the item before deleting it. */
@@ -5568,157 +5585,168 @@ list_join(gap, l, sep, echo)
 }
 
 /*
- * Count the number of references for list/dict "p" inside itself.
- * This is used to find out if there are no more references elsewhere.
- * The tricky bit is that we must not count references in lists/dicts that are
- * used elsewhere, but we can only know by counting their references...
- * This is a bit slow, but required to avoid leaking memory.
- */
-    static int
-count_self_ref(p, type)
-    void	*p;
-    int		type;
-{
-    garray_T	ga;
-    typval_T	*tv;
-    int		selfref;
+ * Garbage collection for lists and dictionaries.
+ *
+ * We use reference counts to be able to free most items right away when they
+ * are no longer used.  But for composite items it's possible that it becomes
+ * unused while the reference count is > 0: When there is a recursive
+ * reference.  Example:
+ *	:let l = [1, 2, 3]
+ *	:let d = {9: l}
+ *	:let l[1] = d
+ *
+ * Since this is quite unusual we handle this with garbage collection: every
+ * once in a while find out which lists and dicts are not referenced from any
+ * variable.
+ *
+ * Here is a good reference text about garbage collection (refers to Python
+ * but it applies to all reference-counting mechanisms):
+ *	http://python.ca/nas/python/gc/
+ */
+
+/*
+ * Do garbage collection for lists and dicts.
+ * Return TRUE if some memory was freed.
+ */
+    int
+garbage_collect()
+{
+    dict_T	*dd;
+    list_T	*ll;
+    int		copyID = ++current_copyID;
+    buf_T	*buf;
+    win_T	*wp;
     int		i;
-    int		n;
-
-    ga_init2(&ga, sizeof(typval_T *), 10);
-    if (type == VAR_DICT)
-	selfref = count_ref_in_dict(p, p, ++current_copyID, &ga);
-    else
-	selfref = count_ref_in_list(p, p, ++current_copyID, &ga);
-    for (i = 0; i < ga.ga_len; ++i)
-    {
-	tv = ((typval_T **)ga.ga_data)[i];
-	if (tv->v_type == VAR_DICT)
-	{
-	    n = count_ref_in_dict(tv->vval.v_dict, tv->vval.v_dict,
-						      ++current_copyID, NULL);
-	    if (n < tv->vval.v_dict->dv_refcount)
-	    {
-		selfref = 0;
-		break;
-	    }
-	}
-	else
-	{
-	    n = count_ref_in_list(tv->vval.v_list, tv->vval.v_list,
-						      ++current_copyID, NULL);
-	    if (n < tv->vval.v_list->lv_refcount)
-	    {
-		selfref = 0;
-		break;
-	    }
-	}
-    }
-
-    ga_clear(&ga);
-    return selfref;
-}
-
-/*
- * Count number of references to "rp" in dictionary "d" and its members.
- * We use "copyID" to avoid recursing into the same list/dict twice.
- */
-    static int
-count_ref_in_dict(d, rp, copyID, gap)
-    dict_T	*d;
-    void	*rp;
+    funccall_T	*fc;
+    int		did_free = FALSE;
+
+    /*
+     * 1. Go through all accessible variables and mark all lists and dicts
+     *    with copyID.
+     */
+    /* script-local variables */
+    for (i = 1; i <= ga_scripts.ga_len; ++i)
+	set_ref_in_ht(&SCRIPT_VARS(i), copyID);
+
+    /* buffer-local variables */
+    for (buf = firstbuf; buf != NULL; buf = buf->b_next)
+	set_ref_in_ht(&buf->b_vars.dv_hashtab, copyID);
+
+    /* window-local variables */
+    FOR_ALL_WINDOWS(wp)
+	set_ref_in_ht(&wp->w_vars.dv_hashtab, copyID);
+
+    /* global variables */
+    set_ref_in_ht(&globvarht, copyID);
+
+    /* function-local variables */
+    for (fc = current_funccal; fc != NULL; fc = fc->caller)
+    {
+	set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID);
+	set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID);
+    }
+
+    /*
+     * 2. Go through the list of dicts and free items without the copyID.
+     */
+    for (dd = first_dict; dd != NULL; )
+	if (dd->dv_copyID != copyID)
+	{
+	    dict_free(dd);
+	    did_free = TRUE;
+
+	    /* restart, next dict may also have been freed */
+	    dd = first_dict;
+	}
+	else
+	    dd = dd->dv_used_next;
+
+    /*
+     * 3. Go through the list of lists and free items without the copyID.
+     */
+    for (ll = first_list; ll != NULL; )
+	if (ll->lv_copyID != copyID)
+	{
+	    list_free(ll);
+	    did_free = TRUE;
+
+	    /* restart, next dict may also have been freed */
+	    ll = first_list;
+	}
+	else
+	    ll = ll->lv_used_next;
+
+    return did_free;
+}
+
+/*
+ * Mark all lists and dicts referenced through hashtab "ht" with "copyID".
+ */
+    static void
+set_ref_in_ht(ht, copyID)
+    hashtab_T	*ht;
     int		copyID;
-    garray_T	*gap;
 {
     int		todo;
     hashitem_T	*hi;
-    int		n = 0;
-
-    todo = d->dv_hashtab.ht_used;
-    for (hi = d->dv_hashtab.ht_array; todo > 0; ++hi)
+
+    todo = ht->ht_used;
+    for (hi = ht->ht_array; todo > 0; ++hi)
 	if (!HASHITEM_EMPTY(hi))
 	{
 	    --todo;
-	    n += count_ref_item(&HI2DI(hi)->di_tv, rp, copyID, gap);
-	}
-    return n;
-}
-
-/*
- * Count number of references to "rp" in list "l" and its members.
- * We use "copyID" to avoid recursing into the same list/dict twice.
- */
-    static int
-count_ref_in_list(l, rp, copyID, gap)
+	    set_ref_in_item(&HI2DI(hi)->di_tv, copyID);
+	}
+}
+
+/*
+ * Mark all lists and dicts referenced through list "l" with "copyID".
+ */
+    static void
+set_ref_in_list(l, copyID)
     list_T	*l;
-    void	*rp;
     int		copyID;
-    garray_T	*gap;
 {
     listitem_T *li;
-    int		n = 0;
 
     for (li = l->lv_first; li != NULL; li = li->li_next)
-	n += count_ref_item(&li->li_tv, rp, copyID, gap);
-    return n;
-}
-
-/*
- * Count number of references to "rp" in item "tv" and any members.
- * We use "copyID" to avoid recursing into the same list/dict twice.
- * When "gap" is not NULL store items that require checking for only
- * references inside the structure.
- */
-    static int
-count_ref_item(tv, rp, copyID, gap)
+	set_ref_in_item(&li->li_tv, copyID);
+}
+
+/*
+ * Mark all lists and dicts referenced through typval "tv" with "copyID".
+ */
+    static void
+set_ref_in_item(tv, copyID)
     typval_T	*tv;
-    void	*rp;
     int		copyID;
-    garray_T	*gap;
 {
     dict_T	*dd;
     list_T	*ll;
-    int		n;
 
     switch (tv->v_type)
     {
 	case VAR_DICT:
 	    dd = tv->vval.v_dict;
-	    if (dd == rp)
-		return 1;	/* match, count it */
-	    if (dd->dv_copyID == copyID)
-		return 0;	/* already inspected this dict */
-	    dd->dv_copyID = copyID;
-	    n = count_ref_in_dict(dd, rp, copyID, gap);
-	    if (n > 0 && gap != NULL && dd->dv_refcount > 1)
-	    {
-		/* We must later check that the references to this dict are
-		 * all in the structure we are freeing. */
-		if (ga_grow(gap, 1) == FAIL)
-		    return 0;
-		((typval_T **)gap->ga_data)[gap->ga_len++] = tv;
-	    }
-	    return n;
+	    if (dd->dv_copyID != copyID)
+	    {
+		/* Didn't see this dict yet. */
+		dd->dv_copyID = copyID;
+		set_ref_in_ht(&dd->dv_hashtab, copyID);
+	    }
+	    break;
 
 	case VAR_LIST:
 	    ll = tv->vval.v_list;
-	    if (ll == rp)
-		return 1;	/* match, count it */
-	    if (ll->lv_copyID == copyID)
-		return 0;	/* already inspected this list */
-	    ll->lv_copyID = copyID;
-	    n = count_ref_in_list(ll, rp, copyID, gap);
-	    if (n > 0 && gap != NULL && ll->lv_refcount > 1)
-	    {
-		/* We must later check that the references to this list are
-		 * all in the structure we are freeing. */
-		if (ga_grow(gap, 1) == FAIL)
-		    return 0;
-		((typval_T **)gap->ga_data)[gap->ga_len++] = tv;
-	    }
-	    return n;
-    }
-    return 0;
+	    if (ll->lv_copyID != copyID)
+	    {
+		/* Didn't see this list yet. */
+		ll->lv_copyID = copyID;
+		set_ref_in_list(ll, copyID);
+	    }
+	    break;
+    }
+    return;
 }
 
 /*
@@ -5732,6 +5760,12 @@ dict_alloc()
     d = (dict_T *)alloc(sizeof(dict_T));
     if (d != NULL)
     {
+	/* Add the list to the hashtable for garbage collection. */
+	if (first_dict != NULL)
+	    first_dict->dv_used_prev = d;
+	d->dv_used_next = first_dict;
+	d->dv_used_prev = NULL;
+
 	hash_init(&d->dv_hashtab);
 	d->dv_lock = 0;
 	d->dv_refcount = 0;
@@ -5748,23 +5782,8 @@ dict_alloc()
 dict_unref(d)
     dict_T *d;
 {
-    int		selfref;
-
-    if (d != NULL && d->dv_refcount != DEL_REFCOUNT)
-    {
-	if (--d->dv_refcount > 0)
-	{
-	    /* Check if the dict contains references to itself.  These need to
-	     * be subtracted from the reference count to find out if we can
-	     * delete the dict. */
-	    selfref = count_self_ref(d, VAR_DICT);
-	}
-	else
-	    selfref = 0;
-	if (d->dv_refcount - selfref == 0)
-	    /* No references to the dict now, free it. */
-	    dict_free(d);
-    }
+    if (d != NULL && d->dv_refcount != DEL_REFCOUNT && --d->dv_refcount <= 0)
+	dict_free(d);
 }
 
 /*
@@ -5782,8 +5801,15 @@ dict_free(d)
     /* Avoid that recursive reference to the dict frees us again. */
     d->dv_refcount = DEL_REFCOUNT;
 
-    /* Lock the hashtab, we don't want it to resize while looping through it.
-     * */
+    /* Remove the dict from the list of dicts for garbage collection. */
+    if (d->dv_used_prev == NULL)
+	first_dict = d->dv_used_next;
+    else
+	d->dv_used_prev->dv_used_next = d->dv_used_next;
+    if (d->dv_used_next != NULL)
+	d->dv_used_next->dv_used_prev = d->dv_used_prev;
+
+    /* Lock the hashtab, we don't want it to resize while freeing items. */
     hash_lock(&d->dv_hashtab);
     todo = d->dv_hashtab.ht_used;
     for (hi = d->dv_hashtab.ht_array; todo > 0; ++hi)
@@ -6505,6 +6531,7 @@ static struct fst
     {"foldtextresult",	1, 1, f_foldtextresult},
     {"foreground",	0, 0, f_foreground},
     {"function",	1, 1, f_function},
+    {"garbagecollect",	0, 0, f_garbagecollect},
     {"get",		2, 3, f_get},
     {"getbufvar",	2, 2, f_getbufvar},
     {"getchar",		0, 1, f_getchar},
@@ -8886,6 +8913,18 @@ f_function(argvars, rettv)
 }
 
 /*
+ * "garbagecollect()" function
+ */
+/*ARGSUSED*/
+    static void
+f_garbagecollect(argvars, rettv)
+    typval_T	*argvars;
+    typval_T	*rettv;
+{
+    garbage_collect();
+}
+
+/*
  * "get()" function
  */
     static void
@@ -15506,8 +15545,10 @@ find_var_in_ht(ht, varname, writing)
 	    case 'v': return &vimvars_var;
 	    case 'b': return &curbuf->b_bufvar;
 	    case 'w': return &curwin->w_winvar;
-	    case 'l': return &current_funccal->l_vars_var;
-	    case 'a': return &current_funccal->l_avars_var;
+	    case 'l': return current_funccal == NULL
+					? NULL : &current_funccal->l_vars_var;
+	    case 'a': return current_funccal == NULL
+				       ? NULL : &current_funccal->l_avars_var;
 	}
 	return NULL;
     }
@@ -15689,6 +15730,7 @@ vars_clear_ext(ht, free_val)
 	}
     }
     hash_clear(ht);
+    ht->ht_used = 0;
 }
 
 /*
@@ -15789,7 +15831,7 @@ set_var(name, tv, copy)
 	}
 	if (function_exists(name))
 	{
-	    EMSG2(_("705: Variable name conflicts with existing function: %s"),
+	    EMSG2(_("E705: Variable name conflicts with existing function: %s"),
 									name);
 	    return;
 	}
@@ -17569,7 +17611,6 @@ call_user_func(fp, argcount, argvars, re
     linenr_T	save_sourcing_lnum;
     scid_T	save_current_SID;
     funccall_T	fc;
-    funccall_T	*save_fcp = current_funccal;
     int		save_did_emsg;
     static int	depth = 0;
     dictitem_T	*v;
@@ -17594,6 +17635,7 @@ call_user_func(fp, argcount, argvars, re
 
     line_breakcheck();		/* check for CTRL-C hit */
 
+    fc.caller = current_funccal;
     current_funccal = &fc;
     fc.func = fp;
     fc.rettv = rettv;
@@ -17752,7 +17794,7 @@ call_user_func(fp, argcount, argvars, re
 	if (!fp->uf_profiling && has_profiling(FALSE, fp->uf_name, NULL))
 	    func_do_profile(fp);
 	if (fp->uf_profiling
-		       || (save_fcp != NULL && &save_fcp->func->uf_profiling))
+		       || (fc.caller != NULL && &fc.caller->func->uf_profiling))
 	{
 	    ++fp->uf_tm_count;
 	    profile_start(&fp->uf_tm_start);
@@ -17782,17 +17824,17 @@ call_user_func(fp, argcount, argvars, re
     }
 
 #ifdef FEAT_PROFILE
-    if (fp->uf_profiling || (save_fcp != NULL && &save_fcp->func->uf_profiling))
+    if (fp->uf_profiling || (fc.caller != NULL && &fc.caller->func->uf_profiling))
     {
 	profile_end(&fp->uf_tm_start);
 	profile_sub_wait(&wait_start, &fp->uf_tm_start);
 	profile_add(&fp->uf_tm_total, &fp->uf_tm_start);
 	profile_add(&fp->uf_tm_self, &fp->uf_tm_start);
 	profile_sub(&fp->uf_tm_self, &fp->uf_tm_children);
-	if (save_fcp != NULL && &save_fcp->func->uf_profiling)
-	{
-	    profile_add(&save_fcp->func->uf_tm_children, &fp->uf_tm_start);
-	    profile_add(&save_fcp->func->uf_tml_children, &fp->uf_tm_start);
+	if (fc.caller != NULL && &fc.caller->func->uf_profiling)
+	{
+	    profile_add(&fc.caller->func->uf_tm_children, &fp->uf_tm_start);
+	    profile_add(&fc.caller->func->uf_tml_children, &fp->uf_tm_start);
 	}
     }
 #endif
@@ -17850,7 +17892,7 @@ call_user_func(fp, argcount, argvars, re
     }
 
     did_emsg |= save_did_emsg;
-    current_funccal = save_fcp;
+    current_funccal = fc.caller;
 
     /* The a: variables typevals were not alloced, only free the allocated
      * variables. */