# HG changeset patch # User Bram Moolenaar # Date 1582749004 -3600 # Node ID b8f778dda1a13342c57a4b304425ab1dbce99805 # Parent d984a63f23df5f67c979b33075d374acf0c9a07c patch 8.2.0323: Vim9: calling a function that is defined later is slow Commit: https://github.com/vim/vim/commit/7eeefd4a395fe3d7c7a2a0879467cf7ed4c29fe6 Author: Bram Moolenaar Date: Wed Feb 26 21:24:23 2020 +0100 patch 8.2.0323: Vim9: calling a function that is defined later is slow 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. diff --git a/src/testdir/test_vim9_disassemble.vim b/src/testdir/test_vim9_disassemble.vim --- a/src/testdir/test_vim9_disassemble.vim +++ b/src/testdir/test_vim9_disassemble.vim @@ -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 diff --git a/src/testdir/test_vim9_script.vim b/src/testdir/test_vim9_script.vim --- a/src/testdir/test_vim9_script.vim +++ b/src/testdir/test_vim9_script.vim @@ -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') diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -739,6 +739,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 323, +/**/ 322, /**/ 321, diff --git a/src/vim9execute.c b/src/vim9execute.c --- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -267,9 +267,10 @@ call_bfunc(int func_idx, int argcount, e /* * 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, 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, /* * 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, 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, 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);