]> granicus.if.org Git - vim/commitdiff
patch 9.0.0470: in :def function all closures in loop get the same variables v9.0.0470
authorBram Moolenaar <Bram@vim.org>
Thu, 15 Sep 2022 16:19:37 +0000 (17:19 +0100)
committerBram Moolenaar <Bram@vim.org>
Thu, 15 Sep 2022 16:19:37 +0000 (17:19 +0100)
Problem:    In a :def function all closures in a loop get the same variables.
Solution:   When in a loop and a closure refers to a variable declared in the
            loop, prepare for making a copy of variables for each closure.

src/proto/vim9instr.pro
src/testdir/test_vim9_disassemble.vim
src/version.c
src/vim9.h
src/vim9cmds.c
src/vim9compile.c
src/vim9execute.c
src/vim9instr.c

index 1fea52b9803b68da7307fc93d0c30a418e6d1db1..887c2f61b7c70514ea182ca96f14aff3d4acd03a 100644 (file)
@@ -43,14 +43,15 @@ int generate_FUNCREF(cctx_T *cctx, ufunc_T *ufunc, isn_T **isnp);
 int generate_NEWFUNC(cctx_T *cctx, char_u *lambda_name, char_u *func_name);
 int generate_DEF(cctx_T *cctx, char_u *name, size_t len);
 int generate_JUMP(cctx_T *cctx, jumpwhen_T when, int where);
+int generate_WHILE(cctx_T *cctx, int funcref_idx);
 int generate_JUMP_IF_ARG_SET(cctx_T *cctx, int arg_off);
 int generate_FOR(cctx_T *cctx, int loop_idx);
+int generate_ENDLOOP(cctx_T *cctx, int funcref_idx, int prev_local_count);
 int generate_TRYCONT(cctx_T *cctx, int levels, int where);
 int check_internal_func_args(cctx_T *cctx, int func_idx, int argcount, int method_call, type2_T **argtypes, type2_T *shuffled_argtypes);
 int generate_BCALL(cctx_T *cctx, int func_idx, int argcount, int method_call);
 int generate_LISTAPPEND(cctx_T *cctx);
 int generate_BLOBAPPEND(cctx_T *cctx);
-int check_args_on_stack(cctx_T *cctx, ufunc_T *ufunc, int argcount);
 int generate_CALL(cctx_T *cctx, ufunc_T *ufunc, int pushed_argcount);
 int generate_UCALL(cctx_T *cctx, char_u *name, int argcount);
 int check_func_args_from_type(cctx_T *cctx, type_T *type, int argcount, int at_top, char_u *name);
index 57b385bd5038e36db69751d11be9fffff878f849..07e4936ea05dce2894e23521da7711e35fa0e903 100644 (file)
@@ -1466,17 +1466,20 @@ def Test_disassemble_for_loop()
         '\d NEWLIST size 0\_s*' ..
         '\d SETTYPE list<number>\_s*' ..
         '\d STORE $0\_s*' ..
+
         'for i in range(3)\_s*' ..
         '\d STORE -1 in $1\_s*' ..
         '\d PUSHNR 3\_s*' ..
         '\d BCALL range(argc 1)\_s*' ..
         '\d FOR $1 -> \d\+\_s*' ..
-        '\d STORE $2\_s*' ..
+        '\d STORE $3\_s*' ..
+
         'res->add(i)\_s*' ..
         '\d LOAD $0\_s*' ..
-        '\d LOAD $2\_s*' ..
+        '\d LOAD $3\_s*' ..
         '\d\+ LISTAPPEND\_s*' ..
         '\d\+ DROP\_s*' ..
+
         'endfor\_s*' ..
         '\d\+ JUMP -> \d\+\_s*' ..
         '\d\+ DROP',
@@ -1498,21 +1501,25 @@ def Test_disassemble_for_loop_eval()
         'var res = ""\_s*' ..
         '\d PUSHS ""\_s*' ..
         '\d STORE $0\_s*' ..
+
         'for str in eval(''\["one", "two"\]'')\_s*' ..
         '\d STORE -1 in $1\_s*' ..
         '\d PUSHS "\["one", "two"\]"\_s*' ..
         '\d BCALL eval(argc 1)\_s*' ..
         '\d FOR $1 -> \d\+\_s*' ..
-        '\d STORE $2\_s*' ..
+        '\d STORE $3\_s*' ..
+
         'res ..= str\_s*' ..
         '\d\+ LOAD $0\_s*' ..
-        '\d\+ LOAD $2\_s*' ..
+        '\d\+ LOAD $3\_s*' ..
         '\d 2STRING_ANY stack\[-1\]\_s*' ..
         '\d\+ CONCAT size 2\_s*' ..
         '\d\+ STORE $0\_s*' ..
+
         'endfor\_s*' ..
         '\d\+ JUMP -> 5\_s*' ..
         '\d\+ DROP\_s*' ..
+
         'return res\_s*' ..
         '\d\+ LOAD $0\_s*' ..
         '\d\+ RETURN',
@@ -1539,12 +1546,14 @@ def Test_disassemble_for_loop_unpack()
         '\d\+ NEWLIST size 2\_s*' ..
         '\d\+ FOR $0 -> 16\_s*' ..
         '\d\+ UNPACK 2\_s*' ..
-        '\d\+ STORE $1\_s*' ..
         '\d\+ STORE $2\_s*' ..
+        '\d\+ STORE $3\_s*' ..
+
         'echo x1 x2\_s*' ..
-        '\d\+ LOAD $1\_s*' ..
         '\d\+ LOAD $2\_s*' ..
+        '\d\+ LOAD $3\_s*' ..
         '\d\+ ECHO 2\_s*' ..
+
         'endfor\_s*' ..
         '\d\+ JUMP -> 8\_s*' ..
         '\d\+ DROP\_s*' ..
@@ -1576,32 +1585,43 @@ def Test_disassemble_for_loop_continue()
         '2 PUSHNR 2\_s*' ..
         '3 NEWLIST size 2\_s*' ..
         '4 FOR $0 -> 22\_s*' ..
-        '5 STORE $1\_s*' ..
+        '5 STORE $2\_s*' ..
+
         'try\_s*' ..
         '6 TRY catch -> 17, endtry -> 20\_s*' ..
+
         'echo "ok"\_s*' ..
         '7 PUSHS "ok"\_s*' ..
         '8 ECHO 1\_s*' ..
+
         'try\_s*' ..
         '9 TRY catch -> 13, endtry -> 15\_s*' ..
+
         'echo "deeper"\_s*' ..
         '10 PUSHS "deeper"\_s*' ..
         '11 ECHO 1\_s*' ..
+
         'catch\_s*' ..
         '12 JUMP -> 15\_s*' ..
         '13 CATCH\_s*' ..
+
         'continue\_s*' ..
         '14 TRY-CONTINUE 2 levels -> 4\_s*' ..
+
         'endtry\_s*' ..
         '15 ENDTRY\_s*' ..
+
         'catch\_s*' ..
         '16 JUMP -> 20\_s*' ..
         '17 CATCH\_s*' ..
+
         'echo "not ok"\_s*' ..
         '18 PUSHS "not ok"\_s*' ..
         '19 ECHO 1\_s*' ..
+
         'endtry\_s*' ..
         '20 ENDTRY\_s*' ..
+
         'endfor\_s*' ..
         '21 JUMP -> 4\_s*' ..
         '\d\+ DROP\_s*' ..
@@ -2478,7 +2498,8 @@ def Test_silent_for()
         '\d NEWLIST size 1\_s*' ..
         '\d CMDMOD_REV\_s*' ..
         '5 FOR $0 -> 8\_s*' ..
-        '\d STORE $1\_s*' ..
+        '\d STORE $2\_s*' ..
+
         'endfor\_s*' ..
         '\d JUMP -> 5\_s*' ..
         '8 DROP\_s*' ..
@@ -2499,7 +2520,7 @@ def Test_silent_while()
         '\d LOADG g:not\_s*' ..
         '\d COND2BOOL\_s*' ..
         '\d CMDMOD_REV\_s*' ..
-        '\d JUMP_IF_FALSE -> 6\_s*' ..
+        '\d WHILE $0 -> 6\_s*' ..
 
         'endwhile\_s*' ..
         '\d JUMP -> 0\_s*' ..
@@ -2691,17 +2712,17 @@ def Test_debug_for()
           '4 STORE -1 in $0\_s*' ..
           '5 PUSHNR 0\_s*' ..
           '6 NEWLIST size 1\_s*' ..
-          '7 DEBUG line 2-2 varcount 2\_s*' ..
+          '7 DEBUG line 2-2 varcount 3\_s*' ..
           '8 FOR $0 -> 15\_s*' ..
-          '9 STORE $1\_s*' ..
+          '9 STORE $2\_s*' ..
 
           'echo a\_s*' ..
-          '10 DEBUG line 3-3 varcount 2\_s*' ..
-          '11 LOAD $1\_s*' ..
+          '10 DEBUG line 3-3 varcount 3\_s*' ..
+          '11 LOAD $2\_s*' ..
           '12 ECHO 1\_s*' ..
 
           'endfor\_s*' ..
-          '13 DEBUG line 4-4 varcount 2\_s*' ..
+          '13 DEBUG line 4-4 varcount 3\_s*' ..
           '14 JUMP -> 7\_s*' ..
           '15 DROP\_s*' ..
           '16 RETURN void*',
index 8b4efd7dbab9a33930e1a1c75f9d8f017d673160..3f832ad0c463c068e67fc2ecc184b842602fb485 100644 (file)
@@ -703,6 +703,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    470,
 /**/
     469,
 /**/
index 9d72b964645cdbcb54809447b1bc3b7f1f355432..1555e4c4a3aed605fe1b8d70fe9f328138679ee4 100644 (file)
@@ -122,6 +122,9 @@ typedef enum {
 
     // loop
     ISN_FOR,       // get next item from a list, uses isn_arg.forloop
+    ISN_WHILE,     // jump if condition false, store funcref count, uses
+                   // isn_arg.whileloop
+    ISN_ENDLOOP,    // handle variables for closures, uses isn_arg.endloop
 
     ISN_TRY,       // add entry to ec_trystack, uses isn_arg.tryref
     ISN_THROW,     // pop value of stack, store in v:exception
@@ -240,6 +243,7 @@ typedef enum {
     JUMP_ALWAYS,
     JUMP_NEVER,
     JUMP_IF_FALSE,             // pop and jump if false
+    JUMP_WHILE_FALSE,          // pop and jump if false for :while
     JUMP_AND_KEEP_IF_TRUE,     // jump if top of stack is truthy, drop if not
     JUMP_IF_COND_TRUE,         // jump if top of stack is true, drop if not
     JUMP_IF_COND_FALSE,                // jump if top of stack is false, drop if not
@@ -263,6 +267,19 @@ typedef struct {
     int            for_end;        // position to jump to after done
 } forloop_T;
 
+// arguments to ISN_WHILE
+typedef struct {
+    int            while_funcref_idx;  // variable index for funcref count
+    int            while_end;          // position to jump to after done
+} whileloop_T;
+
+// arguments to ISN_ENDLOOP
+typedef struct {
+    short    end_funcref_idx;  // variable index of funcrefs.ga_len
+    short    end_var_idx;      // first variable declared in the loop
+    short    end_var_count;    // number of variables declared in the loop
+} endloop_T;
+
 // indirect arguments to ISN_TRY
 typedef struct {
     int            try_catch;      // position to jump to on throw
@@ -446,6 +463,8 @@ struct isn_S {
        jump_T              jump;
        jumparg_T           jumparg;
        forloop_T           forloop;
+       whileloop_T         whileloop;
+       endloop_T           endloop;
        try_T               tryref;
        trycont_T           trycont;
        cbfunc_T            bfunc;
@@ -597,6 +616,9 @@ typedef struct {
 typedef struct {
     int                ws_top_label;       // instruction idx at WHILE
     endlabel_T *ws_end_label;      // instructions to set end
+    int                ws_funcref_idx;     // index of var that holds funcref count
+    int                ws_local_count;     // ctx_locals.ga_len at :while
+    int                ws_closure_count;   // ctx_closure_count at :while
 } whilescope_T;
 
 /*
@@ -605,6 +627,9 @@ typedef struct {
 typedef struct {
     int                fs_top_label;       // instruction idx at FOR
     endlabel_T *fs_end_label;      // break instructions
+    int                fs_funcref_idx;     // index of var that holds funcref count
+    int                fs_local_count;     // ctx_locals.ga_len at :for
+    int                fs_closure_count;   // ctx_closure_count at :for
 } forscope_T;
 
 /*
@@ -726,8 +751,10 @@ struct cctx_S {
 
     garray_T   ctx_locals;         // currently visible local variables
 
-    int                ctx_has_closure;    // set to one if a closure was created in
-                                   // the function
+    int                ctx_has_closure;    // set to one if a FUNCREF was used in the
+                                   // function
+    int                ctx_closure_count;  // incremented for each closure created in
+                                   // the function.
 
     skip_T     ctx_skip;
     scope_T    *ctx_scope;         // current scope, NULL at toplevel
index 2b7c24d8427927c40c4efe832bad94daf7acc8fb..f393afeb484291ca2b18f5b97d5f49a7a807076a 100644 (file)
@@ -278,10 +278,15 @@ compile_unletlock(char_u *arg, exarg_T *eap, cctx_T *cctx)
 }
 
 /*
- * generate a jump to the ":endif"/":endfor"/":endwhile"/":finally"/":endtry".
+ * Generate a jump to the ":endif"/":endfor"/":endwhile"/":finally"/":endtry".
+ * "funcref_idx" is used for JUMP_WHILE_FALSE
  */
     static int
-compile_jump_to_end(endlabel_T **el, jumpwhen_T when, cctx_T *cctx)
+compile_jump_to_end(
+       endlabel_T  **el,
+       jumpwhen_T  when,
+       int         funcref_idx,
+       cctx_T      *cctx)
 {
     garray_T   *instr = &cctx->ctx_instr;
     endlabel_T  *endlabel = ALLOC_CLEAR_ONE(endlabel_T);
@@ -292,7 +297,10 @@ compile_jump_to_end(endlabel_T **el, jumpwhen_T when, cctx_T *cctx)
     *el = endlabel;
     endlabel->el_end_label = instr->ga_len;
 
-    generate_JUMP(cctx, when, 0);
+    if (when == JUMP_WHILE_FALSE)
+       generate_WHILE(cctx, funcref_idx);
+    else
+       generate_JUMP(cctx, when, 0);
     return OK;
 }
 
@@ -564,7 +572,7 @@ compile_elseif(char_u *arg, cctx_T *cctx)
        }
 
        if (compile_jump_to_end(&scope->se_u.se_if.is_end_label,
-                                                   JUMP_ALWAYS, cctx) == FAIL)
+                                                JUMP_ALWAYS, 0, cctx) == FAIL)
            return NULL;
        // previous "if" or "elseif" jumps here
        isn = ((isn_T *)instr->ga_data) + scope->se_u.se_if.is_if_label;
@@ -695,7 +703,7 @@ compile_else(char_u *arg, cctx_T *cctx)
        {
            if (!cctx->ctx_had_return
                    && compile_jump_to_end(&scope->se_u.se_if.is_end_label,
-                                                   JUMP_ALWAYS, cctx) == FAIL)
+                                                JUMP_ALWAYS, 0, cctx) == FAIL)
                return NULL;
        }
 
@@ -771,16 +779,17 @@ compile_endif(char_u *arg, cctx_T *cctx)
  * Compile "for var in expr":
  *
  * Produces instructions:
- *       PUSHNR -1
- *       STORE loop-idx                Set index to -1
- *       EVAL expr             result of "expr" on top of stack
+ *       STORE -1 in loop-idx  Set index to -1
+ *       EVAL expr             Result of "expr" on top of stack
  * top:  FOR loop-idx, end     Increment index, use list on bottom of stack
  *                             - if beyond end, jump to "end"
  *                             - otherwise get item from list and push it
+ *                             - store ec_funcrefs in var "loop-idx" + 1
  *       STORE var             Store item in "var"
  *       ... body ...
- *       JUMP top              Jump back to repeat
- * end:         DROP                   Drop the result of "expr"
+ *       ENDLOOP funcref-idx off count Only if closure uses local var
+ *       JUMP top                      Jump back to repeat
+ * end:         DROP                           Drop the result of "expr"
  *
  * Compile "for [var1, var2] in expr" - as above, but instead of "STORE var":
  *      UNPACK 2               Split item in 2
@@ -801,7 +810,9 @@ compile_for(char_u *arg_start, cctx_T *cctx)
     size_t     varlen;
     garray_T   *instr = &cctx->ctx_instr;
     scope_T    *scope;
+    forscope_T *forscope;
     lvar_T     *loop_lvar;     // loop iteration variable
+    lvar_T     *funcref_lvar;
     lvar_T     *var_lvar;      // variable for "var"
     type_T     *vartype;
     type_T     *item_type = &t_any;
@@ -845,18 +856,28 @@ compile_for(char_u *arg_start, cctx_T *cctx)
     scope = new_scope(cctx, FOR_SCOPE);
     if (scope == NULL)
        return NULL;
+    forscope = &scope->se_u.se_for;
 
     // Reserve a variable to store the loop iteration counter and initialize it
     // to -1.
     loop_lvar = reserve_local(cctx, (char_u *)"", 0, FALSE, &t_number);
     if (loop_lvar == NULL)
     {
-       // out of memory
        drop_scope(cctx);
-       return NULL;
+       return NULL;  // out of memory
     }
     generate_STORENR(cctx, loop_lvar->lv_idx, -1);
 
+    // Reserve a variable to store ec_funcrefs.ga_len, used in ISN_ENDLOOP.
+    // The variable index is always the loop var index plus one.
+    // It is not used when no closures are encountered, we don't know yet.
+    funcref_lvar = reserve_local(cctx, (char_u *)"", 0, FALSE, &t_number);
+    if (funcref_lvar == NULL)
+    {
+       drop_scope(cctx);
+       return NULL;  // out of memory
+    }
+
     // compile "expr", it remains on the stack until "endfor"
     arg = p;
     if (compile_expr0(&arg, cctx) == FAIL)
@@ -901,7 +922,7 @@ compile_for(char_u *arg_start, cctx_T *cctx)
        generate_undo_cmdmods(cctx);
 
        // "for_end" is set when ":endfor" is found
-       scope->se_u.se_for.fs_top_label = current_instr_idx(cctx);
+       forscope->fs_top_label = current_instr_idx(cctx);
 
        if (cctx->ctx_compile_type == CT_DEBUG)
        {
@@ -1019,6 +1040,11 @@ compile_for(char_u *arg_start, cctx_T *cctx)
            arg = skipwhite(p);
            vim_free(name);
        }
+
+       forscope->fs_funcref_idx = funcref_lvar->lv_idx;
+       // remember the number of variables and closures, used in :endfor
+       forscope->fs_local_count = cctx->ctx_locals.ga_len;
+       forscope->fs_closure_count = cctx->ctx_closure_count;
     }
 
     return arg_end;
@@ -1029,6 +1055,23 @@ failed:
     return NULL;
 }
 
+/*
+ * At :endfor and :endwhile: Generate an ISN_ENDLOOP instruction if any
+ * variable was declared that could be used by a new closure.
+ */
+    static int
+compile_loop_end(
+       int     prev_local_count,
+       int     prev_closure_count,
+       int     funcref_idx,
+       cctx_T  *cctx)
+{
+    if (cctx->ctx_locals.ga_len > prev_local_count
+           && cctx->ctx_closure_count > prev_closure_count)
+       return generate_ENDLOOP(cctx, funcref_idx, prev_local_count);
+    return OK;
+}
+
 /*
  * compile "endfor"
  */
@@ -1052,6 +1095,14 @@ compile_endfor(char_u *arg, cctx_T *cctx)
     cctx->ctx_scope = scope->se_outer;
     if (cctx->ctx_skip != SKIP_YES)
     {
+       // Handle the case that any local variables were declared that might be
+       // used in a closure.
+       if (compile_loop_end(forscope->fs_local_count,
+                               forscope->fs_closure_count,
+                               forscope->fs_funcref_idx,
+                               cctx) == FAIL)
+           return NULL;
+
        unwind_locals(cctx, scope->se_local_count);
 
        // At end of ":for" scope jump back to the FOR instruction.
@@ -1080,25 +1131,42 @@ compile_endfor(char_u *arg, cctx_T *cctx)
  * compile "while expr"
  *
  * Produces instructions:
- * top:  EVAL expr             Push result of "expr"
- *       JUMP_IF_FALSE end     jump if false
- *       ... body ...
- *       JUMP top              Jump back to repeat
+ * top:  EVAL expr                     Push result of "expr"
+ *      WHILE funcref-idx  end         Jump if false
+ *      ... body ...
+ *       ENDLOOP funcref-idx off count only if closure uses local var
+ *      JUMP top                       Jump back to repeat
  * end:
  *
  */
     char_u *
 compile_while(char_u *arg, cctx_T *cctx)
 {
-    char_u     *p = arg;
-    scope_T    *scope;
+    char_u         *p = arg;
+    scope_T        *scope;
+    whilescope_T    *whilescope;
+    lvar_T         *funcref_lvar;
 
     scope = new_scope(cctx, WHILE_SCOPE);
     if (scope == NULL)
        return NULL;
+    whilescope = &scope->se_u.se_while;
 
     // "endwhile" jumps back here, one before when profiling or using cmdmods
-    scope->se_u.se_while.ws_top_label = current_instr_idx(cctx);
+    whilescope->ws_top_label = current_instr_idx(cctx);
+
+    // Reserve a variable to store ec_funcrefs.ga_len, used in ISN_ENDLOOP.
+    // It is not used when no closures are encountered, we don't know yet.
+    funcref_lvar = reserve_local(cctx, (char_u *)"", 0, FALSE, &t_number);
+    if (funcref_lvar == NULL)
+    {
+       drop_scope(cctx);
+       return NULL;  // out of memory
+    }
+    whilescope->ws_funcref_idx = funcref_lvar->lv_idx;
+    // remember the number of variables and closures, used in :endwhile
+    whilescope->ws_local_count = cctx->ctx_locals.ga_len;
+    whilescope->ws_closure_count = cctx->ctx_closure_count;
 
     // compile "expr"
     if (compile_expr0(&p, cctx) == FAIL)
@@ -1119,8 +1187,8 @@ compile_while(char_u *arg, cctx_T *cctx)
        generate_undo_cmdmods(cctx);
 
        // "while_end" is set when ":endwhile" is found
-       if (compile_jump_to_end(&scope->se_u.se_while.ws_end_label,
-                                                 JUMP_IF_FALSE, cctx) == FAIL)
+       if (compile_jump_to_end(&whilescope->ws_end_label,
+                        JUMP_WHILE_FALSE, funcref_lvar->lv_idx, cctx) == FAIL)
            return FAIL;
     }
 
@@ -1146,6 +1214,16 @@ compile_endwhile(char_u *arg, cctx_T *cctx)
     cctx->ctx_scope = scope->se_outer;
     if (cctx->ctx_skip != SKIP_YES)
     {
+       whilescope_T    *whilescope = &scope->se_u.se_while;
+
+       // Handle the case that any local variables were declared that might be
+       // used in a closure.
+       if (compile_loop_end(whilescope->ws_local_count,
+                               whilescope->ws_closure_count,
+                               whilescope->ws_funcref_idx,
+                               cctx) == FAIL)
+           return NULL;
+
        unwind_locals(cctx, scope->se_local_count);
 
 #ifdef FEAT_PROFILE
@@ -1250,7 +1328,7 @@ compile_break(char_u *arg, cctx_T *cctx)
 
     // Jump to the end of the FOR or WHILE loop.  The instruction index will be
     // filled in later.
-    if (compile_jump_to_end(el, JUMP_ALWAYS, cctx) == FAIL)
+    if (compile_jump_to_end(el, JUMP_ALWAYS, 0, cctx) == FAIL)
        return FAIL;
 
     return arg;
@@ -1397,7 +1475,7 @@ compile_catch(char_u *arg, cctx_T *cctx UNUSED)
 #endif
        // Jump from end of previous block to :finally or :endtry
        if (compile_jump_to_end(&scope->se_u.se_try.ts_end_label,
-                                                   JUMP_ALWAYS, cctx) == FAIL)
+                                                JUMP_ALWAYS, 0, cctx) == FAIL)
            return NULL;
 
        // End :try or :catch scope: set value in ISN_TRY instruction
index 9755d970897114396b17e98c58e5e1e31d9bc9a7..5530056ba52a9e6dbeca77086a1d0eb79acd680b 100644 (file)
@@ -3449,8 +3449,14 @@ nextline:
        }
        dfunc->df_varcount = dfunc->df_var_names.ga_len;
        dfunc->df_has_closure = cctx.ctx_has_closure;
+
        if (cctx.ctx_outer_used)
+       {
            ufunc->uf_flags |= FC_CLOSURE;
+           if (outer_cctx != NULL)
+               ++outer_cctx->ctx_closure_count;
+       }
+
        ufunc->uf_def_status = UF_COMPILED;
     }
 
index cbf9af7ad36f4e00f79d65e133ec04b6d4ae73cf..a1c2f8b27ca70b6f18bdc29fc68ef441df488447 100644 (file)
@@ -504,7 +504,8 @@ call_dfunc(
     // - if needed: a counter for number of closures created in
     //   ectx->ec_funcrefs.
     varcount = dfunc->df_varcount + dfunc->df_has_closure;
-    if (GA_GROW_FAILS(&ectx->ec_stack, arg_to_add + STACK_FRAME_SIZE + varcount))
+    if (GA_GROW_FAILS(&ectx->ec_stack,
+                                    arg_to_add + STACK_FRAME_SIZE + varcount))
        return FAIL;
 
     // If depth of calling is getting too high, don't execute the function.
@@ -553,6 +554,8 @@ call_dfunc(
     {
        typval_T *tv = STACK_TV_BOT(STACK_FRAME_SIZE + dfunc->df_varcount);
 
+       // Initialize the variable that counts how many closures were created.
+       // This is used in handle_closure_in_use().
        tv->v_type = VAR_NUMBER;
        tv->vval.v_number = 0;
     }
@@ -1821,8 +1824,8 @@ fill_partial_and_closure(partial_T *pt, ufunc_T *ufunc, ectx_T *ectx)
        dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data)
                                                          + ectx->ec_dfunc_idx;
 
-       // The closure may need to find arguments and local variables in the
-       // current stack.
+       // The closure may need to find arguments and local variables of the
+       // current function in the stack.
        pt->pt_outer.out_stack = &ectx->ec_stack;
        pt->pt_outer.out_frame_idx = ectx->ec_frame_idx;
        if (ectx->ec_outer_ref != NULL)
@@ -1836,8 +1839,9 @@ fill_partial_and_closure(partial_T *pt, ufunc_T *ufunc, ectx_T *ectx)
            }
        }
 
-       // If this function returns and the closure is still being used, we
-       // need to make a copy of the context (arguments and local variables).
+       // If the function currently executing returns and the closure is still
+       // being referenced, we need to make a copy of the context (arguments
+       // and local variables) so that the closure can use it later.
        // Store a reference to the partial so we can handle that.
        if (GA_GROW_FAILS(&ectx->ec_funcrefs, 1))
        {
@@ -2477,6 +2481,7 @@ execute_unletrange(isn_T *iptr, ectx_T *ectx)
 execute_for(isn_T *iptr, ectx_T *ectx)
 {
     typval_T   *tv;
+    int                jump = FALSE;
     typval_T   *ltv = STACK_TV_BOT(-1);
     typval_T   *idxtv =
                   STACK_TV_VAR(iptr->isn_arg.forloop.for_idx);
@@ -2492,9 +2497,7 @@ execute_for(isn_T *iptr, ectx_T *ectx)
        if (list == NULL
                       || idxtv->vval.v_number >= list->lv_len)
        {
-           // past the end of the list, jump to "endfor"
-           ectx->ec_iidx = iptr->isn_arg.forloop.for_end;
-           may_restore_cmdmod(&ectx->ec_funclocal);
+           jump = TRUE;
        }
        else if (list->lv_first == &range_list_item)
        {
@@ -2524,9 +2527,7 @@ execute_for(isn_T *iptr, ectx_T *ectx)
        ++idxtv->vval.v_number;
        if (str == NULL || str[idxtv->vval.v_number] == NUL)
        {
-           // past the end of the string, jump to "endfor"
-           ectx->ec_iidx = iptr->isn_arg.forloop.for_end;
-           may_restore_cmdmod(&ectx->ec_funclocal);
+           jump = TRUE;
        }
        else
        {
@@ -2557,12 +2558,9 @@ execute_for(isn_T *iptr, ectx_T *ectx)
 
        // The index is for the previous byte.
        ++idxtv->vval.v_number;
-       if (blob == NULL
-                    || idxtv->vval.v_number >= blob_len(blob))
+       if (blob == NULL || idxtv->vval.v_number >= blob_len(blob))
        {
-           // past the end of the blob, jump to "endfor"
-           ectx->ec_iidx = iptr->isn_arg.forloop.for_end;
-           may_restore_cmdmod(&ectx->ec_funclocal);
+           jump = TRUE;
        }
        else
        {
@@ -2580,6 +2578,33 @@ execute_for(isn_T *iptr, ectx_T *ectx)
                                    vartype_name(ltv->v_type));
        return FAIL;
     }
+
+    if (jump)
+    {
+       // past the end of the list/string/blob, jump to "endfor"
+       ectx->ec_iidx = iptr->isn_arg.forloop.for_end;
+       may_restore_cmdmod(&ectx->ec_funclocal);
+    }
+    else
+    {
+       // Store the current number of funcrefs, this may be used in
+       // ISN_LOOPEND.  The variable index is always one more than the loop
+       // variable index.
+       tv = STACK_TV_VAR(iptr->isn_arg.forloop.for_idx + 1);
+       tv->vval.v_number = ectx->ec_funcrefs.ga_len;
+    }
+
+    return OK;
+}
+
+/*
+ * 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;
 }
 
@@ -3989,6 +4014,31 @@ exec_instructions(ectx_T *ectx)
                }
                break;
 
+           // "while": jump to end if a condition is false
+           case ISN_WHILE:
+               {
+                   int         error = FALSE;
+                   int         jump = TRUE;
+
+                   tv = STACK_TV_BOT(-1);
+                   SOURCING_LNUM = iptr->isn_lnum;
+                   jump = !tv_get_bool_chk(tv, &error);
+                   if (error)
+                       goto on_error;
+                   // drop the value from the stack
+                   clear_tv(tv);
+                   --ectx->ec_stack.ga_len;
+                   if (jump)
+                       ectx->ec_iidx = iptr->isn_arg.whileloop.while_end;
+
+                   // Store the current funccal count, may be used by
+                   // ISN_LOOPEND later
+                   tv = STACK_TV_VAR(
+                                   iptr->isn_arg.whileloop.while_funcref_idx);
+                   tv->vval.v_number = ectx->ec_funcrefs.ga_len;
+               }
+               break;
+
            // Jump if an argument with a default value was already set and not
            // v:none.
            case ISN_JUMP_IF_ARG_SET:
@@ -4005,6 +4055,12 @@ exec_instructions(ectx_T *ectx)
                    goto theend;
                break;
 
+           // end of a for or while loop
+           case ISN_ENDLOOP:
+               if (execute_endloop(iptr, ectx) == FAIL)
+                   goto theend;
+               break;
+
            // start of ":try" block
            case ISN_TRY:
                {
@@ -6185,6 +6241,9 @@ list_instructions(char *pfx, isn_T *instr, int instr_count, ufunc_T *ufunc)
                        case JUMP_IF_FALSE:
                            when = "JUMP_IF_FALSE";
                            break;
+                       case JUMP_WHILE_FALSE:
+                           when = "JUMP_WHILE_FALSE";  // unused
+                           break;
                        case JUMP_IF_COND_FALSE:
                            when = "JUMP_IF_COND_FALSE";
                            break;
@@ -6212,6 +6271,27 @@ list_instructions(char *pfx, isn_T *instr, int instr_count, ufunc_T *ufunc)
                }
                break;
 
+           case ISN_ENDLOOP:
+               {
+                   endloop_T *endloop = &iptr->isn_arg.endloop;
+
+                   smsg("%s%4d ENDLOOP $%d save $%d - $%d", pfx, current,
+                           endloop->end_funcref_idx,
+                           endloop->end_var_idx,
+                           endloop->end_var_idx + endloop->end_var_count - 1);
+               }
+               break;
+
+           case ISN_WHILE:
+               {
+                   whileloop_T *whileloop = &iptr->isn_arg.whileloop;
+
+                   smsg("%s%4d WHILE $%d -> %d", pfx, current,
+                                              whileloop->while_funcref_idx,
+                                              whileloop->while_end);
+               }
+               break;
+
            case ISN_TRY:
                {
                    try_T *try = &iptr->isn_arg.tryref;
index 6a387fa51a59dabefde517208dbee6509f98aee2..46f0b367cf6875d997213ca673bcbbef3c9aa5ff 100644 (file)
@@ -1283,6 +1283,27 @@ generate_JUMP(cctx_T *cctx, jumpwhen_T when, int where)
     return OK;
 }
 
+/*
+ * Generate an ISN_WHILE instruction.  Similar to ISN_JUMP for :while
+ */
+    int
+generate_WHILE(cctx_T *cctx, int funcref_idx)
+{
+    isn_T      *isn;
+    garray_T   *stack = &cctx->ctx_type_stack;
+
+    RETURN_OK_IF_SKIP(cctx);
+    if ((isn = generate_instr(cctx, ISN_WHILE)) == NULL)
+       return FAIL;
+    isn->isn_arg.whileloop.while_funcref_idx = funcref_idx;
+    isn->isn_arg.whileloop.while_end = 0;  // filled in later
+
+    if (stack->ga_len > 0)
+       --stack->ga_len;
+
+    return OK;
+}
+
 /*
  * Generate an ISN_JUMP_IF_ARG_SET instruction.
  */
@@ -1312,6 +1333,25 @@ generate_FOR(cctx_T *cctx, int loop_idx)
     // type doesn't matter, will be stored next
     return push_type_stack(cctx, &t_any);
 }
+
+    int
+generate_ENDLOOP(
+       cctx_T  *cctx,
+       int     funcref_idx,
+       int     prev_local_count)
+{
+    isn_T      *isn;
+
+    RETURN_OK_IF_SKIP(cctx);
+    if ((isn = generate_instr(cctx, ISN_ENDLOOP)) == NULL)
+       return FAIL;
+    isn->isn_arg.endloop.end_funcref_idx = funcref_idx;
+    isn->isn_arg.endloop.end_var_idx = prev_local_count;
+    isn->isn_arg.endloop.end_var_count =
+                                   cctx->ctx_locals.ga_len - prev_local_count;
+    return OK;
+}
+
 /*
  * Generate an ISN_TRYCONT instruction.
  */
@@ -2295,6 +2335,7 @@ delete_instr(isn_T *isn)
        case ISN_ECHOERR:
        case ISN_ECHOMSG:
        case ISN_ECHOWINDOW:
+       case ISN_ENDLOOP:
        case ISN_ENDTRY:
        case ISN_EXECCONCAT:
        case ISN_EXECUTE:
@@ -2341,10 +2382,10 @@ delete_instr(isn_T *isn)
        case ISN_RETURN_VOID:
        case ISN_SHUFFLE:
        case ISN_SLICE:
+       case ISN_SOURCE:
        case ISN_STORE:
        case ISN_STOREINDEX:
        case ISN_STORENR:
-       case ISN_SOURCE:
        case ISN_STOREOUTER:
        case ISN_STORERANGE:
        case ISN_STOREREG:
@@ -2357,6 +2398,7 @@ delete_instr(isn_T *isn)
        case ISN_UNLETRANGE:
        case ISN_UNPACK:
        case ISN_USEDICT:
+       case ISN_WHILE:
        // nothing allocated
        break;
     }