Patch 9.0.0485
Problem: In a :def function all closures in a loop get the same variables.
Solution: Make a copy of loop variables used in a closure.
Files: src/structs.h, src/vim9execute.c, src/proto/vim9execute.pro,
src/eval.c, src/testdir/test_vim9_script.vim
*** ../vim-9.0.0484/src/structs.h 2022-09-16 19:04:19.957886512 +0100
--- src/structs.h 2022-09-17 14:06:43.698042183 +0100
***************
*** 2077,2083 ****
* defined in that function.
*/
typedef struct funcstack_S funcstack_T;
-
struct funcstack_S
{
funcstack_T *fs_next; // linked list at "first_funcstack"
--- 2077,2082 ----
***************
*** 2092,2098 ****
int fs_refcount; // nr of closures referencing this
funcstack
int fs_min_refcount; // nr of closures on this funcstack
! int fs_copyID; // for garray_T collection
};
typedef struct outer_S outer_T;
--- 2091,2113 ----
int fs_refcount; // nr of closures referencing this
funcstack
int fs_min_refcount; // nr of closures on this funcstack
! int fs_copyID; // for garbage collection
! };
!
! /*
! * Structure to hold the variables declared in a loop that are possiblly used
! * in a closure.
! */
! typedef struct loopvars_S loopvars_T;
! struct loopvars_S
! {
! loopvars_T *lvs_next; // linked list at "first_loopvars"
! loopvars_T *lvs_prev;
!
! garray_T lvs_ga; // contains the variables
! int lvs_refcount; // nr of closures referencing this
loopvars
! int lvs_min_refcount; // nr of closures on this loopvars
! int lvs_copyID; // for garbage collection
};
typedef struct outer_S outer_T;
***************
*** 2128,2133 ****
--- 2143,2150 ----
funcstack_T *pt_funcstack; // copy of stack, used after context
// function returns
+ loopvars_T *pt_loopvars; // copy of loop variables, used after
loop
+ // block ends
typval_T *pt_argv; // arguments in allocated array
int pt_argc; // number of arguments
*** ../vim-9.0.0484/src/vim9execute.c 2022-09-16 19:04:19.957886512 +0100
--- src/vim9execute.c 2022-09-17 15:24:58.795524927 +0100
***************
*** 774,790 ****
- closure_count + idx];
if (pt->pt_refcount > 1)
{
- int prev_frame_idx = pt->pt_outer.out_frame_idx;
-
++funcstack->fs_refcount;
pt->pt_funcstack = funcstack;
pt->pt_outer.out_stack = &funcstack->fs_ga;
pt->pt_outer.out_frame_idx = ectx->ec_frame_idx - top;
-
- // TODO: drop this, should be done at ISN_ENDLOOP
- pt->pt_outer.out_loop_stack = &funcstack->fs_ga;
- pt->pt_outer.out_loop_var_idx -=
- prev_frame_idx - pt->pt_outer.out_frame_idx;
}
}
}
--- 774,783 ----
***************
*** 2622,2638 ****
}
/*
* End of a for or while loop: Handle any variables used by a closure.
*/
static int
! execute_endloop(isn_T *iptr UNUSED, ectx_T *ectx UNUSED)
{
! // TODO
return OK;
}
/*
* Load instruction for w:/b:/g:/t: variable.
* "isn_type" is used instead of "iptr->isn_type".
*/
--- 2615,2802 ----
}
/*
+ * Code for handling variables declared inside a loop and used in a closure.
+ * This is very similar to what is done with funcstack_T. The difference is
+ * that the funcstack_T has the scope of a function, while a loopvars_T has
the
+ * scope of the block inside a loop and each loop may have its own.
+ */
+
+ // Double linked list of loopvars_T in use.
+ static loopvars_T *first_loopvars = NULL;
+
+ static void
+ add_loopvars_to_list(loopvars_T *loopvars)
+ {
+ // Link in list of loopvarss.
+ if (first_loopvars != NULL)
+ first_loopvars->lvs_prev = loopvars;
+ loopvars->lvs_next = first_loopvars;
+ loopvars->lvs_prev = NULL;
+ first_loopvars = loopvars;
+ }
+
+ static void
+ remove_loopvars_from_list(loopvars_T *loopvars)
+ {
+ if (loopvars->lvs_prev == NULL)
+ first_loopvars = loopvars->lvs_next;
+ else
+ loopvars->lvs_prev->lvs_next = loopvars->lvs_next;
+ if (loopvars->lvs_next != NULL)
+ loopvars->lvs_next->lvs_prev = loopvars->lvs_prev;
+ }
+
+ /*
* End of a for or while loop: Handle any variables used by a closure.
*/
static int
! execute_endloop(isn_T *iptr, ectx_T *ectx)
{
! endloop_T *endloop = &iptr->isn_arg.endloop;
! typval_T *tv_refcount = STACK_TV_VAR(endloop->end_funcref_idx);
! int prev_closure_count = tv_refcount->vval.v_number;
! garray_T *gap = &ectx->ec_funcrefs;
! int closure_in_use = FALSE;
! loopvars_T *loopvars;
! typval_T *stack;
! int idx;
!
! // Check if any created closure is still being referenced.
! for (idx = prev_closure_count; idx < gap->ga_len; ++idx)
! {
! partial_T *pt = ((partial_T **)gap->ga_data)[idx];
!
! if (pt->pt_refcount > 1)
! {
! int refcount = pt->pt_refcount;
! int i;
!
! // A Reference in a variable inside the loop doesn't count, it gets
! // unreferenced at the end of the loop.
! for (i = 0; i < endloop->end_var_count; ++i)
! {
! typval_T *stv = STACK_TV_VAR(endloop->end_var_idx + i);
!
! if (stv->v_type == VAR_PARTIAL && pt == stv->vval.v_partial)
! --refcount;
! }
! if (refcount > 1)
! {
! closure_in_use = TRUE;
! break;
! }
! }
! }
!
! // If no function reference were created since the start of the loop block
! // or it is no longer referenced there is nothing to do.
! if (!closure_in_use)
! return OK;
!
! // A closure is using variables declared inside the loop.
! // Move them to the called function.
! loopvars = ALLOC_CLEAR_ONE(loopvars_T);
! if (loopvars == NULL)
! return FAIL;
!
! loopvars->lvs_ga.ga_len = endloop->end_var_count;
! stack = ALLOC_CLEAR_MULT(typval_T, loopvars->lvs_ga.ga_len);
! loopvars->lvs_ga.ga_data = stack;
! if (stack == NULL)
! {
! vim_free(loopvars);
! return FAIL;
! }
! add_loopvars_to_list(loopvars);
!
! // Move the variable values.
! for (idx = 0; idx < endloop->end_var_count; ++idx)
! {
! typval_T *tv = STACK_TV_VAR(endloop->end_var_idx + idx);
!
! *(stack + idx) = *tv;
! tv->v_type = VAR_UNKNOWN;
! }
!
! for (idx = prev_closure_count; idx < gap->ga_len; ++idx)
! {
! partial_T *pt = ((partial_T **)gap->ga_data)[idx];
!
! if (pt->pt_refcount > 1)
! {
! ++loopvars->lvs_refcount;
! pt->pt_loopvars = loopvars;
!
! pt->pt_outer.out_loop_stack = &loopvars->lvs_ga;
! pt->pt_outer.out_loop_var_idx -= ectx->ec_frame_idx
! + STACK_FRAME_SIZE + endloop->end_var_idx;
! }
! }
return OK;
}
/*
+ * Called when a partial is freed or its reference count goes down to one.
The
+ * loopvars may be the only reference to the partials in the local variables.
+ * Go over all of them, the funcref and can be freed if all partials
+ * referencing the loopvars have a reference count of one.
+ */
+ void
+ loopvars_check_refcount(loopvars_T *loopvars)
+ {
+ int i;
+ garray_T *gap = &loopvars->lvs_ga;
+ int done = 0;
+
+ if (loopvars->lvs_refcount > loopvars->lvs_min_refcount)
+ return;
+ for (i = 0; i < gap->ga_len; ++i)
+ {
+ typval_T *tv = ((typval_T *)gap->ga_data) + i;
+
+ if (tv->v_type == VAR_PARTIAL && tv->vval.v_partial != NULL
+ && tv->vval.v_partial->pt_loopvars == loopvars
+ && tv->vval.v_partial->pt_refcount == 1)
+ ++done;
+ }
+ if (done == loopvars->lvs_min_refcount)
+ {
+ typval_T *stack = gap->ga_data;
+
+ // All partials referencing the loopvars have a reference count of
+ // one, thus the loopvars is no longer of use.
+ for (i = 0; i < gap->ga_len; ++i)
+ clear_tv(stack + i);
+ vim_free(stack);
+ remove_loopvars_from_list(loopvars);
+ vim_free(loopvars);
+ }
+ }
+
+ /*
+ * For garbage collecting: set references in all variables referenced by
+ * all loopvars.
+ */
+ int
+ set_ref_in_loopvars(int copyID)
+ {
+ loopvars_T *loopvars;
+
+ for (loopvars = first_loopvars; loopvars != NULL;
+ loopvars = loopvars->lvs_next)
+ {
+ typval_T *stack = loopvars->lvs_ga.ga_data;
+ int i;
+
+ for (i = 0; i < loopvars->lvs_ga.ga_len; ++i)
+ if (set_ref_in_item(stack + i, copyID, NULL, NULL))
+ return TRUE; // abort
+ }
+ return FALSE;
+ }
+
+ /*
* Load instruction for w:/b:/g:/t: variable.
* "isn_type" is used instead of "iptr->isn_type".
*/
***************
*** 3609,3615 ****
goto on_error;
break;
! // load or store variable or argument from outer scope
case ISN_LOADOUTER:
case ISN_STOREOUTER:
{
--- 3773,3779 ----
goto on_error;
break;
! // Load or store variable or argument from outer scope.
case ISN_LOADOUTER:
case ISN_STOREOUTER:
{
***************
*** 3635,3645 ****
goto theend;
}
if (depth == OUTER_LOOP_DEPTH)
! // variable declared in loop
tv = ((typval_T *)outer->out_loop_stack->ga_data)
+ outer->out_loop_var_idx
+ iptr->isn_arg.outer.outer_idx;
else
tv = ((typval_T *)outer->out_stack->ga_data)
+ outer->out_frame_idx + STACK_FRAME_SIZE
+ iptr->isn_arg.outer.outer_idx;
--- 3799,3812 ----
goto theend;
}
if (depth == OUTER_LOOP_DEPTH)
! // Variable declared in loop. May be copied if the
! // loop block has already ended.
tv = ((typval_T *)outer->out_loop_stack->ga_data)
+ outer->out_loop_var_idx
+ iptr->isn_arg.outer.outer_idx;
else
+ // Variable declared in a function. May be copied if
+ // the function has already returned.
tv = ((typval_T *)outer->out_stack->ga_data)
+ outer->out_frame_idx + STACK_FRAME_SIZE
+ iptr->isn_arg.outer.outer_idx;
***************
*** 5563,5569 ****
{
outer_T *outer = get_pt_outer(partial);
! if (outer->out_stack == NULL)
{
if (current_ectx != NULL)
{
--- 5730,5736 ----
{
outer_T *outer = get_pt_outer(partial);
! if (outer->out_stack == NULL && outer->out_loop_stack == NULL)
{
if (current_ectx != NULL)
{
***************
*** 5572,5578 ****
ectx.ec_outer_ref->or_outer =
current_ectx->ec_outer_ref->or_outer;
}
! // Should there be an error here?
}
else
{
--- 5739,5745 ----
ectx.ec_outer_ref->or_outer =
current_ectx->ec_outer_ref->or_outer;
}
! // else: should there be an error here?
}
else
{
*** ../vim-9.0.0484/src/proto/vim9execute.pro 2022-09-16 19:04:19.957886512
+0100
--- src/proto/vim9execute.pro 2022-09-17 15:17:05.496048965 +0100
***************
*** 13,18 ****
--- 13,20 ----
int may_load_script(int sid, int *loaded);
typval_T *lookup_debug_var(char_u *name);
int may_break_in_function(ufunc_T *ufunc);
+ void loopvars_check_refcount(loopvars_T *loopvars);
+ int set_ref_in_loopvars(int copyID);
int exe_typval_instr(typval_T *tv, typval_T *rettv);
char_u *exe_substitute_instr(void);
int call_def_function(ufunc_T *ufunc, int argc_arg, typval_T *argv, int
flags, partial_T *partial, funccall_T *funccal, typval_T *rettv);
*** ../vim-9.0.0484/src/eval.c 2022-09-14 00:30:47.077316538 +0100
--- src/eval.c 2022-09-17 15:14:03.556196785 +0100
***************
*** 4857,4862 ****
--- 4857,4868 ----
--pt->pt_funcstack->fs_refcount;
funcstack_check_refcount(pt->pt_funcstack);
}
+ // Similarly for loop variables.
+ if (pt->pt_loopvars != NULL)
+ {
+ --pt->pt_loopvars->lvs_refcount;
+ loopvars_check_refcount(pt->pt_loopvars);
+ }
vim_free(pt);
}
***************
*** 4875,4882 ****
// If the reference count goes down to one, the funcstack may be the
// only reference and can be freed if no other partials reference it.
! else if (pt->pt_refcount == 1 && pt->pt_funcstack != NULL)
! funcstack_check_refcount(pt->pt_funcstack);
}
}
--- 4881,4893 ----
// If the reference count goes down to one, the funcstack may be the
// only reference and can be freed if no other partials reference it.
! else if (pt->pt_refcount == 1)
! {
! if (pt->pt_funcstack != NULL)
! funcstack_check_refcount(pt->pt_funcstack);
! if (pt->pt_loopvars != NULL)
! loopvars_check_refcount(pt->pt_loopvars);
! }
}
}
***************
*** 5016,5021 ****
--- 5027,5035 ----
// 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);
***************
*** 5379,5384 ****
--- 5393,5399 ----
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()
}
}
#ifdef FEAT_JOB_CHANNEL
*** ../vim-9.0.0484/src/testdir/test_vim9_script.vim 2022-09-15
22:26:13.166294520 +0100
--- src/testdir/test_vim9_script.vim 2022-09-17 15:33:49.686821275 +0100
***************
*** 2285,2293 ****
assert_equal(i, flist[i]())
endfor
END
! # FIXME
! # v9.CheckDefAndScriptSuccess(lines)
! v9.CheckScriptSuccess(['vim9script'] + lines)
lines =<< trim END
var flist: list<func>
--- 2285,2291 ----
assert_equal(i, flist[i]())
endfor
END
! v9.CheckDefAndScriptSuccess(lines)
lines =<< trim END
var flist: list<func>
***************
*** 2301,2309 ****
assert_equal(i, flist[i]())
endfor
END
! # FIXME
! # v9.CheckDefAndScriptSuccess(lines)
! v9.CheckScriptSuccess(['vim9script'] + lines)
enddef
def Test_for_loop_fails()
--- 2299,2305 ----
assert_equal(i, flist[i]())
endfor
END
! v9.CheckDefAndScriptSuccess(lines)
enddef
def Test_for_loop_fails()
*** ../vim-9.0.0484/src/version.c 2022-09-17 12:39:54.996464478 +0100
--- src/version.c 2022-09-17 13:51:45.275096217 +0100
***************
*** 705,706 ****
--- 705,708 ----
{ /* Add new patch number below this line */
+ /**/
+ 485,
/**/
--
>From "know your smileys":
(X0||) Double hamburger with lettuce and tomato
/// Bram Moolenaar -- [email protected] -- http://www.Moolenaar.net \\\
/// \\\
\\\ sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ ///
\\\ help me help AIDS victims -- http://ICCF-Holland.org ///
--
--
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php
---
You received this message because you are subscribed to the Google Groups
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/vim_dev/20220917144725.31BA01C0846%40moolenaar.net.