]> granicus.if.org Git - vim/commitdiff
patch 8.2.0323: Vim9: calling a function that is defined later is slow v8.2.0323
authorBram Moolenaar <Bram@vim.org>
Wed, 26 Feb 2020 20:24:23 +0000 (21:24 +0100)
committerBram Moolenaar <Bram@vim.org>
Wed, 26 Feb 2020 20:24:23 +0000 (21:24 +0100)
Problem:    Vim9: calling a function that is defined later is slow.
Solution:   Once the function is found update the instruction so it can be
            called directly.

src/testdir/test_vim9_disassemble.vim
src/testdir/test_vim9_script.vim
src/version.c
src/vim9execute.c

index 375869717170abd8c028e9377801e35f6a28abba..9196184a6fca9683793e73f4c35a0e1a8e7dd764 100644 (file)
@@ -222,6 +222,38 @@ def Test_disassemble_call()
 enddef
 
 
+def FuncWithForwardCall(): string
+  return DefinedLater("yes")
+enddef
+
+def DefinedLater(arg: string): string
+  return arg
+enddef
+
+def Test_disassemble_update_instr()
+  let res = execute('disass FuncWithForwardCall')
+  assert_match('FuncWithForwardCall.*'
+        \ .. 'return DefinedLater("yes").*'
+        \ .. '\d PUSHS "yes".*'
+        \ .. '\d UCALL DefinedLater(argc 1).*'
+        \ .. '\d CHECKTYPE string stack\[-1].*'
+        \ .. '\d RETURN.*'
+        \, res)
+
+  " Calling the function will change UCALL into the faster DCALL
+  assert_equal('yes', FuncWithForwardCall())
+
+  res = execute('disass FuncWithForwardCall')
+  assert_match('FuncWithForwardCall.*'
+        \ .. 'return DefinedLater("yes").*'
+        \ .. '\d PUSHS "yes".*'
+        \ .. '\d DCALL DefinedLater(argc 1).*'
+        \ .. '\d CHECKTYPE string stack\[-1].*'
+        \ .. '\d RETURN.*'
+        \, res)
+enddef
+
+
 def FuncWithDefault(arg: string = 'default'): string
   return arg
 enddef
index 53ee5119c86e10e00cc3bde9c72a346b27d2df2b..f1b21d45e39d14441bd0f3478cb66af0fae3483d 100644 (file)
@@ -212,6 +212,19 @@ func DefinedLater(arg)
   return a:arg
 endfunc
 
+def FuncWithForwardCall()
+  return DefinedEvenLater("yes")
+enddef
+
+def DefinedEvenLater(arg: string): string
+  return arg
+enddef
+
+def Test_error_in_nested_function()
+  " Error in called function requires unwinding the call stack.
+  assert_fails('call FuncWithForwardCall()', 'E1029')
+enddef
+
 def Test_return_type_wrong()
   CheckScriptFailure(['def Func(): number', 'return "a"', 'enddef'], 'expected number but got string')
   CheckScriptFailure(['def Func(): string', 'return 1', 'enddef'], 'expected string but got number')
index 258cab24f631144b51c99f567588125c2cbb500d..f95526c0a91ef7a9920b160e3c36b3e6f65de411 100644 (file)
@@ -738,6 +738,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    323,
 /**/
     322,
 /**/
index 4f2265475453ca0f6abe8405a09b646b0cf795cf..9ecb8657e6f19b3f84f58a0ad46968c36d254028 100644 (file)
@@ -267,9 +267,10 @@ call_bfunc(int func_idx, int argcount, ectx_T *ectx)
 
 /*
  * Execute a user defined function.
+ * "iptr" can be used to replace the instruction with a more efficient one.
  */
     static int
-call_ufunc(ufunc_T *ufunc, int argcount, ectx_T *ectx)
+call_ufunc(ufunc_T *ufunc, int argcount, ectx_T *ectx, isn_T *iptr)
 {
     typval_T   argvars[MAX_FUNC_ARGS];
     funcexe_T   funcexe;
@@ -277,8 +278,17 @@ call_ufunc(ufunc_T *ufunc, int argcount, ectx_T *ectx)
     int                idx;
 
     if (ufunc->uf_dfunc_idx >= 0)
-       // The function has been compiled, can call it quickly.
+    {
+       // The function has been compiled, can call it quickly.  For a function
+       // that was defined later: we can call it directly next time.
+       if (iptr != NULL)
+       {
+           iptr->isn_type = ISN_DCALL;
+           iptr->isn_arg.dfunc.cdf_idx = ufunc->uf_dfunc_idx;
+           iptr->isn_arg.dfunc.cdf_argcount = argcount;
+       }
        return call_dfunc(ufunc->uf_dfunc_idx, argcount, ectx);
+    }
 
     if (call_prepare(argcount, argvars, ectx) == FAIL)
        return FAIL;
@@ -305,10 +315,11 @@ call_ufunc(ufunc_T *ufunc, int argcount, ectx_T *ectx)
 /*
  * Execute a function by "name".
  * This can be a builtin function or a user function.
+ * "iptr" can be used to replace the instruction with a more efficient one.
  * Returns FAIL if not found without an error message.
  */
     static int
-call_by_name(char_u *name, int argcount, ectx_T *ectx)
+call_by_name(char_u *name, int argcount, ectx_T *ectx, isn_T *iptr)
 {
     ufunc_T *ufunc;
 
@@ -325,7 +336,7 @@ call_by_name(char_u *name, int argcount, ectx_T *ectx)
 
     ufunc = find_func(name, NULL);
     if (ufunc != NULL)
-       return call_ufunc(ufunc, argcount, ectx);
+       return call_ufunc(ufunc, argcount, ectx, iptr);
 
     return FAIL;
 }
@@ -341,12 +352,12 @@ call_partial(typval_T *tv, int argcount, ectx_T *ectx)
        partial_T *pt = tv->vval.v_partial;
 
        if (pt->pt_func != NULL)
-           return call_ufunc(pt->pt_func, argcount, ectx);
+           return call_ufunc(pt->pt_func, argcount, ectx, NULL);
        name = pt->pt_name;
     }
     else
        name = tv->vval.v_string;
-    if (call_by_name(name, argcount, ectx) == FAIL)
+    if (call_by_name(name, argcount, ectx, NULL) == FAIL)
     {
        if (called_emsg == called_emsg_before)
            semsg(_(e_unknownfunc), name);
@@ -372,13 +383,14 @@ store_var(char_u *name, typval_T *tv)
 /*
  * Execute a function by "name".
  * This can be a builtin function, user function or a funcref.
+ * "iptr" can be used to replace the instruction with a more efficient one.
  */
     static int
-call_eval_func(char_u *name, int argcount, ectx_T *ectx)
+call_eval_func(char_u *name, int argcount, ectx_T *ectx, isn_T *iptr)
 {
     int                called_emsg_before = called_emsg;
 
-    if (call_by_name(name, argcount, ectx) == FAIL
+    if (call_by_name(name, argcount, ectx, iptr) == FAIL
                                          && called_emsg == called_emsg_before)
     {
        // "name" may be a variable that is a funcref or partial
@@ -983,7 +995,7 @@ call_def_function(
 
                    SOURCING_LNUM = iptr->isn_lnum;
                    if (call_eval_func(cufunc->cuf_name,
-                                         cufunc->cuf_argcount, &ectx) == FAIL)
+                                   cufunc->cuf_argcount, &ectx, iptr) == FAIL)
                        goto failed;
                }
                break;
@@ -1558,10 +1570,7 @@ call_def_function(
 
                    tv = STACK_TV_BOT(-1);
                    if (check_not_string(tv) == FAIL)
-                   {
-                       --ectx.ec_stack.ga_len;
                        goto failed;
-                   }
                    (void)tv_get_number_chk(tv, &error);
                    if (error)
                        goto failed;
@@ -1627,6 +1636,10 @@ done:
     ret = OK;
 
 failed:
+    // When failed need to unwind the call stack.
+    while (ectx.ec_frame != initial_frame_ptr)
+       func_return(&ectx);
+
     for (idx = 0; idx < ectx.ec_stack.ga_len; ++idx)
        clear_tv(STACK_TV(idx));
     vim_free(ectx.ec_stack.ga_data);