vim-patch:7.4.609

Problem:    For complicated list and dict use the garbage collector can run
            out of stack space.
Solution:   Use a stack of dicts and lists to be marked, thus making it
            iterative instead of recursive. (Ben Fritz)

2459a5ecaa
This commit is contained in:
oni-link 2015-09-04 13:30:02 +02:00
parent bb46cc2c9c
commit 6ea21f5668
3 changed files with 202 additions and 107 deletions

View File

@ -5570,96 +5570,97 @@ static int list_join(garray_T *gap, list_T *l, char_u *sep, int echo_style, int
* http://python.ca/nas/python/gc/ * http://python.ca/nas/python/gc/
*/ */
/* /// Do garbage collection for lists and dicts.
* Do garbage collection for lists and dicts. ///
* Return TRUE if some memory was freed. /// @returns true if some memory was freed.
*/ bool garbage_collect(void)
int garbage_collect(void)
{ {
int copyID; bool abort = false;
funccall_T *fc, **pfc;
int did_free;
int did_free_funccal = FALSE;
/* Only do this once. */ // Only do this once.
want_garbage_collect = FALSE; want_garbage_collect = false;
may_garbage_collect = FALSE; may_garbage_collect = false;
garbage_collect_at_exit = FALSE; garbage_collect_at_exit = false;
/* We advance by two because we add one for items referenced through // We advance by two because we add one for items referenced through
* previous_funccal. */ // previous_funccal.
current_copyID += COPYID_INC; current_copyID += COPYID_INC;
copyID = current_copyID; int copyID = current_copyID;
/* // 1. Go through all accessible variables and mark all lists and dicts
* 1. Go through all accessible variables and mark all lists and dicts // with copyID.
* with copyID.
*/
/* Don't free variables in the previous_funccal list unless they are only // Don't free variables in the previous_funccal list unless they are only
* referenced through previous_funccal. This must be first, because if // referenced through previous_funccal. This must be first, because if
* the item is referenced elsewhere the funccal must not be freed. */ // the item is referenced elsewhere the funccal must not be freed.
for (fc = previous_funccal; fc != NULL; fc = fc->caller) { for (funccall_T *fc = previous_funccal; fc != NULL; fc = fc->caller) {
set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID + 1); abort = abort || set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID + 1, NULL);
set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID + 1); abort = abort || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID + 1, NULL);
} }
/* script-local variables */ // script-local variables
for (int i = 1; i <= ga_scripts.ga_len; ++i) for (int i = 1; i <= ga_scripts.ga_len; ++i) {
set_ref_in_ht(&SCRIPT_VARS(i), copyID); abort = abort || set_ref_in_ht(&SCRIPT_VARS(i), copyID, NULL);
}
/* buffer-local variables */ // buffer-local variables
FOR_ALL_BUFFERS(buf) { FOR_ALL_BUFFERS(buf) {
set_ref_in_item(&buf->b_bufvar.di_tv, copyID); abort = abort || set_ref_in_item(&buf->b_bufvar.di_tv, copyID, NULL, NULL);
} }
/* window-local variables */ // window-local variables
FOR_ALL_TAB_WINDOWS(tp, wp) { FOR_ALL_TAB_WINDOWS(tp, wp) {
set_ref_in_item(&wp->w_winvar.di_tv, copyID); abort = abort || set_ref_in_item(&wp->w_winvar.di_tv, copyID, NULL, NULL);
}
if (aucmd_win != NULL) {
abort = abort ||
set_ref_in_item(&aucmd_win->w_winvar.di_tv, copyID, NULL, NULL);
} }
if (aucmd_win != NULL)
set_ref_in_item(&aucmd_win->w_winvar.di_tv, copyID);
/* tabpage-local variables */ // tabpage-local variables
FOR_ALL_TABS(tp) { FOR_ALL_TABS(tp) {
set_ref_in_item(&tp->tp_winvar.di_tv, copyID); abort = abort || set_ref_in_item(&tp->tp_winvar.di_tv, copyID, NULL, NULL);
} }
/* global variables */ // global variables
set_ref_in_ht(&globvarht, copyID); abort = abort || set_ref_in_ht(&globvarht, copyID, NULL);
/* function-local variables */ // function-local variables
for (fc = current_funccal; fc != NULL; fc = fc->caller) { for (funccall_T *fc = current_funccal; fc != NULL; fc = fc->caller) {
set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID); abort = abort || set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID, NULL);
set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID); abort = abort || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID, NULL);
} }
/* v: vars */ // v: vars
set_ref_in_ht(&vimvarht, copyID); abort = abort || set_ref_in_ht(&vimvarht, copyID, NULL);
/* bool did_free = false;
* 2. Free lists and dictionaries that are not referenced. if (!abort) {
*/ // 2. Free lists and dictionaries that are not referenced.
did_free = free_unref_items(copyID); did_free = free_unref_items(copyID);
/* // 3. Check if any funccal can be freed now.
* 3. Check if any funccal can be freed now. bool did_free_funccal = false;
*/ for (funccall_T **pfc = &previous_funccal; *pfc != NULL;) {
for (pfc = &previous_funccal; *pfc != NULL; ) { if (can_free_funccal(*pfc, copyID)) {
if (can_free_funccal(*pfc, copyID)) { funccall_T *fc = *pfc;
fc = *pfc; *pfc = fc->caller;
*pfc = fc->caller; free_funccal(fc, true);
free_funccal(fc, TRUE); did_free = true;
did_free = TRUE; did_free_funccal = true;
did_free_funccal = TRUE; } else {
} else pfc = &(*pfc)->caller;
pfc = &(*pfc)->caller; }
}
if (did_free_funccal) {
// When a funccal was freed some more items might be garbage
// collected, so run again.
(void)garbage_collect();
}
} else if (p_verbose > 0) {
verb_msg((char_u *)_(
"Not enough memory to set references, garbage collection aborted!"));
} }
if (did_free_funccal)
/* When a funccal was freed some more items might be garbage
* collected, so run again. */
(void)garbage_collect();
return did_free; return did_free;
} }
@ -5711,61 +5712,143 @@ static int free_unref_items(int copyID)
return did_free; return did_free;
} }
/* /// Mark all lists and dicts referenced through hashtab "ht" with "copyID".
* Mark all lists and dicts referenced through hashtab "ht" with "copyID". ///
*/ /// @param ht Hashtab content will be marked.
void set_ref_in_ht(hashtab_T *ht, int copyID) /// @param copyID New mark for lists and dicts.
/// @param list_stack Used to add lists to be marked. Can be NULL.
///
/// @returns true if setting references failed somehow.
bool set_ref_in_ht(hashtab_T *ht, int copyID, list_stack_T **list_stack)
{ {
int todo; bool abort = false;
hashitem_T *hi; ht_stack_T *ht_stack = NULL;
todo = (int)ht->ht_used; hashtab_T *cur_ht = ht;
for (hi = ht->ht_array; todo > 0; ++hi) for (;;) {
if (!HASHITEM_EMPTY(hi)) { if (!abort) {
--todo; // Mark each item in the hashtab. If the item contains a hashtab
set_ref_in_item(&HI2DI(hi)->di_tv, copyID); // it is added to ht_stack, if it contains a list it is added to
// list_stack.
int todo = (int)cur_ht->ht_used;
for (hashitem_T *hi = cur_ht->ht_array; todo > 0; ++hi) {
if (!HASHITEM_EMPTY(hi)) {
--todo;
abort = abort || set_ref_in_item(&HI2DI(hi)->di_tv, copyID, &ht_stack,
list_stack);
}
}
} }
if (ht_stack == NULL) {
break;
}
// take an item from the stack
cur_ht = ht_stack->ht;
ht_stack_T *tempitem = ht_stack;
ht_stack = ht_stack->prev;
xfree(tempitem);
}
return abort;
} }
/* /// Mark all lists and dicts referenced through list "l" with "copyID".
* Mark all lists and dicts referenced through list "l" with "copyID". ///
*/ /// @param l List content will be marked.
void set_ref_in_list(list_T *l, int copyID) /// @param copyID New mark for lists and dicts.
/// @param ht_stack Used to add hashtabs to be marked. Can be NULL.
///
/// @returns true if setting references failed somehow.
bool set_ref_in_list(list_T *l, int copyID, ht_stack_T **ht_stack)
{ {
listitem_T *li; bool abort = false;
list_stack_T *list_stack = NULL;
for (li = l->lv_first; li != NULL; li = li->li_next) list_T *cur_l = l;
set_ref_in_item(&li->li_tv, copyID); for (;;) {
if (!abort) {
// Mark each item in the list. If the item contains a hashtab
// it is added to ht_stack, if it contains a list it is added to
// list_stack.
for (listitem_T *li = cur_l->lv_first; !abort && li != NULL;
li = li->li_next) {
abort = set_ref_in_item(&li->li_tv, copyID, ht_stack, &list_stack);
}
}
if (list_stack == NULL) {
break;
}
// take an item from the stack
cur_l = list_stack->list;
list_stack_T *tempitem = list_stack;
list_stack = list_stack->prev;
xfree(tempitem);
}
return abort;
} }
/* /// Mark all lists and dicts referenced through typval "tv" with "copyID".
* Mark all lists and dicts referenced through typval "tv" with "copyID". ///
*/ /// @param tv Typval content will be marked.
void set_ref_in_item(typval_T *tv, int copyID) /// @param copyID New mark for lists and dicts.
/// @param ht_stack Used to add hashtabs to be marked. Can be NULL.
/// @param list_stack Used to add lists to be marked. Can be NULL.
///
/// @returns true if setting references failed somehow.
bool set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack,
list_stack_T **list_stack)
{ {
dict_T *dd; bool abort = false;
list_T *ll;
switch (tv->v_type) { switch (tv->v_type) {
case VAR_DICT: case VAR_DICT: {
dd = tv->vval.v_dict; dict_T *dd = tv->vval.v_dict;
if (dd != NULL && dd->dv_copyID != copyID) { if (dd != NULL && dd->dv_copyID != copyID) {
/* Didn't see this dict yet. */ // Didn't see this dict yet.
dd->dv_copyID = copyID; dd->dv_copyID = copyID;
set_ref_in_ht(&dd->dv_hashtab, copyID); if (ht_stack == NULL) {
abort = set_ref_in_ht(&dd->dv_hashtab, copyID, list_stack);
} else {
ht_stack_T *newitem = try_malloc(sizeof(ht_stack_T));
if (newitem == NULL) {
abort = true;
} else {
newitem->ht = &dd->dv_hashtab;
newitem->prev = *ht_stack;
*ht_stack = newitem;
}
}
}
break;
} }
break;
case VAR_LIST: case VAR_LIST: {
ll = tv->vval.v_list; list_T *ll = tv->vval.v_list;
if (ll != NULL && ll->lv_copyID != copyID) { if (ll != NULL && ll->lv_copyID != copyID) {
/* Didn't see this list yet. */ // Didn't see this list yet.
ll->lv_copyID = copyID; ll->lv_copyID = copyID;
set_ref_in_list(ll, copyID); if (list_stack == NULL) {
abort = set_ref_in_list(ll, copyID, ht_stack);
} else {
list_stack_T *newitem = try_malloc(sizeof(list_stack_T));
if (newitem == NULL) {
abort = true;
} else {
newitem->list = ll;
newitem->prev = *list_stack;
*list_stack = newitem;
}
}
}
break;
} }
break;
} }
return; return abort;
} }
/* /*

View File

@ -120,4 +120,16 @@ struct dictvar_S {
// prevent garbage collection // prevent garbage collection
}; };
#endif // NVIM_EVAL_DEFS_H // structure used for explicit stack while garbage collecting hash tables
typedef struct ht_stack_S {
hashtab_T *ht;
struct ht_stack_S *prev;
} ht_stack_T;
// structure used for explicit stack while garbage collecting lists
typedef struct list_stack_S {
list_T *list;
struct list_stack_S *prev;
} list_stack_T;
#endif // NVIM_EVAL_DEFS_H

View File

@ -312,7 +312,7 @@ static int included_patches[] = {
// 612, // 612,
// 611 NA // 611 NA
// 610 NA // 610 NA
// 609, 609,
// 608, // 608,
// 607, // 607,
606, 606,