Skip to content

Commit 457bb9a

Browse files
brammooldouglasdrumond
authored andcommitted
updated for version 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)
1 parent 7db209f commit 457bb9a

File tree

11 files changed

+226
-82
lines changed

11 files changed

+226
-82
lines changed

src/eval.c

Lines changed: 166 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,6 @@ typedef struct lval_S
9393
char_u *ll_newkey; /* New key for Dict in alloc. mem or NULL. */
9494
} lval_T;
9595

96-
9796
static char *e_letunexp = N_("E18: Unexpected characters in :let");
9897
static char *e_listidx = N_("E684: list index out of range: %ld");
9998
static char *e_undefvar = N_("E121: Undefined variable: %s");
@@ -6811,6 +6810,7 @@ list_join(gap, l, sep, echo_style, copyID)
68116810
garbage_collect()
68126811
{
68136812
int copyID;
6813+
int abort = FALSE;
68146814
buf_T *buf;
68156815
win_T *wp;
68166816
int i;
@@ -6841,82 +6841,95 @@ garbage_collect()
68416841
* the item is referenced elsewhere the funccal must not be freed. */
68426842
for (fc = previous_funccal; fc != NULL; fc = fc->caller)
68436843
{
6844-
set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID + 1);
6845-
set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID + 1);
6844+
abort = abort || set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID + 1,
6845+
NULL);
6846+
abort = abort || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID + 1,
6847+
NULL);
68466848
}
68476849

68486850
/* script-local variables */
68496851
for (i = 1; i <= ga_scripts.ga_len; ++i)
6850-
set_ref_in_ht(&SCRIPT_VARS(i), copyID);
6852+
abort = abort || set_ref_in_ht(&SCRIPT_VARS(i), copyID, NULL);
68516853

68526854
/* buffer-local variables */
68536855
for (buf = firstbuf; buf != NULL; buf = buf->b_next)
6854-
set_ref_in_item(&buf->b_bufvar.di_tv, copyID);
6856+
abort = abort || set_ref_in_item(&buf->b_bufvar.di_tv, copyID,
6857+
NULL, NULL);
68556858

68566859
/* window-local variables */
68576860
FOR_ALL_TAB_WINDOWS(tp, wp)
6858-
set_ref_in_item(&wp->w_winvar.di_tv, copyID);
6861+
abort = abort || set_ref_in_item(&wp->w_winvar.di_tv, copyID,
6862+
NULL, NULL);
68596863
#ifdef FEAT_AUTOCMD
68606864
if (aucmd_win != NULL)
6861-
set_ref_in_item(&aucmd_win->w_winvar.di_tv, copyID);
6865+
abort = abort || set_ref_in_item(&aucmd_win->w_winvar.di_tv, copyID,
6866+
NULL, NULL);
68626867
#endif
68636868

68646869
#ifdef FEAT_WINDOWS
68656870
/* tabpage-local variables */
68666871
for (tp = first_tabpage; tp != NULL; tp = tp->tp_next)
6867-
set_ref_in_item(&tp->tp_winvar.di_tv, copyID);
6872+
abort = abort || set_ref_in_item(&tp->tp_winvar.di_tv, copyID,
6873+
NULL, NULL);
68686874
#endif
68696875

68706876
/* global variables */
6871-
set_ref_in_ht(&globvarht, copyID);
6877+
abort = abort || set_ref_in_ht(&globvarht, copyID, NULL);
68726878

68736879
/* function-local variables */
68746880
for (fc = current_funccal; fc != NULL; fc = fc->caller)
68756881
{
6876-
set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID);
6877-
set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID);
6882+
abort = abort || set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID, NULL);
6883+
abort = abort || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID, NULL);
68786884
}
68796885

68806886
/* v: vars */
6881-
set_ref_in_ht(&vimvarht, copyID);
6887+
abort = abort || set_ref_in_ht(&vimvarht, copyID, NULL);
68826888

68836889
#ifdef FEAT_LUA
6884-
set_ref_in_lua(copyID);
6890+
abort = abort || set_ref_in_lua(copyID);
68856891
#endif
68866892

68876893
#ifdef FEAT_PYTHON
6888-
set_ref_in_python(copyID);
6894+
abort = abort || set_ref_in_python(copyID);
68896895
#endif
68906896

68916897
#ifdef FEAT_PYTHON3
6892-
set_ref_in_python3(copyID);
6898+
abort = abort || set_ref_in_python3(copyID);
68936899
#endif
68946900

6895-
/*
6896-
* 2. Free lists and dictionaries that are not referenced.
6897-
*/
6898-
did_free = free_unref_items(copyID);
6899-
6900-
/*
6901-
* 3. Check if any funccal can be freed now.
6902-
*/
6903-
for (pfc = &previous_funccal; *pfc != NULL; )
6901+
if (!abort)
69046902
{
6905-
if (can_free_funccal(*pfc, copyID))
6903+
/*
6904+
* 2. Free lists and dictionaries that are not referenced.
6905+
*/
6906+
did_free = free_unref_items(copyID);
6907+
6908+
/*
6909+
* 3. Check if any funccal can be freed now.
6910+
*/
6911+
for (pfc = &previous_funccal; *pfc != NULL; )
69066912
{
6907-
fc = *pfc;
6908-
*pfc = fc->caller;
6909-
free_funccal(fc, TRUE);
6910-
did_free = TRUE;
6911-
did_free_funccal = TRUE;
6913+
if (can_free_funccal(*pfc, copyID))
6914+
{
6915+
fc = *pfc;
6916+
*pfc = fc->caller;
6917+
free_funccal(fc, TRUE);
6918+
did_free = TRUE;
6919+
did_free_funccal = TRUE;
6920+
}
6921+
else
6922+
pfc = &(*pfc)->caller;
69126923
}
6913-
else
6914-
pfc = &(*pfc)->caller;
6924+
if (did_free_funccal)
6925+
/* When a funccal was freed some more items might be garbage
6926+
* collected, so run again. */
6927+
(void)garbage_collect();
6928+
}
6929+
else if (p_verbose > 0)
6930+
{
6931+
verb_msg((char_u *)_("Not enough memory to set references, garbage collection aborted!"));
69156932
}
6916-
if (did_free_funccal)
6917-
/* When a funccal was freed some more items might be garbage
6918-
* collected, so run again. */
6919-
(void)garbage_collect();
69206933

69216934
return did_free;
69226935
}
@@ -6976,48 +6989,112 @@ free_unref_items(copyID)
69766989

69776990
/*
69786991
* Mark all lists and dicts referenced through hashtab "ht" with "copyID".
6992+
* "list_stack" is used to add lists to be marked. Can be NULL.
6993+
*
6994+
* Returns TRUE if setting references failed somehow.
69796995
*/
6980-
void
6981-
set_ref_in_ht(ht, copyID)
6982-
hashtab_T *ht;
6983-
int copyID;
6996+
int
6997+
set_ref_in_ht(ht, copyID, list_stack)
6998+
hashtab_T *ht;
6999+
int copyID;
7000+
list_stack_T **list_stack;
69847001
{
69857002
int todo;
7003+
int abort = FALSE;
69867004
hashitem_T *hi;
7005+
hashtab_T *cur_ht;
7006+
ht_stack_T *ht_stack = NULL;
7007+
ht_stack_T *tempitem;
69877008

6988-
todo = (int)ht->ht_used;
6989-
for (hi = ht->ht_array; todo > 0; ++hi)
6990-
if (!HASHITEM_EMPTY(hi))
7009+
cur_ht = ht;
7010+
for (;;)
7011+
{
7012+
if (!abort)
69917013
{
6992-
--todo;
6993-
set_ref_in_item(&HI2DI(hi)->di_tv, copyID);
7014+
/* Mark each item in the hashtab. If the item contains a hashtab
7015+
* it is added to ht_stack, if it contains a list it is added to
7016+
* list_stack. */
7017+
todo = (int)cur_ht->ht_used;
7018+
for (hi = cur_ht->ht_array; todo > 0; ++hi)
7019+
if (!HASHITEM_EMPTY(hi))
7020+
{
7021+
--todo;
7022+
abort = abort || set_ref_in_item(&HI2DI(hi)->di_tv, copyID,
7023+
&ht_stack, list_stack);
7024+
}
69947025
}
7026+
7027+
if (ht_stack == NULL)
7028+
break;
7029+
7030+
/* take an item from the stack */
7031+
cur_ht = ht_stack->ht;
7032+
tempitem = ht_stack;
7033+
ht_stack = ht_stack->prev;
7034+
free(tempitem);
7035+
}
7036+
7037+
return abort;
69957038
}
69967039

69977040
/*
69987041
* Mark all lists and dicts referenced through list "l" with "copyID".
7042+
* "ht_stack" is used to add hashtabs to be marked. Can be NULL.
7043+
*
7044+
* Returns TRUE if setting references failed somehow.
69997045
*/
7000-
void
7001-
set_ref_in_list(l, copyID)
7046+
int
7047+
set_ref_in_list(l, copyID, ht_stack)
70027048
list_T *l;
70037049
int copyID;
7050+
ht_stack_T **ht_stack;
70047051
{
7005-
listitem_T *li;
7052+
listitem_T *li;
7053+
int abort = FALSE;
7054+
list_T *cur_l;
7055+
list_stack_T *list_stack = NULL;
7056+
list_stack_T *tempitem;
70067057

7007-
for (li = l->lv_first; li != NULL; li = li->li_next)
7008-
set_ref_in_item(&li->li_tv, copyID);
7058+
cur_l = l;
7059+
for (;;)
7060+
{
7061+
if (!abort)
7062+
/* Mark each item in the list. If the item contains a hashtab
7063+
* it is added to ht_stack, if it contains a list it is added to
7064+
* list_stack. */
7065+
for (li = cur_l->lv_first; !abort && li != NULL; li = li->li_next)
7066+
abort = abort || set_ref_in_item(&li->li_tv, copyID,
7067+
ht_stack, &list_stack);
7068+
if (list_stack == NULL)
7069+
break;
7070+
7071+
/* take an item from the stack */
7072+
cur_l = list_stack->list;
7073+
tempitem = list_stack;
7074+
list_stack = list_stack->prev;
7075+
free(tempitem);
7076+
}
7077+
7078+
return abort;
70097079
}
70107080

70117081
/*
70127082
* Mark all lists and dicts referenced through typval "tv" with "copyID".
7083+
* "list_stack" is used to add lists to be marked. Can be NULL.
7084+
* "ht_stack" is used to add hashtabs to be marked. Can be NULL.
7085+
*
7086+
* Returns TRUE if setting references failed somehow.
70137087
*/
7014-
void
7015-
set_ref_in_item(tv, copyID)
7016-
typval_T *tv;
7017-
int copyID;
7088+
int
7089+
set_ref_in_item(tv, copyID, ht_stack, list_stack)
7090+
typval_T *tv;
7091+
int copyID;
7092+
ht_stack_T **ht_stack;
7093+
list_stack_T **list_stack;
70187094
{
70197095
dict_T *dd;
70207096
list_T *ll;
7097+
int abort = FALSE;
70217098

70227099
switch (tv->v_type)
70237100
{
@@ -7027,7 +7104,23 @@ set_ref_in_item(tv, copyID)
70277104
{
70287105
/* Didn't see this dict yet. */
70297106
dd->dv_copyID = copyID;
7030-
set_ref_in_ht(&dd->dv_hashtab, copyID);
7107+
if (ht_stack == NULL)
7108+
{
7109+
abort = set_ref_in_ht(&dd->dv_hashtab, copyID, list_stack);
7110+
}
7111+
else
7112+
{
7113+
ht_stack_T *newitem = (ht_stack_T*)malloc(
7114+
sizeof(ht_stack_T));
7115+
if (newitem == NULL)
7116+
abort = TRUE;
7117+
else
7118+
{
7119+
newitem->ht = &dd->dv_hashtab;
7120+
newitem->prev = *ht_stack;
7121+
*ht_stack = newitem;
7122+
}
7123+
}
70317124
}
70327125
break;
70337126

@@ -7037,11 +7130,27 @@ set_ref_in_item(tv, copyID)
70377130
{
70387131
/* Didn't see this list yet. */
70397132
ll->lv_copyID = copyID;
7040-
set_ref_in_list(ll, copyID);
7133+
if (list_stack == NULL)
7134+
{
7135+
abort = set_ref_in_list(ll, copyID, ht_stack);
7136+
}
7137+
else
7138+
{
7139+
list_stack_T *newitem = (list_stack_T*)malloc(
7140+
sizeof(list_stack_T));
7141+
if (newitem == NULL)
7142+
abort = TRUE;
7143+
else
7144+
{
7145+
newitem->list = ll;
7146+
newitem->prev = *list_stack;
7147+
*list_stack = newitem;
7148+
}
7149+
}
70417150
}
70427151
break;
70437152
}
7044-
return;
7153+
return abort;
70457154
}
70467155

70477156
/*

src/if_lua.c

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1523,12 +1523,14 @@ luaV_luaeval (lua_State *L)
15231523
static int
15241524
luaV_setref (lua_State *L)
15251525
{
1526-
int copyID = lua_tointeger(L, 1);
1527-
typval_T tv;
1526+
int copyID = lua_tointeger(L, 1);
1527+
int abort = FALSE;
1528+
typval_T tv;
1529+
15281530
luaV_getfield(L, LUAVIM_LIST);
15291531
luaV_getfield(L, LUAVIM_DICT);
15301532
lua_pushnil(L);
1531-
while (lua_next(L, lua_upvalueindex(1)) != 0) /* traverse cache table */
1533+
while (!abort && lua_next(L, lua_upvalueindex(1)) != 0) /* traverse cache table */
15321534
{
15331535
lua_getmetatable(L, -1);
15341536
if (lua_rawequal(L, -1, 2)) /* list? */
@@ -1542,9 +1544,9 @@ luaV_setref (lua_State *L)
15421544
tv.vval.v_dict = (dict_T *) lua_touserdata(L, 4); /* key */
15431545
}
15441546
lua_pop(L, 2); /* metatable and value */
1545-
set_ref_in_item(&tv, copyID);
1547+
abort = set_ref_in_item(&tv, copyID, NULL, NULL);
15461548
}
1547-
return 0;
1549+
lua_pushinteger(L, abort);
15481550
}
15491551

15501552
static int
@@ -1770,13 +1772,23 @@ do_luaeval (char_u *str, typval_T *arg, typval_T *rettv)
17701772
lua_call(L, 3, 0);
17711773
}
17721774

1773-
void
1775+
int
17741776
set_ref_in_lua (int copyID)
17751777
{
1776-
if (!lua_isopen()) return;
1777-
luaV_getfield(L, LUAVIM_SETREF);
1778-
lua_pushinteger(L, copyID);
1779-
lua_call(L, 1, 0);
1778+
int aborted = 0;
1779+
1780+
if (lua_isopen())
1781+
{
1782+
luaV_getfield(L, LUAVIM_SETREF);
1783+
/* call the function with 1 arg, getting 1 result back */
1784+
lua_pushinteger(L, copyID);
1785+
lua_call(L, 1, 1);
1786+
/* get the result */
1787+
aborted = lua_tointeger(L, -1);
1788+
/* pop result off the stack */
1789+
lua_pop(L, 1);
1790+
}
1791+
return aborted;
17801792
}
17811793

17821794
#endif

0 commit comments

Comments
 (0)