Mercurial > vim
changeset 35241:500731fe8161 v9.1.0431
patch 9.1.0431: eval.c is too long
Commit: https://github.com/vim/vim/commit/25536f415eab5a90d68b3c689c66a72803cd8e5d
Author: Yegappan Lakshmanan <yegappan@yahoo.com>
Date: Wed May 22 16:45:04 2024 +0200
patch 9.1.0431: eval.c is too long
Problem: eval.c is too long
Solution: Move garbage collection code to new gc.c file
(Yegappan Lakshmanan)
closes: #14824
Signed-off-by: Yegappan Lakshmanan <yegappan@yahoo.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
author | Christian Brabandt <cb@256bit.org> |
---|---|
date | Wed, 22 May 2024 17:00:04 +0200 |
parents | fbccf48852ad |
children | 3b7ee87c9e79 |
files | Filelist src/Make_ami.mak src/Make_cyg_ming.mak src/Make_mvc.mak src/Make_vms.mms src/Makefile src/README.md src/eval.c src/gc.c src/list.c src/proto.h src/proto/eval.pro src/proto/gc.pro src/proto/list.pro src/version.c |
diffstat | 15 files changed, 846 insertions(+), 795 deletions(-) [+] |
line wrap: on
line diff
--- a/Filelist +++ b/Filelist @@ -73,6 +73,7 @@ SRC_ALL = \ src/float.c \ src/fold.c \ src/getchar.c \ + src/gc.c \ src/globals.h \ src/gui.c \ src/gui.h \ @@ -264,6 +265,7 @@ SRC_ALL = \ src/proto/float.pro \ src/proto/fold.pro \ src/proto/getchar.pro \ + src/proto/gc.pro \ src/proto/gui.pro \ src/proto/gui_beval.pro \ src/proto/hardcopy.pro \
--- a/src/Make_ami.mak +++ b/src/Make_ami.mak @@ -114,6 +114,7 @@ SRC += \ float.c \ fold.c \ getchar.c \ + gc.c \ hardcopy.c \ hashtab.c \ help.c \
--- a/src/Make_cyg_ming.mak +++ b/src/Make_cyg_ming.mak @@ -797,6 +797,7 @@ OBJ = \ $(OUTDIR)/float.o \ $(OUTDIR)/fold.o \ $(OUTDIR)/getchar.o \ + $(OUTDIR)/gc.o \ $(OUTDIR)/gui_xim.o \ $(OUTDIR)/hardcopy.o \ $(OUTDIR)/hashtab.o \
--- a/src/Make_mvc.mak +++ b/src/Make_mvc.mak @@ -718,6 +718,7 @@ OBJ = \ $(OUTDIR)\float.obj \ $(OUTDIR)\fold.obj \ $(OUTDIR)\getchar.obj \ + $(OUTDIR)\gc.obj \ $(OUTDIR)\gui_xim.obj \ $(OUTDIR)\hardcopy.obj \ $(OUTDIR)\hashtab.obj \ @@ -1575,6 +1576,8 @@ test_vim9: $(OUTDIR)/getchar.obj: $(OUTDIR) getchar.c $(INCL) +$(OUTDIR)/gc.obj: $(OUTDIR) gc.c $(INCL) + $(OUTDIR)/gui_xim.obj: $(OUTDIR) gui_xim.c $(INCL) $(OUTDIR)/hardcopy.obj: $(OUTDIR) hardcopy.c $(INCL) version.h @@ -1915,6 +1918,7 @@ proto.h: \ proto/findfile.pro \ proto/float.pro \ proto/getchar.pro \ + proto/gc.pro \ proto/gui_xim.pro \ proto/hardcopy.pro \ proto/hashtab.pro \
--- a/src/Make_vms.mms +++ b/src/Make_vms.mms @@ -369,6 +369,7 @@ SRC = \ float.c \ fold.c \ getchar.c \ + gc.c \ gui_xim.c \ hardcopy.c \ hashtab.c \ @@ -500,6 +501,7 @@ OBJ = \ float.obj \ fold.obj \ getchar.obj \ + gc.obj \ gui_xim.obj \ hardcopy.obj \ hashtab.obj \ @@ -942,6 +944,10 @@ getchar.obj : getchar.c vim.h [.auto]con ascii.h keymap.h termdefs.h macros.h structs.h regexp.h \ gui.h beval.h [.proto]gui_beval.pro option.h ex_cmds.h proto.h \ errors.h globals.h +gc.obj : gc.c vim.h [.auto]config.h feature.h os_unix.h \ + ascii.h keymap.h termdefs.h macros.h structs.h regexp.h \ + gui.h beval.h [.proto]gui_beval.pro option.h ex_cmds.h proto.h \ + errors.h globals.h gui_xim.obj : gui_xim.c vim.h [.auto]config.h feature.h os_unix.h \ ascii.h keymap.h termdefs.h macros.h structs.h regexp.h \ gui.h beval.h [.proto]gui_beval.pro option.h ex_cmds.h proto.h \
--- a/src/Makefile +++ b/src/Makefile @@ -1520,6 +1520,7 @@ BASIC_SRC = \ float.c \ fold.c \ getchar.c \ + gc.c \ gui_xim.c \ hardcopy.c \ hashtab.c \ @@ -1682,6 +1683,7 @@ OBJ_COMMON = \ objects/float.o \ objects/fold.o \ objects/getchar.o \ + objects/gc.o \ objects/gui_xim.o \ objects/hardcopy.o \ objects/hashtab.o \ @@ -1860,6 +1862,7 @@ PRO_AUTO = \ float.pro \ fold.pro \ getchar.pro \ + gc.pro \ gui_xim.pro \ gui_beval.pro \ hardcopy.pro \ @@ -3224,6 +3227,9 @@ objects/fold.o: fold.c objects/getchar.o: getchar.c $(CCC) -o $@ getchar.c +objects/gc.o: gc.c + $(CCC) -o $@ gc.c + objects/hardcopy.o: hardcopy.c $(CCC) -o $@ hardcopy.c @@ -3875,6 +3881,11 @@ objects/getchar.o: getchar.c vim.h proto proto/gui_beval.pro structs.h regexp.h gui.h libvterm/include/vterm.h \ libvterm/include/vterm_keycodes.h alloc.h ex_cmds.h spell.h proto.h \ globals.h errors.h +objects/gc.o: gc.c vim.h protodef.h auto/config.h feature.h os_unix.h \ + auto/osdef.h ascii.h keymap.h termdefs.h macros.h option.h beval.h \ + proto/gui_beval.pro structs.h regexp.h gui.h libvterm/include/vterm.h \ + libvterm/include/vterm_keycodes.h alloc.h ex_cmds.h spell.h proto.h \ + globals.h errors.h objects/gui_xim.o: gui_xim.c vim.h protodef.h auto/config.h feature.h os_unix.h \ auto/osdef.h ascii.h keymap.h termdefs.h macros.h option.h beval.h \ proto/gui_beval.pro structs.h regexp.h gui.h libvterm/include/vterm.h \
--- a/src/README.md +++ b/src/README.md @@ -49,6 +49,7 @@ filepath.c | dealing with file names and findfile.c | search for files in 'path' fold.c | folding getchar.c | getting characters and key mapping +gc.c | garbage collection help.c | vim help related functions highlight.c | syntax highlighting indent.c | text indentation
--- a/src/eval.c +++ b/src/eval.c @@ -22,13 +22,6 @@ #define NAMESPACE_CHAR (char_u *)"abglstvw" -/* - * When recursively copying lists and dicts we need to remember which ones we - * have done to avoid endless recursiveness. This unique ID is used for that. - * The last bit is used for previous_funccal, ignored when comparing. - */ -static int current_copyID = 0; - static int eval2(char_u **arg, typval_T *rettv, evalarg_T *evalarg); static int eval3(char_u **arg, typval_T *rettv, evalarg_T *evalarg); static int eval4(char_u **arg, typval_T *rettv, evalarg_T *evalarg); @@ -39,7 +32,6 @@ static int eval8(char_u **arg, typval_T static int eval9(char_u **arg, typval_T *rettv, evalarg_T *evalarg, int want_string); static int eval9_leader(typval_T *rettv, int numeric_only, char_u *start_leader, char_u **end_leaderp); -static int free_unref_items(int copyID); static char_u *make_expanded_name(char_u *in_start, char_u *expr_start, char_u *expr_end, char_u *in_end); /* @@ -5424,30 +5416,6 @@ check_can_index(typval_T *rettv, int eva } /* - * slice() function - */ - void -f_slice(typval_T *argvars, typval_T *rettv) -{ - if (in_vim9script() - && ((argvars[0].v_type != VAR_STRING - && argvars[0].v_type != VAR_LIST - && argvars[0].v_type != VAR_BLOB - && check_for_list_arg(argvars, 0) == FAIL) - || check_for_number_arg(argvars, 1) == FAIL - || check_for_opt_number_arg(argvars, 2) == FAIL)) - return; - - if (check_can_index(&argvars[0], TRUE, FALSE) != OK) - return; - - copy_tv(argvars, rettv); - eval_index_inner(rettv, TRUE, argvars + 1, - argvars[2].v_type == VAR_UNKNOWN ? NULL : argvars + 2, - TRUE, NULL, 0, FALSE); -} - -/* * Apply index or range to "rettv". * "var1" is the first index, NULL for [:expr]. * "var2" is the second index, NULL for [expr] and [expr: ] @@ -5697,759 +5665,6 @@ partial_unref(partial_T *pt) } /* - * Return the next (unique) copy ID. - * Used for serializing nested structures. - */ - int -get_copyID(void) -{ - current_copyID += COPYID_INC; - return current_copyID; -} - -/* - * 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. - * When "testing" is TRUE this is called from test_garbagecollect_now(). - * Return TRUE if some memory was freed. - */ - int -garbage_collect(int testing) -{ - int copyID; - int abort = FALSE; - buf_T *buf; - win_T *wp; - int did_free = FALSE; - tabpage_T *tp; - - if (!testing) - { - // Only do this once. - want_garbage_collect = FALSE; - may_garbage_collect = FALSE; - garbage_collect_at_exit = FALSE; - } - - // The execution stack can grow big, limit the size. - if (exestack.ga_maxlen - exestack.ga_len > 500) - { - size_t new_len; - char_u *pp; - int n; - - // Keep 150% of the current size, with a minimum of the growth size. - n = exestack.ga_len / 2; - if (n < exestack.ga_growsize) - n = exestack.ga_growsize; - - // Don't make it bigger though. - if (exestack.ga_len + n < exestack.ga_maxlen) - { - new_len = (size_t)exestack.ga_itemsize * (exestack.ga_len + n); - pp = vim_realloc(exestack.ga_data, new_len); - if (pp == NULL) - return FAIL; - exestack.ga_maxlen = exestack.ga_len + n; - exestack.ga_data = pp; - } - } - - // We advance by two because we add one for items referenced through - // previous_funccal. - copyID = get_copyID(); - - /* - * 1. Go through all accessible variables and mark all lists and dicts - * with copyID. - */ - - // Don't free variables in the previous_funccal list unless they are only - // referenced through previous_funccal. This must be first, because if - // the item is referenced elsewhere the funccal must not be freed. - abort = abort || set_ref_in_previous_funccal(copyID); - - // script-local variables - abort = abort || garbage_collect_scriptvars(copyID); - - // buffer-local variables - FOR_ALL_BUFFERS(buf) - abort = abort || set_ref_in_item(&buf->b_bufvar.di_tv, copyID, - NULL, NULL); - - // window-local variables - FOR_ALL_TAB_WINDOWS(tp, wp) - abort = abort || set_ref_in_item(&wp->w_winvar.di_tv, copyID, - NULL, NULL); - // window-local variables in autocmd windows - for (int i = 0; i < AUCMD_WIN_COUNT; ++i) - if (aucmd_win[i].auc_win != NULL) - abort = abort || set_ref_in_item( - &aucmd_win[i].auc_win->w_winvar.di_tv, copyID, NULL, NULL); -#ifdef FEAT_PROP_POPUP - FOR_ALL_POPUPWINS(wp) - abort = abort || set_ref_in_item(&wp->w_winvar.di_tv, copyID, - NULL, NULL); - FOR_ALL_TABPAGES(tp) - FOR_ALL_POPUPWINS_IN_TAB(tp, wp) - abort = abort || set_ref_in_item(&wp->w_winvar.di_tv, copyID, - NULL, NULL); -#endif - - // tabpage-local variables - FOR_ALL_TABPAGES(tp) - abort = abort || set_ref_in_item(&tp->tp_winvar.di_tv, copyID, - NULL, NULL); - // global variables - abort = abort || garbage_collect_globvars(copyID); - - // function-local variables - abort = abort || set_ref_in_call_stack(copyID); - - // named functions (matters for closures) - abort = abort || set_ref_in_functions(copyID); - - // function call arguments, if v:testing is set. - abort = abort || set_ref_in_func_args(copyID); - - // funcstacks keep variables for closures - abort = abort || set_ref_in_funcstacks(copyID); - - // loopvars keep variables for loop blocks - abort = abort || set_ref_in_loopvars(copyID); - - // v: vars - abort = abort || garbage_collect_vimvars(copyID); - - // callbacks in buffers - abort = abort || set_ref_in_buffers(copyID); - - // 'completefunc', 'omnifunc' and 'thesaurusfunc' callbacks - abort = abort || set_ref_in_insexpand_funcs(copyID); - - // 'operatorfunc' callback - abort = abort || set_ref_in_opfunc(copyID); - - // 'tagfunc' callback - abort = abort || set_ref_in_tagfunc(copyID); - - // 'imactivatefunc' and 'imstatusfunc' callbacks - abort = abort || set_ref_in_im_funcs(copyID); - -#ifdef FEAT_LUA - abort = abort || set_ref_in_lua(copyID); -#endif - -#ifdef FEAT_PYTHON - abort = abort || set_ref_in_python(copyID); -#endif - -#ifdef FEAT_PYTHON3 - abort = abort || set_ref_in_python3(copyID); -#endif - -#ifdef FEAT_JOB_CHANNEL - abort = abort || set_ref_in_channel(copyID); - abort = abort || set_ref_in_job(copyID); -#endif -#ifdef FEAT_NETBEANS_INTG - abort = abort || set_ref_in_nb_channel(copyID); -#endif - -#ifdef FEAT_TIMERS - abort = abort || set_ref_in_timer(copyID); -#endif - -#ifdef FEAT_QUICKFIX - abort = abort || set_ref_in_quickfix(copyID); -#endif - -#ifdef FEAT_TERMINAL - abort = abort || set_ref_in_term(copyID); -#endif - -#ifdef FEAT_PROP_POPUP - abort = abort || set_ref_in_popups(copyID); -#endif - - abort = abort || set_ref_in_classes(copyID); - - if (!abort) - { - /* - * 2. Free lists and dictionaries that are not referenced. - */ - did_free = free_unref_items(copyID); - - /* - * 3. Check if any funccal can be freed now. - * This may call us back recursively. - */ - free_unref_funccal(copyID, testing); - } - else if (p_verbose > 0) - { - verb_msg(_("Not enough memory to set references, garbage collection aborted!")); - } - - return did_free; -} - -/* - * Free lists, dictionaries, channels and jobs that are no longer referenced. - */ - static int -free_unref_items(int copyID) -{ - int did_free = FALSE; - - // Let all "free" functions know that we are here. This means no - // dictionaries, lists, channels or jobs are to be freed, because we will - // do that here. - in_free_unref_items = TRUE; - - /* - * PASS 1: free the contents of the items. We don't free the items - * themselves yet, so that it is possible to decrement refcount counters - */ - - // Go through the list of dicts and free items without this copyID. - did_free |= dict_free_nonref(copyID); - - // Go through the list of lists and free items without this copyID. - did_free |= list_free_nonref(copyID); - - // Go through the list of objects and free items without this copyID. - did_free |= object_free_nonref(copyID); - - // Go through the list of classes and free items without this copyID. - did_free |= class_free_nonref(copyID); - -#ifdef FEAT_JOB_CHANNEL - // Go through the list of jobs and free items without the copyID. This - // must happen before doing channels, because jobs refer to channels, but - // the reference from the channel to the job isn't tracked. - did_free |= free_unused_jobs_contents(copyID, COPYID_MASK); - - // Go through the list of channels and free items without the copyID. - did_free |= free_unused_channels_contents(copyID, COPYID_MASK); -#endif - - /* - * PASS 2: free the items themselves. - */ - object_free_items(copyID); - dict_free_items(copyID); - list_free_items(copyID); - -#ifdef FEAT_JOB_CHANNEL - // Go through the list of jobs and free items without the copyID. This - // must happen before doing channels, because jobs refer to channels, but - // the reference from the channel to the job isn't tracked. - free_unused_jobs(copyID, COPYID_MASK); - - // Go through the list of channels and free items without the copyID. - free_unused_channels(copyID, COPYID_MASK); -#endif - - in_free_unref_items = FALSE; - - return did_free; -} - -/* - * Mark all lists and dicts referenced through hashtab "ht" with "copyID". - * "list_stack" is used to add lists to be marked. Can be NULL. - * - * Returns TRUE if setting references failed somehow. - */ - int -set_ref_in_ht(hashtab_T *ht, int copyID, list_stack_T **list_stack) -{ - int todo; - int abort = FALSE; - hashitem_T *hi; - hashtab_T *cur_ht; - ht_stack_T *ht_stack = NULL; - ht_stack_T *tempitem; - - cur_ht = ht; - for (;;) - { - if (!abort) - { - // Mark each item in the hashtab. If the item contains a hashtab - // it is added to ht_stack, if it contains a list it is added to - // list_stack. - todo = (int)cur_ht->ht_used; - FOR_ALL_HASHTAB_ITEMS(cur_ht, hi, todo) - 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; - tempitem = ht_stack; - ht_stack = ht_stack->prev; - free(tempitem); - } - - return abort; -} - -#if defined(FEAT_LUA) || defined(FEAT_PYTHON) || defined(FEAT_PYTHON3) \ - || defined(PROTO) -/* - * Mark a dict and its items with "copyID". - * Returns TRUE if setting references failed somehow. - */ - int -set_ref_in_dict(dict_T *d, int copyID) -{ - if (d != NULL && d->dv_copyID != copyID) - { - d->dv_copyID = copyID; - return set_ref_in_ht(&d->dv_hashtab, copyID, NULL); - } - return FALSE; -} -#endif - -/* - * Mark a list and its items with "copyID". - * Returns TRUE if setting references failed somehow. - */ - int -set_ref_in_list(list_T *ll, int copyID) -{ - if (ll != NULL && ll->lv_copyID != copyID) - { - ll->lv_copyID = copyID; - return set_ref_in_list_items(ll, copyID, NULL); - } - return FALSE; -} - -/* - * Mark all lists and dicts referenced through list "l" with "copyID". - * "ht_stack" is used to add hashtabs to be marked. Can be NULL. - * - * Returns TRUE if setting references failed somehow. - */ - int -set_ref_in_list_items(list_T *l, int copyID, ht_stack_T **ht_stack) -{ - listitem_T *li; - int abort = FALSE; - list_T *cur_l; - list_stack_T *list_stack = NULL; - list_stack_T *tempitem; - - cur_l = l; - for (;;) - { - if (!abort && cur_l->lv_first != &range_list_item) - // 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 (li = cur_l->lv_first; !abort && li != NULL; li = li->li_next) - abort = 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; - tempitem = list_stack; - list_stack = list_stack->prev; - free(tempitem); - } - - return abort; -} - -/* - * Mark the partial in callback 'cb' with "copyID". - */ - int -set_ref_in_callback(callback_T *cb, int copyID) -{ - typval_T tv; - - if (cb->cb_name == NULL || *cb->cb_name == NUL || cb->cb_partial == NULL) - return FALSE; - - tv.v_type = VAR_PARTIAL; - tv.vval.v_partial = cb->cb_partial; - return set_ref_in_item(&tv, copyID, NULL, NULL); -} - -/* - * Mark the dict "dd" with "copyID". - * Also see set_ref_in_item(). - */ - static int -set_ref_in_item_dict( - dict_T *dd, - int copyID, - ht_stack_T **ht_stack, - list_stack_T **list_stack) -{ - if (dd == NULL || dd->dv_copyID == copyID) - return FALSE; - - // Didn't see this dict yet. - dd->dv_copyID = copyID; - if (ht_stack == NULL) - return set_ref_in_ht(&dd->dv_hashtab, copyID, list_stack); - - ht_stack_T *newitem = ALLOC_ONE(ht_stack_T); - if (newitem == NULL) - return TRUE; - - newitem->ht = &dd->dv_hashtab; - newitem->prev = *ht_stack; - *ht_stack = newitem; - - return FALSE; -} - -/* - * Mark the list "ll" with "copyID". - * Also see set_ref_in_item(). - */ - static int -set_ref_in_item_list( - list_T *ll, - int copyID, - ht_stack_T **ht_stack, - list_stack_T **list_stack) -{ - if (ll == NULL || ll->lv_copyID == copyID) - return FALSE; - - // Didn't see this list yet. - ll->lv_copyID = copyID; - if (list_stack == NULL) - return set_ref_in_list_items(ll, copyID, ht_stack); - - list_stack_T *newitem = ALLOC_ONE(list_stack_T); - if (newitem == NULL) - return TRUE; - - newitem->list = ll; - newitem->prev = *list_stack; - *list_stack = newitem; - - return FALSE; -} - -/* - * Mark the partial "pt" with "copyID". - * Also see set_ref_in_item(). - */ - static int -set_ref_in_item_partial( - partial_T *pt, - int copyID, - ht_stack_T **ht_stack, - list_stack_T **list_stack) -{ - if (pt == NULL || pt->pt_copyID == copyID) - return FALSE; - - // Didn't see this partial yet. - pt->pt_copyID = copyID; - - int abort = set_ref_in_func(pt->pt_name, pt->pt_func, copyID); - - if (pt->pt_dict != NULL) - { - typval_T dtv; - - dtv.v_type = VAR_DICT; - dtv.vval.v_dict = pt->pt_dict; - set_ref_in_item(&dtv, copyID, ht_stack, list_stack); - } - - if (pt->pt_obj != NULL) - { - typval_T objtv; - - objtv.v_type = VAR_OBJECT; - objtv.vval.v_object = pt->pt_obj; - set_ref_in_item(&objtv, copyID, ht_stack, list_stack); - } - - for (int i = 0; i < pt->pt_argc; ++i) - abort = abort || set_ref_in_item(&pt->pt_argv[i], copyID, - ht_stack, list_stack); - // pt_funcstack is handled in set_ref_in_funcstacks() - // pt_loopvars is handled in set_ref_in_loopvars() - - return abort; -} - -#ifdef FEAT_JOB_CHANNEL -/* - * Mark the job "pt" with "copyID". - * Also see set_ref_in_item(). - */ - static int -set_ref_in_item_job( - job_T *job, - int copyID, - ht_stack_T **ht_stack, - list_stack_T **list_stack) -{ - typval_T dtv; - - if (job == NULL || job->jv_copyID == copyID) - return FALSE; - - job->jv_copyID = copyID; - if (job->jv_channel != NULL) - { - dtv.v_type = VAR_CHANNEL; - dtv.vval.v_channel = job->jv_channel; - set_ref_in_item(&dtv, copyID, ht_stack, list_stack); - } - if (job->jv_exit_cb.cb_partial != NULL) - { - dtv.v_type = VAR_PARTIAL; - dtv.vval.v_partial = job->jv_exit_cb.cb_partial; - set_ref_in_item(&dtv, copyID, ht_stack, list_stack); - } - - return FALSE; -} - -/* - * Mark the channel "ch" with "copyID". - * Also see set_ref_in_item(). - */ - static int -set_ref_in_item_channel( - channel_T *ch, - int copyID, - ht_stack_T **ht_stack, - list_stack_T **list_stack) -{ - typval_T dtv; - - if (ch == NULL || ch->ch_copyID == copyID) - return FALSE; - - ch->ch_copyID = copyID; - for (ch_part_T part = PART_SOCK; part < PART_COUNT; ++part) - { - for (jsonq_T *jq = ch->ch_part[part].ch_json_head.jq_next; - jq != NULL; jq = jq->jq_next) - set_ref_in_item(jq->jq_value, copyID, ht_stack, list_stack); - for (cbq_T *cq = ch->ch_part[part].ch_cb_head.cq_next; cq != NULL; - cq = cq->cq_next) - if (cq->cq_callback.cb_partial != NULL) - { - dtv.v_type = VAR_PARTIAL; - dtv.vval.v_partial = cq->cq_callback.cb_partial; - set_ref_in_item(&dtv, copyID, ht_stack, list_stack); - } - if (ch->ch_part[part].ch_callback.cb_partial != NULL) - { - dtv.v_type = VAR_PARTIAL; - dtv.vval.v_partial = ch->ch_part[part].ch_callback.cb_partial; - set_ref_in_item(&dtv, copyID, ht_stack, list_stack); - } - } - if (ch->ch_callback.cb_partial != NULL) - { - dtv.v_type = VAR_PARTIAL; - dtv.vval.v_partial = ch->ch_callback.cb_partial; - set_ref_in_item(&dtv, copyID, ht_stack, list_stack); - } - if (ch->ch_close_cb.cb_partial != NULL) - { - dtv.v_type = VAR_PARTIAL; - dtv.vval.v_partial = ch->ch_close_cb.cb_partial; - set_ref_in_item(&dtv, copyID, ht_stack, list_stack); - } - - return FALSE; -} -#endif - -/* - * Mark the class "cl" with "copyID". - * Also see set_ref_in_item(). - */ - int -set_ref_in_item_class( - class_T *cl, - int copyID, - ht_stack_T **ht_stack, - list_stack_T **list_stack) -{ - int abort = FALSE; - - if (cl == NULL || cl->class_copyID == copyID) - return FALSE; - - cl->class_copyID = copyID; - if (cl->class_members_tv != NULL) - { - // The "class_members_tv" table is allocated only for regular classes - // and not for interfaces. - for (int i = 0; !abort && i < cl->class_class_member_count; ++i) - abort = abort || set_ref_in_item( - &cl->class_members_tv[i], - copyID, ht_stack, list_stack); - } - - for (int i = 0; !abort && i < cl->class_class_function_count; ++i) - abort = abort || set_ref_in_func(NULL, - cl->class_class_functions[i], copyID); - - for (int i = 0; !abort && i < cl->class_obj_method_count; ++i) - abort = abort || set_ref_in_func(NULL, - cl->class_obj_methods[i], copyID); - - return abort; -} - -/* - * Mark the object "cl" with "copyID". - * Also see set_ref_in_item(). - */ - static int -set_ref_in_item_object( - object_T *obj, - int copyID, - ht_stack_T **ht_stack, - list_stack_T **list_stack) -{ - int abort = FALSE; - - if (obj == NULL || obj->obj_copyID == copyID) - return FALSE; - - obj->obj_copyID = copyID; - - // The typval_T array is right after the object_T. - typval_T *mtv = (typval_T *)(obj + 1); - for (int i = 0; !abort - && i < obj->obj_class->class_obj_member_count; ++i) - abort = abort || set_ref_in_item(mtv + i, copyID, - ht_stack, list_stack); - - return abort; -} - -/* - * Mark all lists, dicts and other container types referenced through typval - * "tv" with "copyID". - * "list_stack" is used to add lists to be marked. Can be NULL. - * "ht_stack" is used to add hashtabs to be marked. Can be NULL. - * - * Returns TRUE if setting references failed somehow. - */ - int -set_ref_in_item( - typval_T *tv, - int copyID, - ht_stack_T **ht_stack, - list_stack_T **list_stack) -{ - int abort = FALSE; - - switch (tv->v_type) - { - case VAR_DICT: - return set_ref_in_item_dict(tv->vval.v_dict, copyID, - ht_stack, list_stack); - - case VAR_LIST: - return set_ref_in_item_list(tv->vval.v_list, copyID, - ht_stack, list_stack); - - case VAR_FUNC: - { - abort = set_ref_in_func(tv->vval.v_string, NULL, copyID); - break; - } - - case VAR_PARTIAL: - return set_ref_in_item_partial(tv->vval.v_partial, copyID, - ht_stack, list_stack); - - case VAR_JOB: -#ifdef FEAT_JOB_CHANNEL - return set_ref_in_item_job(tv->vval.v_job, copyID, - ht_stack, list_stack); -#else - break; -#endif - - case VAR_CHANNEL: -#ifdef FEAT_JOB_CHANNEL - return set_ref_in_item_channel(tv->vval.v_channel, copyID, - ht_stack, list_stack); -#else - break; -#endif - - case VAR_CLASS: - return set_ref_in_item_class(tv->vval.v_class, copyID, - ht_stack, list_stack); - - case VAR_OBJECT: - return set_ref_in_item_object(tv->vval.v_object, copyID, - ht_stack, list_stack); - - case VAR_UNKNOWN: - case VAR_ANY: - case VAR_VOID: - case VAR_BOOL: - case VAR_SPECIAL: - case VAR_NUMBER: - case VAR_FLOAT: - case VAR_STRING: - case VAR_BLOB: - case VAR_TYPEALIAS: - case VAR_INSTR: - // Types that do not contain any other item - break; - } - - return abort; -} - -/* * Return a textual representation of a string in "tv". * If the memory is allocated "tofree" is set to it, otherwise NULL. * When both "echo_style" and "composite_val" are FALSE, put quotes around
new file mode 100644 --- /dev/null +++ b/src/gc.c @@ -0,0 +1,780 @@ +/* vi:set ts=8 sts=4 sw=4 noet: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + * See README.txt for an overview of the Vim source code. + */ + +/* + * gc.c: Garbage Collection + */ + +#include "vim.h" + +#if defined(FEAT_EVAL) || defined(PROTO) + +/* + * When recursively copying lists and dicts we need to remember which ones we + * have done to avoid endless recursiveness. This unique ID is used for that. + * The last bit is used for previous_funccal, ignored when comparing. + */ +static int current_copyID = 0; + +static int free_unref_items(int copyID); + +/* + * Return the next (unique) copy ID. + * Used for serializing nested structures. + */ + int +get_copyID(void) +{ + current_copyID += COPYID_INC; + return current_copyID; +} + +/* + * 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. + * When "testing" is TRUE this is called from test_garbagecollect_now(). + * Return TRUE if some memory was freed. + */ + int +garbage_collect(int testing) +{ + int copyID; + int abort = FALSE; + buf_T *buf; + win_T *wp; + int did_free = FALSE; + tabpage_T *tp; + + if (!testing) + { + // Only do this once. + want_garbage_collect = FALSE; + may_garbage_collect = FALSE; + garbage_collect_at_exit = FALSE; + } + + // The execution stack can grow big, limit the size. + if (exestack.ga_maxlen - exestack.ga_len > 500) + { + size_t new_len; + char_u *pp; + int n; + + // Keep 150% of the current size, with a minimum of the growth size. + n = exestack.ga_len / 2; + if (n < exestack.ga_growsize) + n = exestack.ga_growsize; + + // Don't make it bigger though. + if (exestack.ga_len + n < exestack.ga_maxlen) + { + new_len = (size_t)exestack.ga_itemsize * (exestack.ga_len + n); + pp = vim_realloc(exestack.ga_data, new_len); + if (pp == NULL) + return FAIL; + exestack.ga_maxlen = exestack.ga_len + n; + exestack.ga_data = pp; + } + } + + // We advance by two because we add one for items referenced through + // previous_funccal. + copyID = get_copyID(); + + /* + * 1. Go through all accessible variables and mark all lists and dicts + * with copyID. + */ + + // Don't free variables in the previous_funccal list unless they are only + // referenced through previous_funccal. This must be first, because if + // the item is referenced elsewhere the funccal must not be freed. + abort = abort || set_ref_in_previous_funccal(copyID); + + // script-local variables + abort = abort || garbage_collect_scriptvars(copyID); + + // buffer-local variables + FOR_ALL_BUFFERS(buf) + abort = abort || set_ref_in_item(&buf->b_bufvar.di_tv, copyID, + NULL, NULL); + + // window-local variables + FOR_ALL_TAB_WINDOWS(tp, wp) + abort = abort || set_ref_in_item(&wp->w_winvar.di_tv, copyID, + NULL, NULL); + // window-local variables in autocmd windows + for (int i = 0; i < AUCMD_WIN_COUNT; ++i) + if (aucmd_win[i].auc_win != NULL) + abort = abort || set_ref_in_item( + &aucmd_win[i].auc_win->w_winvar.di_tv, copyID, NULL, NULL); +#ifdef FEAT_PROP_POPUP + FOR_ALL_POPUPWINS(wp) + abort = abort || set_ref_in_item(&wp->w_winvar.di_tv, copyID, + NULL, NULL); + FOR_ALL_TABPAGES(tp) + FOR_ALL_POPUPWINS_IN_TAB(tp, wp) + abort = abort || set_ref_in_item(&wp->w_winvar.di_tv, copyID, + NULL, NULL); +#endif + + // tabpage-local variables + FOR_ALL_TABPAGES(tp) + abort = abort || set_ref_in_item(&tp->tp_winvar.di_tv, copyID, + NULL, NULL); + // global variables + abort = abort || garbage_collect_globvars(copyID); + + // function-local variables + abort = abort || set_ref_in_call_stack(copyID); + + // named functions (matters for closures) + abort = abort || set_ref_in_functions(copyID); + + // function call arguments, if v:testing is set. + abort = abort || set_ref_in_func_args(copyID); + + // funcstacks keep variables for closures + abort = abort || set_ref_in_funcstacks(copyID); + + // loopvars keep variables for loop blocks + abort = abort || set_ref_in_loopvars(copyID); + + // v: vars + abort = abort || garbage_collect_vimvars(copyID); + + // callbacks in buffers + abort = abort || set_ref_in_buffers(copyID); + + // 'completefunc', 'omnifunc' and 'thesaurusfunc' callbacks + abort = abort || set_ref_in_insexpand_funcs(copyID); + + // 'operatorfunc' callback + abort = abort || set_ref_in_opfunc(copyID); + + // 'tagfunc' callback + abort = abort || set_ref_in_tagfunc(copyID); + + // 'imactivatefunc' and 'imstatusfunc' callbacks + abort = abort || set_ref_in_im_funcs(copyID); + +#ifdef FEAT_LUA + abort = abort || set_ref_in_lua(copyID); +#endif + +#ifdef FEAT_PYTHON + abort = abort || set_ref_in_python(copyID); +#endif + +#ifdef FEAT_PYTHON3 + abort = abort || set_ref_in_python3(copyID); +#endif + +#ifdef FEAT_JOB_CHANNEL + abort = abort || set_ref_in_channel(copyID); + abort = abort || set_ref_in_job(copyID); +#endif +#ifdef FEAT_NETBEANS_INTG + abort = abort || set_ref_in_nb_channel(copyID); +#endif + +#ifdef FEAT_TIMERS + abort = abort || set_ref_in_timer(copyID); +#endif + +#ifdef FEAT_QUICKFIX + abort = abort || set_ref_in_quickfix(copyID); +#endif + +#ifdef FEAT_TERMINAL + abort = abort || set_ref_in_term(copyID); +#endif + +#ifdef FEAT_PROP_POPUP + abort = abort || set_ref_in_popups(copyID); +#endif + + abort = abort || set_ref_in_classes(copyID); + + if (!abort) + { + /* + * 2. Free lists and dictionaries that are not referenced. + */ + did_free = free_unref_items(copyID); + + /* + * 3. Check if any funccal can be freed now. + * This may call us back recursively. + */ + free_unref_funccal(copyID, testing); + } + else if (p_verbose > 0) + { + verb_msg(_("Not enough memory to set references, garbage collection aborted!")); + } + + return did_free; +} + +/* + * Free lists, dictionaries, channels and jobs that are no longer referenced. + */ + static int +free_unref_items(int copyID) +{ + int did_free = FALSE; + + // Let all "free" functions know that we are here. This means no + // dictionaries, lists, channels or jobs are to be freed, because we will + // do that here. + in_free_unref_items = TRUE; + + /* + * PASS 1: free the contents of the items. We don't free the items + * themselves yet, so that it is possible to decrement refcount counters + */ + + // Go through the list of dicts and free items without this copyID. + did_free |= dict_free_nonref(copyID); + + // Go through the list of lists and free items without this copyID. + did_free |= list_free_nonref(copyID); + + // Go through the list of objects and free items without this copyID. + did_free |= object_free_nonref(copyID); + + // Go through the list of classes and free items without this copyID. + did_free |= class_free_nonref(copyID); + +#ifdef FEAT_JOB_CHANNEL + // Go through the list of jobs and free items without the copyID. This + // must happen before doing channels, because jobs refer to channels, but + // the reference from the channel to the job isn't tracked. + did_free |= free_unused_jobs_contents(copyID, COPYID_MASK); + + // Go through the list of channels and free items without the copyID. + did_free |= free_unused_channels_contents(copyID, COPYID_MASK); +#endif + + /* + * PASS 2: free the items themselves. + */ + object_free_items(copyID); + dict_free_items(copyID); + list_free_items(copyID); + +#ifdef FEAT_JOB_CHANNEL + // Go through the list of jobs and free items without the copyID. This + // must happen before doing channels, because jobs refer to channels, but + // the reference from the channel to the job isn't tracked. + free_unused_jobs(copyID, COPYID_MASK); + + // Go through the list of channels and free items without the copyID. + free_unused_channels(copyID, COPYID_MASK); +#endif + + in_free_unref_items = FALSE; + + return did_free; +} + +/* + * Mark all lists and dicts referenced through hashtab "ht" with "copyID". + * "list_stack" is used to add lists to be marked. Can be NULL. + * + * Returns TRUE if setting references failed somehow. + */ + int +set_ref_in_ht(hashtab_T *ht, int copyID, list_stack_T **list_stack) +{ + int todo; + int abort = FALSE; + hashitem_T *hi; + hashtab_T *cur_ht; + ht_stack_T *ht_stack = NULL; + ht_stack_T *tempitem; + + cur_ht = ht; + for (;;) + { + if (!abort) + { + // Mark each item in the hashtab. If the item contains a hashtab + // it is added to ht_stack, if it contains a list it is added to + // list_stack. + todo = (int)cur_ht->ht_used; + FOR_ALL_HASHTAB_ITEMS(cur_ht, hi, todo) + 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; + tempitem = ht_stack; + ht_stack = ht_stack->prev; + free(tempitem); + } + + return abort; +} + +#if defined(FEAT_LUA) || defined(FEAT_PYTHON) || defined(FEAT_PYTHON3) \ + || defined(PROTO) +/* + * Mark a dict and its items with "copyID". + * Returns TRUE if setting references failed somehow. + */ + int +set_ref_in_dict(dict_T *d, int copyID) +{ + if (d != NULL && d->dv_copyID != copyID) + { + d->dv_copyID = copyID; + return set_ref_in_ht(&d->dv_hashtab, copyID, NULL); + } + return FALSE; +} +#endif + +/* + * Mark a list and its items with "copyID". + * Returns TRUE if setting references failed somehow. + */ + int +set_ref_in_list(list_T *ll, int copyID) +{ + if (ll != NULL && ll->lv_copyID != copyID) + { + ll->lv_copyID = copyID; + return set_ref_in_list_items(ll, copyID, NULL); + } + return FALSE; +} + +/* + * Mark all lists and dicts referenced through list "l" with "copyID". + * "ht_stack" is used to add hashtabs to be marked. Can be NULL. + * + * Returns TRUE if setting references failed somehow. + */ + int +set_ref_in_list_items(list_T *l, int copyID, ht_stack_T **ht_stack) +{ + listitem_T *li; + int abort = FALSE; + list_T *cur_l; + list_stack_T *list_stack = NULL; + list_stack_T *tempitem; + + cur_l = l; + for (;;) + { + if (!abort && cur_l->lv_first != &range_list_item) + // 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 (li = cur_l->lv_first; !abort && li != NULL; li = li->li_next) + abort = 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; + tempitem = list_stack; + list_stack = list_stack->prev; + free(tempitem); + } + + return abort; +} + +/* + * Mark the partial in callback 'cb' with "copyID". + */ + int +set_ref_in_callback(callback_T *cb, int copyID) +{ + typval_T tv; + + if (cb->cb_name == NULL || *cb->cb_name == NUL || cb->cb_partial == NULL) + return FALSE; + + tv.v_type = VAR_PARTIAL; + tv.vval.v_partial = cb->cb_partial; + return set_ref_in_item(&tv, copyID, NULL, NULL); +} + +/* + * Mark the dict "dd" with "copyID". + * Also see set_ref_in_item(). + */ + static int +set_ref_in_item_dict( + dict_T *dd, + int copyID, + ht_stack_T **ht_stack, + list_stack_T **list_stack) +{ + if (dd == NULL || dd->dv_copyID == copyID) + return FALSE; + + // Didn't see this dict yet. + dd->dv_copyID = copyID; + if (ht_stack == NULL) + return set_ref_in_ht(&dd->dv_hashtab, copyID, list_stack); + + ht_stack_T *newitem = ALLOC_ONE(ht_stack_T); + if (newitem == NULL) + return TRUE; + + newitem->ht = &dd->dv_hashtab; + newitem->prev = *ht_stack; + *ht_stack = newitem; + + return FALSE; +} + +/* + * Mark the list "ll" with "copyID". + * Also see set_ref_in_item(). + */ + static int +set_ref_in_item_list( + list_T *ll, + int copyID, + ht_stack_T **ht_stack, + list_stack_T **list_stack) +{ + if (ll == NULL || ll->lv_copyID == copyID) + return FALSE; + + // Didn't see this list yet. + ll->lv_copyID = copyID; + if (list_stack == NULL) + return set_ref_in_list_items(ll, copyID, ht_stack); + + list_stack_T *newitem = ALLOC_ONE(list_stack_T); + if (newitem == NULL) + return TRUE; + + newitem->list = ll; + newitem->prev = *list_stack; + *list_stack = newitem; + + return FALSE; +} + +/* + * Mark the partial "pt" with "copyID". + * Also see set_ref_in_item(). + */ + static int +set_ref_in_item_partial( + partial_T *pt, + int copyID, + ht_stack_T **ht_stack, + list_stack_T **list_stack) +{ + if (pt == NULL || pt->pt_copyID == copyID) + return FALSE; + + // Didn't see this partial yet. + pt->pt_copyID = copyID; + + int abort = set_ref_in_func(pt->pt_name, pt->pt_func, copyID); + + if (pt->pt_dict != NULL) + { + typval_T dtv; + + dtv.v_type = VAR_DICT; + dtv.vval.v_dict = pt->pt_dict; + set_ref_in_item(&dtv, copyID, ht_stack, list_stack); + } + + if (pt->pt_obj != NULL) + { + typval_T objtv; + + objtv.v_type = VAR_OBJECT; + objtv.vval.v_object = pt->pt_obj; + set_ref_in_item(&objtv, copyID, ht_stack, list_stack); + } + + for (int i = 0; i < pt->pt_argc; ++i) + abort = abort || set_ref_in_item(&pt->pt_argv[i], copyID, + ht_stack, list_stack); + // pt_funcstack is handled in set_ref_in_funcstacks() + // pt_loopvars is handled in set_ref_in_loopvars() + + return abort; +} + +#ifdef FEAT_JOB_CHANNEL +/* + * Mark the job "pt" with "copyID". + * Also see set_ref_in_item(). + */ + static int +set_ref_in_item_job( + job_T *job, + int copyID, + ht_stack_T **ht_stack, + list_stack_T **list_stack) +{ + typval_T dtv; + + if (job == NULL || job->jv_copyID == copyID) + return FALSE; + + job->jv_copyID = copyID; + if (job->jv_channel != NULL) + { + dtv.v_type = VAR_CHANNEL; + dtv.vval.v_channel = job->jv_channel; + set_ref_in_item(&dtv, copyID, ht_stack, list_stack); + } + if (job->jv_exit_cb.cb_partial != NULL) + { + dtv.v_type = VAR_PARTIAL; + dtv.vval.v_partial = job->jv_exit_cb.cb_partial; + set_ref_in_item(&dtv, copyID, ht_stack, list_stack); + } + + return FALSE; +} + +/* + * Mark the channel "ch" with "copyID". + * Also see set_ref_in_item(). + */ + static int +set_ref_in_item_channel( + channel_T *ch, + int copyID, + ht_stack_T **ht_stack, + list_stack_T **list_stack) +{ + typval_T dtv; + + if (ch == NULL || ch->ch_copyID == copyID) + return FALSE; + + ch->ch_copyID = copyID; + for (ch_part_T part = PART_SOCK; part < PART_COUNT; ++part) + { + for (jsonq_T *jq = ch->ch_part[part].ch_json_head.jq_next; + jq != NULL; jq = jq->jq_next) + set_ref_in_item(jq->jq_value, copyID, ht_stack, list_stack); + for (cbq_T *cq = ch->ch_part[part].ch_cb_head.cq_next; cq != NULL; + cq = cq->cq_next) + if (cq->cq_callback.cb_partial != NULL) + { + dtv.v_type = VAR_PARTIAL; + dtv.vval.v_partial = cq->cq_callback.cb_partial; + set_ref_in_item(&dtv, copyID, ht_stack, list_stack); + } + if (ch->ch_part[part].ch_callback.cb_partial != NULL) + { + dtv.v_type = VAR_PARTIAL; + dtv.vval.v_partial = ch->ch_part[part].ch_callback.cb_partial; + set_ref_in_item(&dtv, copyID, ht_stack, list_stack); + } + } + if (ch->ch_callback.cb_partial != NULL) + { + dtv.v_type = VAR_PARTIAL; + dtv.vval.v_partial = ch->ch_callback.cb_partial; + set_ref_in_item(&dtv, copyID, ht_stack, list_stack); + } + if (ch->ch_close_cb.cb_partial != NULL) + { + dtv.v_type = VAR_PARTIAL; + dtv.vval.v_partial = ch->ch_close_cb.cb_partial; + set_ref_in_item(&dtv, copyID, ht_stack, list_stack); + } + + return FALSE; +} +#endif + +/* + * Mark the class "cl" with "copyID". + * Also see set_ref_in_item(). + */ + int +set_ref_in_item_class( + class_T *cl, + int copyID, + ht_stack_T **ht_stack, + list_stack_T **list_stack) +{ + int abort = FALSE; + + if (cl == NULL || cl->class_copyID == copyID) + return FALSE; + + cl->class_copyID = copyID; + if (cl->class_members_tv != NULL) + { + // The "class_members_tv" table is allocated only for regular classes + // and not for interfaces. + for (int i = 0; !abort && i < cl->class_class_member_count; ++i) + abort = abort || set_ref_in_item( + &cl->class_members_tv[i], + copyID, ht_stack, list_stack); + } + + for (int i = 0; !abort && i < cl->class_class_function_count; ++i) + abort = abort || set_ref_in_func(NULL, + cl->class_class_functions[i], copyID); + + for (int i = 0; !abort && i < cl->class_obj_method_count; ++i) + abort = abort || set_ref_in_func(NULL, + cl->class_obj_methods[i], copyID); + + return abort; +} + +/* + * Mark the object "cl" with "copyID". + * Also see set_ref_in_item(). + */ + static int +set_ref_in_item_object( + object_T *obj, + int copyID, + ht_stack_T **ht_stack, + list_stack_T **list_stack) +{ + int abort = FALSE; + + if (obj == NULL || obj->obj_copyID == copyID) + return FALSE; + + obj->obj_copyID = copyID; + + // The typval_T array is right after the object_T. + typval_T *mtv = (typval_T *)(obj + 1); + for (int i = 0; !abort + && i < obj->obj_class->class_obj_member_count; ++i) + abort = abort || set_ref_in_item(mtv + i, copyID, + ht_stack, list_stack); + + return abort; +} + +/* + * Mark all lists, dicts and other container types referenced through typval + * "tv" with "copyID". + * "list_stack" is used to add lists to be marked. Can be NULL. + * "ht_stack" is used to add hashtabs to be marked. Can be NULL. + * + * Returns TRUE if setting references failed somehow. + */ + int +set_ref_in_item( + typval_T *tv, + int copyID, + ht_stack_T **ht_stack, + list_stack_T **list_stack) +{ + int abort = FALSE; + + switch (tv->v_type) + { + case VAR_DICT: + return set_ref_in_item_dict(tv->vval.v_dict, copyID, + ht_stack, list_stack); + + case VAR_LIST: + return set_ref_in_item_list(tv->vval.v_list, copyID, + ht_stack, list_stack); + + case VAR_FUNC: + { + abort = set_ref_in_func(tv->vval.v_string, NULL, copyID); + break; + } + + case VAR_PARTIAL: + return set_ref_in_item_partial(tv->vval.v_partial, copyID, + ht_stack, list_stack); + + case VAR_JOB: +#ifdef FEAT_JOB_CHANNEL + return set_ref_in_item_job(tv->vval.v_job, copyID, + ht_stack, list_stack); +#else + break; +#endif + + case VAR_CHANNEL: +#ifdef FEAT_JOB_CHANNEL + return set_ref_in_item_channel(tv->vval.v_channel, copyID, + ht_stack, list_stack); +#else + break; +#endif + + case VAR_CLASS: + return set_ref_in_item_class(tv->vval.v_class, copyID, + ht_stack, list_stack); + + case VAR_OBJECT: + return set_ref_in_item_object(tv->vval.v_object, copyID, + ht_stack, list_stack); + + case VAR_UNKNOWN: + case VAR_ANY: + case VAR_VOID: + case VAR_BOOL: + case VAR_SPECIAL: + case VAR_NUMBER: + case VAR_FLOAT: + case VAR_STRING: + case VAR_BLOB: + case VAR_TYPEALIAS: + case VAR_INSTR: + // Types that do not contain any other item + break; + } + + return abort; +} + +#endif
--- a/src/list.c +++ b/src/list.c @@ -3194,4 +3194,28 @@ f_reduce(typval_T *argvars, typval_T *re blob_reduce(argvars, &argvars[1], rettv); } +/* + * slice() function + */ + void +f_slice(typval_T *argvars, typval_T *rettv) +{ + if (in_vim9script() + && ((argvars[0].v_type != VAR_STRING + && argvars[0].v_type != VAR_LIST + && argvars[0].v_type != VAR_BLOB + && check_for_list_arg(argvars, 0) == FAIL) + || check_for_number_arg(argvars, 1) == FAIL + || check_for_opt_number_arg(argvars, 2) == FAIL)) + return; + + if (check_can_index(&argvars[0], TRUE, FALSE) != OK) + return; + + copy_tv(argvars, rettv); + eval_index_inner(rettv, TRUE, argvars + 1, + argvars[2].v_type == VAR_UNKNOWN ? NULL : argvars + 2, + TRUE, NULL, 0, FALSE); +} + #endif // defined(FEAT_EVAL)
--- a/src/proto.h +++ b/src/proto.h @@ -94,6 +94,7 @@ extern int _stricoll(char *a, char *b); # include "float.pro" # include "fold.pro" # include "getchar.pro" +# include "gc.pro" # include "gui_xim.pro" # include "hardcopy.pro" # include "hashtab.pro"
--- a/src/proto/eval.pro +++ b/src/proto/eval.pro @@ -48,19 +48,9 @@ int eval_addlist(typval_T *tv1, typval_T int eval_leader(char_u **arg, int vim9); int handle_predefined(char_u *s, int len, typval_T *rettv); int check_can_index(typval_T *rettv, int evaluate, int verbose); -void f_slice(typval_T *argvars, typval_T *rettv); int eval_index_inner(typval_T *rettv, int is_range, typval_T *var1, typval_T *var2, int exclusive, char_u *key, int keylen, int verbose); char_u *partial_name(partial_T *pt); void partial_unref(partial_T *pt); -int get_copyID(void); -int garbage_collect(int testing); -int set_ref_in_ht(hashtab_T *ht, int copyID, list_stack_T **list_stack); -int set_ref_in_dict(dict_T *d, int copyID); -int set_ref_in_list(list_T *ll, int copyID); -int set_ref_in_list_items(list_T *l, int copyID, ht_stack_T **ht_stack); -int set_ref_in_callback(callback_T *cb, int copyID); -int set_ref_in_item_class(class_T *cl, int copyID, ht_stack_T **ht_stack, list_stack_T **list_stack); -int set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack, list_stack_T **list_stack); char_u *echo_string_core(typval_T *tv, char_u **tofree, char_u *numbuf, int copyID, int echo_style, int restore_copyID, int composite_val); char_u *echo_string(typval_T *tv, char_u **tofree, char_u *numbuf, int copyID); int buf_byteidx_to_charidx(buf_T *buf, int lnum, int byteidx);
new file mode 100644 --- /dev/null +++ b/src/proto/gc.pro @@ -0,0 +1,12 @@ +/* gc.c */ +int get_copyID(void); +int garbage_collect(int testing); +int set_ref_in_ht(hashtab_T *ht, int copyID, list_stack_T **list_stack); +int set_ref_in_dict(dict_T *d, int copyID); +int set_ref_in_list(list_T *ll, int copyID); +int set_ref_in_list_items(list_T *l, int copyID, ht_stack_T **ht_stack); +int set_ref_in_callback(callback_T *cb, int copyID); +int set_ref_in_item_class(class_T *cl, int copyID, ht_stack_T **ht_stack, list_stack_T **list_stack); +int set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack, list_stack_T **list_stack); +/* vim: set ft=c : */ +
--- a/src/proto/list.pro +++ b/src/proto/list.pro @@ -65,4 +65,5 @@ void f_insert(typval_T *argvars, typval_ void f_remove(typval_T *argvars, typval_T *rettv); void f_reverse(typval_T *argvars, typval_T *rettv); void f_reduce(typval_T *argvars, typval_T *rettv); +void f_slice(typval_T *argvars, typval_T *rettv); /* vim: set ft=c : */