]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 8.2.2784: Vim9: cannot use \=expr in :substitute v8.2.2784
authorBram Moolenaar <Bram@vim.org>
Mon, 19 Apr 2021 14:48:48 +0000 (16:48 +0200)
committerBram Moolenaar <Bram@vim.org>
Mon, 19 Apr 2021 14:48:48 +0000 (16:48 +0200)
Problem:    Vim9: cannot use \=expr in :substitute.
Solution:   Compile the expression into instructions and execute them when
            invoked.

src/ex_cmds.c
src/globals.h
src/proto/ex_cmds.pro
src/proto/vim9execute.pro
src/regexp.c
src/testdir/test_vim9_cmd.vim
src/testdir/test_vim9_disassemble.vim
src/version.c
src/vim9.h
src/vim9compile.c
src/vim9execute.c

index 6cd54d20ce690a59632cef9461998782e20e5818..96ff6ecba681819fd941761e220ebf1add52beb1 100644 (file)
@@ -3603,6 +3603,29 @@ typedef struct {
     int        do_ic;          // ignore case flag
 } subflags_T;
 
+/*
+ * Skip over the "sub" part in :s/pat/sub/ where "delimiter" is the separating
+ * character.
+ */
+    char_u *
+skip_substitute(char_u *start, int delimiter)
+{
+    char_u *p = start;
+
+    while (p[0])
+    {
+       if (p[0] == delimiter)          // end delimiter found
+       {
+           *p++ = NUL;                 // replace it with a NUL
+           break;
+       }
+       if (p[0] == '\\' && p[1] != 0)  // skip escaped characters
+           ++p;
+       MB_PTR_ADV(p);
+    }
+    return p;
+}
+
 /*
  * Perform a substitution from line eap->line1 to line eap->line2 using the
  * command pointed to by eap->arg which should be of the form:
@@ -3704,18 +3727,7 @@ ex_substitute(exarg_T *eap)
         * Vim we want to use '\n' to find/substitute a NUL.
         */
        sub = cmd;          // remember the start of the substitution
-
-       while (cmd[0])
-       {
-           if (cmd[0] == delimiter)            // end delimiter found
-           {
-               *cmd++ = NUL;                   // replace it with a NUL
-               break;
-           }
-           if (cmd[0] == '\\' && cmd[1] != 0)  // skip escaped characters
-               ++cmd;
-           MB_PTR_ADV(cmd);
-       }
+       cmd = skip_substitute(cmd, delimiter);
 
        if (!eap->skip)
        {
index 51a69465c70a418ab0f481b8431ad31dd3c17aec..017c059a484d9069176709617bf0b3b6bfe411b7 100644 (file)
@@ -1379,6 +1379,9 @@ EXTERN char_u no_lines_msg[]      INIT(= N_("--No lines in buffer--"));
 EXTERN long    sub_nsubs;      // total number of substitutions
 EXTERN linenr_T        sub_nlines;     // total number of lines changed
 
+// Used when a compiled :substitute has an expression.
+EXTERN struct subs_expr_S      *substitute_instr INIT(= NULL);
+
 // table to store parsed 'wildmode'
 EXTERN char_u  wim_flags[4];
 
index 9036dd205b5fc5a5fbabb122eff821603d3e0b33..1711af4537cbd9c6d7c3de2f1f61485b76e08013 100644 (file)
@@ -27,6 +27,7 @@ void ex_change(exarg_T *eap);
 void ex_z(exarg_T *eap);
 int check_restricted(void);
 int check_secure(void);
+char_u *skip_substitute(char_u *start, int delimiter);
 void ex_substitute(exarg_T *eap);
 int do_sub_msg(int count_only);
 void ex_global(exarg_T *eap);
index 212428219d08a7d6f49b9656a914fac3ba2155ea..46b314c8b1bda8686ec35fd762cb688f70b5cb89 100644 (file)
@@ -4,6 +4,7 @@ void funcstack_check_refcount(funcstack_T *funcstack);
 char_u *char_from_string(char_u *str, varnumber_T index);
 char_u *string_slice(char_u *str, varnumber_T first, varnumber_T last, int exclusive);
 int fill_partial_and_closure(partial_T *pt, ufunc_T *ufunc, ectx_T *ectx);
+char_u *exe_substitute_instr(void);
 int call_def_function(ufunc_T *ufunc, int argc_arg, typval_T *argv, partial_T *partial, typval_T *rettv);
 void ex_disassemble(exarg_T *eap);
 int tv2bool(typval_T *tv);
index 9d2d441fc4d7935aae13ab148f8beae3f59c817f..e372dd44dc339687976946348624c05cdbbe597b 100644 (file)
@@ -2069,6 +2069,9 @@ vim_regsub_both(
                }
                clear_tv(&rettv);
            }
+           else if (substitute_instr != NULL)
+               // Execute instructions from ISN_SUBSTITUTE.
+               eval_result = exe_substitute_instr();
            else
                eval_result = eval_to_string(source + 2, TRUE);
 
index 3b479f31d574e5546705ee8f465422b34f15f3c1..15c9a59870f67fb447c32edf256401139b2d9d4e 100644 (file)
@@ -1172,5 +1172,27 @@ def Test_lockvar()
   CheckDefFailure(lines, 'E1178', 2)
 enddef
 
+def Test_substitute_expr()
+  var to = 'repl'
+  new
+  setline(1, 'one from two')
+  s/from/\=to
+  assert_equal('one repl two', getline(1))
+
+  setline(1, 'one from two')
+  s/from/\=to .. '_x'
+  assert_equal('one repl_x two', getline(1))
+
+  setline(1, 'one from two from three')
+  var also = 'also'
+  s/from/\=to .. '_' .. also/g#e
+  assert_equal('one repl_also two repl_also three', getline(1))
+
+  CheckDefFailure(['s/from/\="x")/'], 'E488:')
+  CheckDefFailure(['s/from/\="x"/9'], 'E488:')
+
+  bwipe!
+enddef
+
 
 " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker
index e6b3751452cbd26ffd47b8e5955a10b44b75c33d..8a96bf27326e9c8316ce6fbc829c2bf97d9a729d 100644 (file)
@@ -121,6 +121,25 @@ def Test_disassemble_exec_expr()
         res)
 enddef
 
+def s:Substitute()
+  var expr = "abc"
+  :%s/a/\=expr/&g#c
+enddef
+
+def Test_disassemble_substitute()
+  var res = execute('disass s:Substitute')
+  assert_match('<SNR>\d*_Substitute.*' ..
+        ' var expr = "abc"\_s*' ..
+        '\d PUSHS "abc"\_s*' ..
+        '\d STORE $0\_s*' ..
+        ' :%s/a/\\=expr/&g#c\_s*' ..
+        '\d SUBSTITUTE   :%s/a/\\=expr/&g#c\_s*' ..
+        '    0 LOAD $0\_s*' ..
+        '    -------------\_s*' ..
+        '\d RETURN 0',
+        res)
+enddef
+
 def s:YankRange()
   norm! m[jjm]
   :'[,']yank
index 9a2da2fbc2112e73ddb97409e4a45df6fe1d5ffa..a43a2f432d03e4aba72f269ec602fcea08323c46 100644 (file)
@@ -750,6 +750,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    2784,
 /**/
     2783,
 /**/
index 4a10e12b1cad365b0a467ccf08a5ae20be09a85c..52b7c6dbff8aef483bf8ec707aff1a0122a66f2d 100644 (file)
@@ -19,6 +19,7 @@ typedef enum {
     ISN_ECHOMSG,    // echo Ex commands isn_arg.number items on top of stack
     ISN_ECHOERR,    // echo Ex commands isn_arg.number items on top of stack
     ISN_RANGE,     // compute range from isn_arg.string, push to stack
+    ISN_SUBSTITUTE, // :s command with expression
 
     // get and set variables
     ISN_LOAD,      // push local variable isn_arg.number
@@ -94,7 +95,8 @@ typedef enum {
 
     // expression operations
     ISN_JUMP,      // jump if condition is matched isn_arg.jump
-    ISN_JUMP_IF_ARG_SET, // jump if argument is already set, uses isn_arg.jumparg
+    ISN_JUMP_IF_ARG_SET, // jump if argument is already set, uses
+                        // isn_arg.jumparg
 
     // loop
     ISN_FOR,       // get next item from a list, uses isn_arg.forloop
@@ -165,7 +167,9 @@ typedef enum {
 
     ISN_UNPACK,            // unpack list into items, uses isn_arg.unpack
     ISN_SHUFFLE,    // move item on stack up or down
-    ISN_DROP       // pop stack and discard value
+    ISN_DROP,      // pop stack and discard value
+
+    ISN_FINISH     // end marker in list of instructions
 } isntype_T;
 
 
@@ -339,6 +343,12 @@ typedef struct {
     int                outer_depth;    // nesting level, stack frames to go up
 } isn_outer_T;
 
+// arguments to ISN_SUBSTITUTE
+typedef struct {
+    char_u     *subs_cmd;      // :s command
+    isn_T      *subs_instr;    // sequence of instructions
+} subs_T;
+
 /*
  * Instruction
  */
@@ -381,6 +391,7 @@ struct isn_S {
        cmod_T              cmdmod;
        unpack_T            unpack;
        isn_outer_T         outer;
+       subs_T              subs;
     } isn_arg;
 };
 
index 961050f985d4ad3c82dda04649f37cfb01ce57c5..71a8831623d934e3c5e61af07740a024453bbfeb 100644 (file)
@@ -2130,6 +2130,33 @@ generate_EXECCONCAT(cctx_T *cctx, int count)
     return OK;
 }
 
+    static int
+generate_substitute(char_u *cmd, int instr_start, cctx_T *cctx)
+{
+    isn_T      *isn;
+    isn_T      *instr;
+    int                instr_count = cctx->ctx_instr.ga_len - instr_start;
+
+    instr = ALLOC_MULT(isn_T, instr_count + 1);
+    if (instr == NULL)
+       return FAIL;
+    // Move the generated instructions into the ISN_SUBSTITUTE instructions,
+    // then truncate the list of instructions, so they are used only once.
+    mch_memmove(instr, ((isn_T *)cctx->ctx_instr.ga_data) + instr_start,
+                                             instr_count * sizeof(isn_T));
+    instr[instr_count].isn_type = ISN_FINISH;
+    cctx->ctx_instr.ga_len = instr_start;
+
+    if ((isn = generate_instr(cctx, ISN_SUBSTITUTE)) == NULL)
+    {
+       vim_free(instr);
+       return FAIL;
+    }
+    isn->isn_arg.subs.subs_cmd = vim_strsave(cmd);
+    isn->isn_arg.subs.subs_instr = instr;
+    return OK;
+}
+
 /*
  * Generate ISN_RANGE.  Consumes "range".  Return OK/FAIL.
  */
@@ -8465,6 +8492,55 @@ theend:
     return nextcmd;
 }
 
+/*
+ * :s/pat/repl/
+ */
+    static char_u *
+compile_substitute(char_u *arg, exarg_T *eap, cctx_T *cctx)
+{
+    char_u  *cmd = eap->arg;
+    char_u  *expr = (char_u *)strstr((char *)cmd, "\\=");
+
+    if (expr != NULL)
+    {
+       int delimiter = *cmd++;
+
+       // There is a \=expr, find it in the substitute part.
+       cmd = skip_regexp_ex(cmd, delimiter, magic_isset(),
+                                                            NULL, NULL, NULL);
+       if (cmd[0] == delimiter && cmd[1] == '\\' && cmd[2] == '=')
+       {
+           int     instr_count = cctx->ctx_instr.ga_len;
+           char_u  *end;
+
+           cmd += 3;
+           end = skip_substitute(cmd, delimiter);
+
+           compile_expr0(&cmd, cctx);
+           if (end[-1] == NUL)
+               end[-1] = delimiter;
+           cmd = skipwhite(cmd);
+           if (*cmd != delimiter && *cmd != NUL)
+           {
+               semsg(_(e_trailing_arg), cmd);
+               return NULL;
+           }
+
+           if (generate_substitute(arg, instr_count, cctx) == FAIL)
+               return NULL;
+
+           // skip over flags
+           if (*end == '&')
+               ++end;
+           while (ASCII_ISALPHA(*end) || *end == '#')
+               ++end;
+           return end;
+       }
+    }
+
+    return compile_exec(arg, eap, cctx);
+}
+
 /*
  * Add a function to the list of :def functions.
  * This sets "ufunc->uf_dfunc_idx" but the function isn't compiled yet.
@@ -8996,6 +9072,16 @@ compile_def_function(
                    line = compile_put(p, &ea, &cctx);
                    break;
 
+           case CMD_substitute:
+                   if (cctx.ctx_skip == SKIP_YES)
+                       line = (char_u *)"";
+                   else
+                   {
+                       ea.arg = p;
+                       line = compile_substitute(line, &ea, &cctx);
+                   }
+                   break;
+
            // TODO: any other commands with an expression argument?
 
            case CMD_append:
@@ -9223,6 +9309,11 @@ delete_instr(isn_T *isn)
            vim_free(isn->isn_arg.string);
            break;
 
+       case ISN_SUBSTITUTE:
+           vim_free(isn->isn_arg.subs.subs_cmd);
+           vim_free(isn->isn_arg.subs.subs_instr);
+           break;
+
        case ISN_LOADS:
        case ISN_STORES:
            vim_free(isn->isn_arg.loadstore.ls_name);
@@ -9400,6 +9491,7 @@ delete_instr(isn_T *isn)
        case ISN_UNLETINDEX:
        case ISN_UNLETRANGE:
        case ISN_UNPACK:
+       case ISN_FINISH:
            // nothing allocated
            break;
     }
index 60b58f98a3674f35413a4bd3bb263dc563db058a..6a11e8a6bc9292e859cf0543a84fc5a21dd9f1e1 100644 (file)
@@ -34,6 +34,14 @@ typedef struct {
     int            tcd_return;         // when TRUE return from end of :finally
 } trycmd_T;
 
+// Data local to a function.
+// On a function call, if not empty, is saved on the stack and restored when
+// returning.
+typedef struct {
+    int                floc_restore_cmdmod;
+    cmdmod_T   floc_save_cmdmod;
+    int                floc_restore_cmdmod_stacklen;
+} funclocal_T;
 
 // A stack is used to store:
 // - arguments passed to a :def function
@@ -60,8 +68,10 @@ typedef struct {
 struct ectx_S {
     garray_T   ec_stack;       // stack of typval_T values
     int                ec_frame_idx;   // index in ec_stack: context of ec_dfunc_idx
+    int                ec_initial_frame_idx;   // frame index when called
 
     outer_T    *ec_outer;      // outer scope used for closures, allocated
+    funclocal_T ec_funclocal;
 
     garray_T   ec_trystack;    // stack of trycmd_T values
     int                ec_in_catch;    // when TRUE in catch or finally block
@@ -71,6 +81,10 @@ struct ectx_S {
     int                ec_iidx;        // index in ec_instr: instruction to execute
 
     garray_T   ec_funcrefs;    // partials that might be a closure
+
+    int                ec_did_emsg_before;
+    int                ec_trylevel_at_start;
+    where_T    ec_where;
 };
 
 #ifdef FEAT_PROFILE
@@ -125,15 +139,6 @@ exe_newlist(int count, ectx_T *ectx)
     return OK;
 }
 
-// Data local to a function.
-// On a function call, if not empty, is saved on the stack and restored when
-// returning.
-typedef struct {
-    int                floc_restore_cmdmod;
-    cmdmod_T   floc_save_cmdmod;
-    int                floc_restore_cmdmod_stacklen;
-} funclocal_T;
-
 /*
  * Call compiled function "cdf_idx" from compiled code.
  * This adds a stack frame and sets the instruction pointer to the start of the
@@ -154,7 +159,6 @@ call_dfunc(
        int             cdf_idx,
        partial_T       *pt,
        int             argcount_arg,
-       funclocal_T     *funclocal,
        ectx_T          *ectx)
 {
     int                argcount = argcount_arg;
@@ -254,13 +258,13 @@ call_dfunc(
        return FAIL;
 
     // Only make a copy of funclocal if it contains something to restore.
-    if (funclocal->floc_restore_cmdmod)
+    if (ectx->ec_funclocal.floc_restore_cmdmod)
     {
        floc = ALLOC_ONE(funclocal_T);
        if (floc == NULL)
            return FAIL;
-       *floc = *funclocal;
-       funclocal->floc_restore_cmdmod = FALSE;
+       *floc = ectx->ec_funclocal;
+       ectx->ec_funclocal.floc_restore_cmdmod = FALSE;
     }
 
     // Move the vararg-list to below the missing optional arguments.
@@ -527,7 +531,7 @@ funcstack_check_refcount(funcstack_T *funcstack)
  * Return from the current function.
  */
     static int
-func_return(funclocal_T *funclocal, ectx_T *ectx)
+func_return(ectx_T *ectx)
 {
     int                idx;
     int                ret_idx;
@@ -598,10 +602,10 @@ func_return(funclocal_T *funclocal, ectx_T *ectx)
     ectx->ec_instr = INSTRUCTIONS(prev_dfunc);
 
     if (floc == NULL)
-       funclocal->floc_restore_cmdmod = FALSE;
+       ectx->ec_funclocal.floc_restore_cmdmod = FALSE;
     else
     {
-       *funclocal = *floc;
+       ectx->ec_funclocal = *floc;
        vim_free(floc);
     }
 
@@ -698,7 +702,6 @@ call_ufunc(
        ufunc_T     *ufunc,
        partial_T   *pt,
        int         argcount,
-       funclocal_T *funclocal,
        ectx_T      *ectx,
        isn_T       *iptr)
 {
@@ -738,7 +741,7 @@ call_ufunc(
            iptr->isn_arg.dfunc.cdf_idx = ufunc->uf_dfunc_idx;
            iptr->isn_arg.dfunc.cdf_argcount = argcount;
        }
-       return call_dfunc(ufunc->uf_dfunc_idx, pt, argcount, funclocal, ectx);
+       return call_dfunc(ufunc->uf_dfunc_idx, pt, argcount, ectx);
     }
 
     if (call_prepare(argcount, argvars, ectx) == FAIL)
@@ -800,7 +803,6 @@ vim9_aborting(int prev_called_emsg)
 call_by_name(
        char_u      *name,
        int         argcount,
-       funclocal_T *funclocal,
        ectx_T      *ectx,
        isn_T       *iptr)
 {
@@ -853,7 +855,7 @@ call_by_name(
            }
        }
 
-       return call_ufunc(ufunc, NULL, argcount, funclocal, ectx, iptr);
+       return call_ufunc(ufunc, NULL, argcount, ectx, iptr);
     }
 
     return FAIL;
@@ -863,7 +865,6 @@ call_by_name(
 call_partial(
        typval_T    *tv,
        int         argcount_arg,
-       funclocal_T *funclocal,
        ectx_T      *ectx)
 {
     int                argcount = argcount_arg;
@@ -893,7 +894,7 @@ call_partial(
        }
 
        if (pt->pt_func != NULL)
-           return call_ufunc(pt->pt_func, pt, argcount, funclocal, ectx, NULL);
+           return call_ufunc(pt->pt_func, pt, argcount, ectx, NULL);
 
        name = pt->pt_name;
     }
@@ -911,7 +912,7 @@ call_partial(
        if (error != FCERR_NONE)
            res = FAIL;
        else
-           res = call_by_name(fname, argcount, funclocal, ectx, NULL);
+           res = call_by_name(fname, argcount, ectx, NULL);
        vim_free(tofree);
     }
 
@@ -1184,14 +1185,13 @@ get_script_svar(scriptref_T *sref, ectx_T *ectx)
 call_eval_func(
        char_u      *name,
        int         argcount,
-       funclocal_T *funclocal,
        ectx_T      *ectx,
        isn_T       *iptr)
 {
     int            called_emsg_before = called_emsg;
     int            res;
 
-    res = call_by_name(name, argcount, funclocal, ectx, iptr);
+    res = call_by_name(name, argcount, ectx, iptr);
     if (res == FAIL && called_emsg == called_emsg_before)
     {
        dictitem_T      *v;
@@ -1207,7 +1207,7 @@ call_eval_func(
            semsg(_(e_unknownfunc), name);
            return FAIL;
        }
-       return call_partial(&v->di_tv, argcount, funclocal, ectx);
+       return call_partial(&v->di_tv, argcount, ectx);
     }
     return res;
 }
@@ -1257,326 +1257,106 @@ fill_partial_and_closure(partial_T *pt, ufunc_T *ufunc, ectx_T *ectx)
     return OK;
 }
 
-
-/*
- * Call a "def" function from old Vim script.
- * Return OK or FAIL.
- */
-    int
-call_def_function(
-    ufunc_T    *ufunc,
-    int                argc_arg,       // nr of arguments
-    typval_T   *argv,          // arguments
-    partial_T  *partial,       // optional partial for context
-    typval_T   *rettv)         // return value
-{
-    ectx_T     ectx;           // execution context
-    int                argc = argc_arg;
-    int                initial_frame_idx;
-    typval_T   *tv;
-    int                idx;
-    int                ret = FAIL;
-    int                defcount = ufunc->uf_args.ga_len - argc;
-    sctx_T     save_current_sctx = current_sctx;
-    int                breakcheck_count = 0;
-    int                did_emsg_before = did_emsg_cumul + did_emsg;
-    int                save_suppress_errthrow = suppress_errthrow;
-    msglist_T  **saved_msg_list = NULL;
-    msglist_T  *private_msg_list = NULL;
-    funclocal_T funclocal;
-    int                save_emsg_silent_def = emsg_silent_def;
-    int                save_did_emsg_def = did_emsg_def;
-    int                trylevel_at_start = trylevel;
-    int                orig_funcdepth;
-    where_T    where;
+// used for substitute_instr
+typedef struct subs_expr_S {
+    ectx_T     *subs_ectx;
+    isn_T      *subs_instr;
+    int                subs_status;
+} subs_expr_T;
 
 // Get pointer to item in the stack.
-#define STACK_TV(idx) (((typval_T *)ectx.ec_stack.ga_data) + idx)
+#define STACK_TV(idx) (((typval_T *)ectx->ec_stack.ga_data) + idx)
 
 // Get pointer to item at the bottom of the stack, -1 is the bottom.
 #undef STACK_TV_BOT
-#define STACK_TV_BOT(idx) (((typval_T *)ectx.ec_stack.ga_data) + ectx.ec_stack.ga_len + idx)
+#define STACK_TV_BOT(idx) (((typval_T *)ectx->ec_stack.ga_data) + ectx->ec_stack.ga_len + idx)
 
 // Get pointer to a local variable on the stack.  Negative for arguments.
-#define STACK_TV_VAR(idx) (((typval_T *)ectx.ec_stack.ga_data) + ectx.ec_frame_idx + STACK_FRAME_SIZE + idx)
-
-    if (ufunc->uf_def_status == UF_NOT_COMPILED
-           || ufunc->uf_def_status == UF_COMPILE_ERROR
-           || (func_needs_compiling(ufunc, PROFILING(ufunc))
-               && compile_def_function(ufunc, FALSE, PROFILING(ufunc), NULL)
-                                                                     == FAIL))
-    {
-       if (did_emsg_cumul + did_emsg == did_emsg_before)
-           semsg(_(e_function_is_not_compiled_str),
-                                                  printable_func_name(ufunc));
-       return FAIL;
-    }
-
-    {
-       // Check the function was really compiled.
-       dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data)
-                                                        + ufunc->uf_dfunc_idx;
-       if (INSTRUCTIONS(dfunc) == NULL)
-       {
-           iemsg("using call_def_function() on not compiled function");
-           return FAIL;
-       }
-    }
+#define STACK_TV_VAR(idx) (((typval_T *)ectx->ec_stack.ga_data) + ectx->ec_frame_idx + STACK_FRAME_SIZE + idx)
 
-    // If depth of calling is getting too high, don't execute the function.
-    orig_funcdepth = funcdepth_get();
-    if (funcdepth_increment() == FAIL)
-       return FAIL;
+/*
+ * Execute instructions in execution context "ectx".
+ * Return OK or FAIL;
+ */
+    static int
+exec_instructions(ectx_T *ectx)
+{
+    int                breakcheck_count = 0;
+    typval_T   *tv;
 
-    CLEAR_FIELD(funclocal);
-    CLEAR_FIELD(ectx);
-    ectx.ec_dfunc_idx = ufunc->uf_dfunc_idx;
-    ga_init2(&ectx.ec_stack, sizeof(typval_T), 500);
-    if (ga_grow(&ectx.ec_stack, 20) == FAIL)
-    {
-       funcdepth_decrement();
-       return FAIL;
-    }
-    ga_init2(&ectx.ec_trystack, sizeof(trycmd_T), 10);
-    ga_init2(&ectx.ec_funcrefs, sizeof(partial_T *), 10);
+    // Start execution at the first instruction.
+    ectx->ec_iidx = 0;
 
-    idx = argc - ufunc->uf_args.ga_len;
-    if (idx > 0 && ufunc->uf_va_name == NULL)
+    for (;;)
     {
-       if (idx == 1)
-           emsg(_(e_one_argument_too_many));
-       else
-           semsg(_(e_nr_arguments_too_many), idx);
-       goto failed_early;
-    }
+       isn_T       *iptr;
 
-    // Put arguments on the stack, but no more than what the function expects.
-    // A lambda can be called with more arguments than it uses.
-    for (idx = 0; idx < argc
-           && (ufunc->uf_va_name != NULL || idx < ufunc->uf_args.ga_len);
-                                                                        ++idx)
-    {
-       if (idx >= ufunc->uf_args.ga_len - ufunc->uf_def_args.ga_len
-               && argv[idx].v_type == VAR_SPECIAL
-               && argv[idx].vval.v_number == VVAL_NONE)
+       if (++breakcheck_count >= 100)
        {
-           // Use the default value.
-           STACK_TV_BOT(0)->v_type = VAR_UNKNOWN;
+           line_breakcheck();
+           breakcheck_count = 0;
        }
-       else
+       if (got_int)
        {
-           if (ufunc->uf_arg_types != NULL && idx < ufunc->uf_args.ga_len
-                   && check_typval_arg_type(
-                       ufunc->uf_arg_types[idx], &argv[idx], idx + 1) == FAIL)
-               goto failed_early;
-           copy_tv(&argv[idx], STACK_TV_BOT(0));
+           // Turn CTRL-C into an exception.
+           got_int = FALSE;
+           if (throw_exception("Vim:Interrupt", ET_INTERRUPT, NULL) == FAIL)
+               return FAIL;
+           did_throw = TRUE;
        }
-       ++ectx.ec_stack.ga_len;
-    }
-
-    // Turn varargs into a list.  Empty list if no args.
-    if (ufunc->uf_va_name != NULL)
-    {
-       int vararg_count = argc - ufunc->uf_args.ga_len;
-
-       if (vararg_count < 0)
-           vararg_count = 0;
-       else
-           argc -= vararg_count;
-       if (exe_newlist(vararg_count, &ectx) == FAIL)
-           goto failed_early;
 
-       // Check the type of the list items.
-       tv = STACK_TV_BOT(-1);
-       if (ufunc->uf_va_type != NULL
-               && ufunc->uf_va_type != &t_list_any
-               && ufunc->uf_va_type->tt_member != &t_any
-               && tv->vval.v_list != NULL)
+       if (did_emsg && msg_list != NULL && *msg_list != NULL)
        {
-           type_T      *expected = ufunc->uf_va_type->tt_member;
-           listitem_T  *li = tv->vval.v_list->lv_first;
-
-           for (idx = 0; idx < vararg_count; ++idx)
-           {
-               if (check_typval_arg_type(expected, &li->li_tv,
-                                                      argc + idx + 1) == FAIL)
-                   goto failed_early;
-               li = li->li_next;
-           }
+           // Turn an error message into an exception.
+           did_emsg = FALSE;
+           if (throw_exception(*msg_list, ET_ERROR, NULL) == FAIL)
+               return FAIL;
+           did_throw = TRUE;
+           *msg_list = NULL;
        }
 
-       if (defcount > 0)
-           // Move varargs list to below missing default arguments.
-           *STACK_TV_BOT(defcount - 1) = *STACK_TV_BOT(-1);
-       --ectx.ec_stack.ga_len;
-    }
-
-    // Make space for omitted arguments, will store default value below.
-    // Any varargs list goes after them.
-    if (defcount > 0)
-       for (idx = 0; idx < defcount; ++idx)
+       if (did_throw && !ectx->ec_in_catch)
        {
-           STACK_TV_BOT(0)->v_type = VAR_UNKNOWN;
-           ++ectx.ec_stack.ga_len;
-       }
-    if (ufunc->uf_va_name != NULL)
-       ++ectx.ec_stack.ga_len;
-
-    // Frame pointer points to just after arguments.
-    ectx.ec_frame_idx = ectx.ec_stack.ga_len;
-    initial_frame_idx = ectx.ec_frame_idx;
-
-    {
-       dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data)
-                                                        + ufunc->uf_dfunc_idx;
-       ufunc_T *base_ufunc = dfunc->df_ufunc;
+           garray_T    *trystack = &ectx->ec_trystack;
+           trycmd_T    *trycmd = NULL;
 
-       // "uf_partial" is on the ufunc that "df_ufunc" points to, as is done
-       // by copy_func().
-       if (partial != NULL || base_ufunc->uf_partial != NULL)
-       {
-           ectx.ec_outer = ALLOC_CLEAR_ONE(outer_T);
-           if (ectx.ec_outer == NULL)
-               goto failed_early;
-           if (partial != NULL)
+           // An exception jumps to the first catch, finally, or returns from
+           // the current function.
+           if (trystack->ga_len > 0)
+               trycmd = ((trycmd_T *)trystack->ga_data) + trystack->ga_len - 1;
+           if (trycmd != NULL && trycmd->tcd_frame_idx == ectx->ec_frame_idx)
            {
-               if (partial->pt_outer.out_stack == NULL && current_ectx != NULL)
+               // jump to ":catch" or ":finally"
+               ectx->ec_in_catch = TRUE;
+               ectx->ec_iidx = trycmd->tcd_catch_idx;
+           }
+           else
+           {
+               // Not inside try or need to return from current functions.
+               // Push a dummy return value.
+               if (GA_GROW(&ectx->ec_stack, 1) == FAIL)
+                   return FAIL;
+               tv = STACK_TV_BOT(0);
+               tv->v_type = VAR_NUMBER;
+               tv->vval.v_number = 0;
+               ++ectx->ec_stack.ga_len;
+               if (ectx->ec_frame_idx == ectx->ec_initial_frame_idx)
                {
-                   if (current_ectx->ec_outer != NULL)
-                       *ectx.ec_outer = *current_ectx->ec_outer;
+                   // At the toplevel we are done.
+                   need_rethrow = TRUE;
+                   if (handle_closure_in_use(ectx, FALSE) == FAIL)
+                       return FAIL;
+                   goto done;
                }
-               else
-                   *ectx.ec_outer = partial->pt_outer;
+
+               if (func_return(ectx) == FAIL)
+                   return FAIL;
            }
-           else
-               *ectx.ec_outer = base_ufunc->uf_partial->pt_outer;
-           ectx.ec_outer->out_up_is_copy = TRUE;
+           continue;
        }
-    }
-
-    // dummy frame entries
-    for (idx = 0; idx < STACK_FRAME_SIZE; ++idx)
-    {
-       STACK_TV(ectx.ec_stack.ga_len)->v_type = VAR_UNKNOWN;
-       ++ectx.ec_stack.ga_len;
-    }
-
-    {
-       // Reserve space for local variables and any closure reference count.
-       dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data)
-                                                        + ufunc->uf_dfunc_idx;
 
-       for (idx = 0; idx < dfunc->df_varcount; ++idx)
-           STACK_TV_VAR(idx)->v_type = VAR_UNKNOWN;
-       ectx.ec_stack.ga_len += dfunc->df_varcount;
-       if (dfunc->df_has_closure)
-       {
-           STACK_TV_VAR(idx)->v_type = VAR_NUMBER;
-           STACK_TV_VAR(idx)->vval.v_number = 0;
-           ++ectx.ec_stack.ga_len;
-       }
-
-       ectx.ec_instr = INSTRUCTIONS(dfunc);
-    }
-
-    // Following errors are in the function, not the caller.
-    // Commands behave like vim9script.
-    estack_push_ufunc(ufunc, 1);
-    current_sctx = ufunc->uf_script_ctx;
-    current_sctx.sc_version = SCRIPT_VERSION_VIM9;
-
-    // Use a specific location for storing error messages to be converted to an
-    // exception.
-    saved_msg_list = msg_list;
-    msg_list = &private_msg_list;
-
-    // Do turn errors into exceptions.
-    suppress_errthrow = FALSE;
-
-    // Do not delete the function while executing it.
-    ++ufunc->uf_calls;
-
-    // When ":silent!" was used before calling then we still abort the
-    // function.  If ":silent!" is used in the function then we don't.
-    emsg_silent_def = emsg_silent;
-    did_emsg_def = 0;
-
-    where.wt_index = 0;
-    where.wt_variable = FALSE;
-
-    // Start execution at the first instruction.
-    ectx.ec_iidx = 0;
-
-    for (;;)
-    {
-       isn_T       *iptr;
-
-       if (++breakcheck_count >= 100)
-       {
-           line_breakcheck();
-           breakcheck_count = 0;
-       }
-       if (got_int)
-       {
-           // Turn CTRL-C into an exception.
-           got_int = FALSE;
-           if (throw_exception("Vim:Interrupt", ET_INTERRUPT, NULL) == FAIL)
-               goto failed;
-           did_throw = TRUE;
-       }
-
-       if (did_emsg && msg_list != NULL && *msg_list != NULL)
-       {
-           // Turn an error message into an exception.
-           did_emsg = FALSE;
-           if (throw_exception(*msg_list, ET_ERROR, NULL) == FAIL)
-               goto failed;
-           did_throw = TRUE;
-           *msg_list = NULL;
-       }
-
-       if (did_throw && !ectx.ec_in_catch)
-       {
-           garray_T    *trystack = &ectx.ec_trystack;
-           trycmd_T    *trycmd = NULL;
-
-           // An exception jumps to the first catch, finally, or returns from
-           // the current function.
-           if (trystack->ga_len > 0)
-               trycmd = ((trycmd_T *)trystack->ga_data) + trystack->ga_len - 1;
-           if (trycmd != NULL && trycmd->tcd_frame_idx == ectx.ec_frame_idx)
-           {
-               // jump to ":catch" or ":finally"
-               ectx.ec_in_catch = TRUE;
-               ectx.ec_iidx = trycmd->tcd_catch_idx;
-           }
-           else
-           {
-               // Not inside try or need to return from current functions.
-               // Push a dummy return value.
-               if (GA_GROW(&ectx.ec_stack, 1) == FAIL)
-                   goto failed;
-               tv = STACK_TV_BOT(0);
-               tv->v_type = VAR_NUMBER;
-               tv->vval.v_number = 0;
-               ++ectx.ec_stack.ga_len;
-               if (ectx.ec_frame_idx == initial_frame_idx)
-               {
-                   // At the toplevel we are done.
-                   need_rethrow = TRUE;
-                   if (handle_closure_in_use(&ectx, FALSE) == FAIL)
-                       goto failed;
-                   goto done;
-               }
-
-               if (func_return(&funclocal, &ectx) == FAIL)
-                   goto failed;
-           }
-           continue;
-       }
-
-       iptr = &ectx.ec_instr[ectx.ec_iidx++];
-       switch (iptr->isn_type)
+       iptr = &ectx->ec_instr[ectx->ec_iidx++];
+       switch (iptr->isn_type)
        {
            // execute Ex command line
            case ISN_EXEC:
@@ -1597,6 +1377,38 @@ call_def_function(
                }
                break;
 
+           // execute :substitute with an expression
+           case ISN_SUBSTITUTE:
+               {
+                   subs_T              *subs = &iptr->isn_arg.subs;
+                   source_cookie_T     cookie;
+                   struct subs_expr_S  *save_instr = substitute_instr;
+                   struct subs_expr_S  subs_instr;
+                   int                 res;
+
+                   subs_instr.subs_ectx = ectx;
+                   subs_instr.subs_instr = subs->subs_instr;
+                   subs_instr.subs_status = OK;
+                   substitute_instr = &subs_instr;
+
+                   SOURCING_LNUM = iptr->isn_lnum;
+                   // This is very much like ISN_EXEC
+                   CLEAR_FIELD(cookie);
+                   cookie.sourcing_lnum = iptr->isn_lnum - 1;
+                   res = do_cmdline(subs->subs_cmd,
+                               getsourceline, &cookie,
+                                  DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_KEYTYPED);
+                   substitute_instr = save_instr;
+
+                   if (res == FAIL || did_emsg
+                                            || subs_instr.subs_status == FAIL)
+                       goto on_error;
+               }
+               break;
+
+           case ISN_FINISH:
+               goto done;
+
            // execute Ex command from pieces on the stack
            case ISN_EXECCONCAT:
                {
@@ -1626,7 +1438,7 @@ call_def_function(
                        {
                            cmd = alloc(len + 1);
                            if (cmd == NULL)
-                               goto failed;
+                               return FAIL;
                            len = 0;
                        }
                    }
@@ -1643,6 +1455,7 @@ call_def_function(
                    int count = iptr->isn_arg.echo.echo_count;
                    int atstart = TRUE;
                    int needclr = TRUE;
+                   int idx;
 
                    for (idx = 0; idx < count; ++idx)
                    {
@@ -1653,7 +1466,7 @@ call_def_function(
                    }
                    if (needclr)
                        msg_clr_eos();
-                   ectx.ec_stack.ga_len -= count;
+                   ectx->ec_stack.ga_len -= count;
                }
                break;
 
@@ -1670,6 +1483,7 @@ call_def_function(
                    char_u      *p;
                    int         len;
                    int         failed = FALSE;
+                   int         idx;
 
                    ga_init2(&ga, 1, 80);
                    for (idx = 0; idx < count; ++idx)
@@ -1702,7 +1516,7 @@ call_def_function(
                        }
                        clear_tv(tv);
                    }
-                   ectx.ec_stack.ga_len -= count;
+                   ectx->ec_stack.ga_len -= count;
                    if (failed)
                    {
                        ga_clear(&ga);
@@ -1742,18 +1556,18 @@ call_def_function(
 
            // load local variable or argument
            case ISN_LOAD:
-               if (GA_GROW(&ectx.ec_stack, 1) == FAIL)
-                   goto failed;
+               if (GA_GROW(&ectx->ec_stack, 1) == FAIL)
+                   return FAIL;
                copy_tv(STACK_TV_VAR(iptr->isn_arg.number), STACK_TV_BOT(0));
-               ++ectx.ec_stack.ga_len;
+               ++ectx->ec_stack.ga_len;
                break;
 
            // load v: variable
            case ISN_LOADV:
-               if (GA_GROW(&ectx.ec_stack, 1) == FAIL)
-                   goto failed;
+               if (GA_GROW(&ectx->ec_stack, 1) == FAIL)
+                   return FAIL;
                copy_tv(get_vim_var_tv(iptr->isn_arg.number), STACK_TV_BOT(0));
-               ++ectx.ec_stack.ga_len;
+               ++ectx->ec_stack.ga_len;
                break;
 
            // load s: variable in Vim9 script
@@ -1762,14 +1576,14 @@ call_def_function(
                    scriptref_T *sref = iptr->isn_arg.script.scriptref;
                    svar_T       *sv;
 
-                   sv = get_script_svar(sref, &ectx);
+                   sv = get_script_svar(sref, ectx);
                    if (sv == NULL)
-                       goto failed;
+                       return FAIL;
                    allocate_if_null(sv->sv_tv);
-                   if (GA_GROW(&ectx.ec_stack, 1) == FAIL)
-                       goto failed;
+                   if (GA_GROW(&ectx->ec_stack, 1) == FAIL)
+                       return FAIL;
                    copy_tv(sv->sv_tv, STACK_TV_BOT(0));
-                   ++ectx.ec_stack.ga_len;
+                   ++ectx->ec_stack.ga_len;
                }
                break;
 
@@ -1789,10 +1603,10 @@ call_def_function(
                    }
                    else
                    {
-                       if (GA_GROW(&ectx.ec_stack, 1) == FAIL)
-                           goto failed;
+                       if (GA_GROW(&ectx->ec_stack, 1) == FAIL)
+                           return FAIL;
                        copy_tv(&di->di_tv, STACK_TV_BOT(0));
-                       ++ectx.ec_stack.ga_len;
+                       ++ectx->ec_stack.ga_len;
                    }
                }
                break;
@@ -1826,7 +1640,7 @@ call_def_function(
                            namespace = 't';
                            break;
                        default:  // Cannot reach here
-                           goto failed;
+                           return FAIL;
                    }
                    di = find_var_in_ht(ht, 0, iptr->isn_arg.string, TRUE);
 
@@ -1839,10 +1653,10 @@ call_def_function(
                    }
                    else
                    {
-                       if (GA_GROW(&ectx.ec_stack, 1) == FAIL)
-                           goto failed;
+                       if (GA_GROW(&ectx->ec_stack, 1) == FAIL)
+                           return FAIL;
                        copy_tv(&di->di_tv, STACK_TV_BOT(0));
-                       ++ectx.ec_stack.ga_len;
+                       ++ectx->ec_stack.ga_len;
                    }
                }
                break;
@@ -1852,13 +1666,13 @@ call_def_function(
                {
                    char_u *name = iptr->isn_arg.string;
 
-                   if (GA_GROW(&ectx.ec_stack, 1) == FAIL)
-                       goto failed;
+                   if (GA_GROW(&ectx->ec_stack, 1) == FAIL)
+                       return FAIL;
                    SOURCING_LNUM = iptr->isn_lnum;
                    if (eval_variable(name, (int)STRLEN(name),
                              STACK_TV_BOT(0), NULL, EVAL_VAR_VERBOSE) == FAIL)
                        goto on_error;
-                   ++ectx.ec_stack.ga_len;
+                   ++ectx->ec_stack.ga_len;
                }
                break;
 
@@ -1877,16 +1691,16 @@ call_def_function(
                        case ISN_LOADWDICT: d = curwin->w_vars; break;
                        case ISN_LOADTDICT: d = curtab->tp_vars; break;
                        default:  // Cannot reach here
-                           goto failed;
+                           return FAIL;
                    }
-                   if (GA_GROW(&ectx.ec_stack, 1) == FAIL)
-                       goto failed;
+                   if (GA_GROW(&ectx->ec_stack, 1) == FAIL)
+                       return FAIL;
                    tv = STACK_TV_BOT(0);
                    tv->v_type = VAR_DICT;
                    tv->v_lock = 0;
                    tv->vval.v_dict = d;
                    ++d->dv_refcount;
-                   ++ectx.ec_stack.ga_len;
+                   ++ectx->ec_stack.ga_len;
                }
                break;
 
@@ -1898,12 +1712,12 @@ call_def_function(
 
                    // This is not expected to fail, name is checked during
                    // compilation: don't set SOURCING_LNUM.
-                   if (GA_GROW(&ectx.ec_stack, 1) == FAIL)
-                       goto failed;
+                   if (GA_GROW(&ectx->ec_stack, 1) == FAIL)
+                       return FAIL;
                    if (eval_option(&name, &optval, TRUE) == FAIL)
-                       goto failed;
+                       return FAIL;
                    *STACK_TV_BOT(0) = optval;
-                   ++ectx.ec_stack.ga_len;
+                   ++ectx->ec_stack.ga_len;
                }
                break;
 
@@ -1913,19 +1727,19 @@ call_def_function(
                    typval_T    optval;
                    char_u      *name = iptr->isn_arg.string;
 
-                   if (GA_GROW(&ectx.ec_stack, 1) == FAIL)
-                       goto failed;
+                   if (GA_GROW(&ectx->ec_stack, 1) == FAIL)
+                       return FAIL;
                    // name is always valid, checked when compiling
                    (void)eval_env_var(&name, &optval, TRUE);
                    *STACK_TV_BOT(0) = optval;
-                   ++ectx.ec_stack.ga_len;
+                   ++ectx->ec_stack.ga_len;
                }
                break;
 
            // load @register
            case ISN_LOADREG:
-               if (GA_GROW(&ectx.ec_stack, 1) == FAIL)
-                   goto failed;
+               if (GA_GROW(&ectx->ec_stack, 1) == FAIL)
+                   return FAIL;
                tv = STACK_TV_BOT(0);
                tv->v_type = VAR_STRING;
                tv->v_lock = 0;
@@ -1933,12 +1747,12 @@ call_def_function(
                // empty string.
                tv->vval.v_string = get_reg_contents(
                                          iptr->isn_arg.number, GREG_EXPR_SRC);
-               ++ectx.ec_stack.ga_len;
+               ++ectx->ec_stack.ga_len;
                break;
 
            // store local variable
            case ISN_STORE:
-               --ectx.ec_stack.ga_len;
+               --ectx->ec_stack.ga_len;
                tv = STACK_TV_VAR(iptr->isn_arg.number);
                clear_tv(tv);
                *tv = *STACK_TV_BOT(0);
@@ -1952,7 +1766,7 @@ call_def_function(
                    char_u      *name = iptr->isn_arg.loadstore.ls_name;
                    dictitem_T  *di = find_var_in_ht(ht, 0, name + 2, TRUE);
 
-                   --ectx.ec_stack.ga_len;
+                   --ectx->ec_stack.ga_len;
                    if (di == NULL)
                        store_var(name, STACK_TV_BOT(0));
                    else
@@ -1975,10 +1789,10 @@ call_def_function(
                    scriptref_T     *sref = iptr->isn_arg.script.scriptref;
                    svar_T          *sv;
 
-                   sv = get_script_svar(sref, &ectx);
+                   sv = get_script_svar(sref, ectx);
                    if (sv == NULL)
-                       goto failed;
-                   --ectx.ec_stack.ga_len;
+                       return FAIL;
+                   --ectx->ec_stack.ga_len;
 
                    // "const" and "final" are checked at compile time, locking
                    // the value needs to be checked here.
@@ -2001,7 +1815,7 @@ call_def_function(
                    char_u      *s = NULL;
                    char        *msg;
 
-                   --ectx.ec_stack.ga_len;
+                   --ectx->ec_stack.ga_len;
                    tv = STACK_TV_BOT(0);
                    if (tv->v_type == VAR_STRING)
                    {
@@ -2026,7 +1840,7 @@ call_def_function(
 
            // store $ENV
            case ISN_STOREENV:
-               --ectx.ec_stack.ga_len;
+               --ectx->ec_stack.ga_len;
                tv = STACK_TV_BOT(0);
                vim_setenv_ext(iptr->isn_arg.string, tv_get_string(tv));
                clear_tv(tv);
@@ -2037,7 +1851,7 @@ call_def_function(
                {
                    int reg = iptr->isn_arg.number;
 
-                   --ectx.ec_stack.ga_len;
+                   --ectx->ec_stack.ga_len;
                    tv = STACK_TV_BOT(0);
                    write_reg_contents(reg == '@' ? '"' : reg,
                                                 tv_get_string(tv), -1, FALSE);
@@ -2047,7 +1861,7 @@ call_def_function(
 
            // store v: variable
            case ISN_STOREV:
-               --ectx.ec_stack.ga_len;
+               --ectx->ec_stack.ga_len;
                if (set_vim_var_tv(iptr->isn_arg.number, STACK_TV_BOT(0))
                                                                       == FAIL)
                    // should not happen, type is checked when compiling
@@ -2079,10 +1893,10 @@ call_def_function(
                            ht = &curtab->tp_vars->dv_hashtab;
                            break;
                        default:  // Cannot reach here
-                           goto failed;
+                           return FAIL;
                    }
 
-                   --ectx.ec_stack.ga_len;
+                   --ectx->ec_stack.ga_len;
                    di = find_var_in_ht(ht, 0, name, TRUE);
                    if (di == NULL)
                        store_var(iptr->isn_arg.string, STACK_TV_BOT(0));
@@ -2102,7 +1916,7 @@ call_def_function(
                SOURCING_LNUM = iptr->isn_lnum;
                set_var(iptr->isn_arg.string, STACK_TV_BOT(-1), TRUE);
                clear_tv(STACK_TV_BOT(-1));
-               --ectx.ec_stack.ga_len;
+               --ectx->ec_stack.ga_len;
                break;
 
            // store number in local variable
@@ -2184,7 +1998,7 @@ call_def_function(
                                goto on_error;
                            // append to list, only fails when out of memory
                            if (list_append_tv(list, tv) == FAIL)
-                               goto failed;
+                               return FAIL;
                            clear_tv(tv);
                        }
                    }
@@ -2219,7 +2033,7 @@ call_def_function(
                                goto on_error;
                            // add to dict, only fails when out of memory
                            if (dict_add_tv(dict, (char *)key, tv) == FAIL)
-                               goto failed;
+                               return FAIL;
                            clear_tv(tv);
                        }
                    }
@@ -2263,7 +2077,7 @@ call_def_function(
 
                    clear_tv(tv_idx);
                    clear_tv(tv_dest);
-                   ectx.ec_stack.ga_len -= 3;
+                   ectx->ec_stack.ga_len -= 3;
                    if (status == FAIL)
                    {
                        clear_tv(tv);
@@ -2328,7 +2142,7 @@ call_def_function(
                    clear_tv(tv_idx1);
                    clear_tv(tv_idx2);
                    clear_tv(tv_dest);
-                   ectx.ec_stack.ga_len -= 4;
+                   ectx->ec_stack.ga_len -= 4;
                    clear_tv(tv);
 
                    if (status == FAIL)
@@ -2341,7 +2155,7 @@ call_def_function(
            case ISN_STOREOUTER:
                {
                    int         depth = iptr->isn_arg.outer.outer_depth;
-                   outer_T     *outer = ectx.ec_outer;
+                   outer_T     *outer = ectx->ec_outer;
 
                    while (depth > 1 && outer != NULL)
                    {
@@ -2352,21 +2166,21 @@ call_def_function(
                    {
                        SOURCING_LNUM = iptr->isn_lnum;
                        iemsg("LOADOUTER depth more than scope levels");
-                       goto failed;
+                       return FAIL;
                    }
                    tv = ((typval_T *)outer->out_stack->ga_data)
                                    + outer->out_frame_idx + STACK_FRAME_SIZE
                                    + iptr->isn_arg.outer.outer_idx;
                    if (iptr->isn_type == ISN_LOADOUTER)
                    {
-                       if (GA_GROW(&ectx.ec_stack, 1) == FAIL)
-                           goto failed;
+                       if (GA_GROW(&ectx->ec_stack, 1) == FAIL)
+                           return FAIL;
                        copy_tv(tv, STACK_TV_BOT(0));
-                       ++ectx.ec_stack.ga_len;
+                       ++ectx->ec_stack.ga_len;
                    }
                    else
                    {
-                       --ectx.ec_stack.ga_len;
+                       --ectx->ec_stack.ga_len;
                        clear_tv(tv);
                        *tv = *STACK_TV_BOT(0);
                    }
@@ -2453,7 +2267,7 @@ call_def_function(
 
                    clear_tv(tv_idx);
                    clear_tv(tv_dest);
-                   ectx.ec_stack.ga_len -= 2;
+                   ectx->ec_stack.ga_len -= 2;
                    if (status == FAIL)
                        goto on_error;
                }
@@ -2505,7 +2319,7 @@ call_def_function(
                    clear_tv(tv_idx1);
                    clear_tv(tv_idx2);
                    clear_tv(tv_dest);
-                   ectx.ec_stack.ga_len -= 3;
+                   ectx->ec_stack.ga_len -= 3;
                    if (status == FAIL)
                        goto on_error;
                }
@@ -2521,11 +2335,11 @@ call_def_function(
            case ISN_PUSHFUNC:
            case ISN_PUSHCHANNEL:
            case ISN_PUSHJOB:
-               if (GA_GROW(&ectx.ec_stack, 1) == FAIL)
-                   goto failed;
+               if (GA_GROW(&ectx->ec_stack, 1) == FAIL)
+                   return FAIL;
                tv = STACK_TV_BOT(0);
                tv->v_lock = 0;
-               ++ectx.ec_stack.ga_len;
+               ++ectx->ec_stack.ga_len;
                switch (iptr->isn_type)
                {
                    case ISN_PUSHNR:
@@ -2597,8 +2411,8 @@ call_def_function(
            // create a list from items on the stack; uses a single allocation
            // for the list header and the items
            case ISN_NEWLIST:
-               if (exe_newlist(iptr->isn_arg.number, &ectx) == FAIL)
-                   goto failed;
+               if (exe_newlist(iptr->isn_arg.number, ectx) == FAIL)
+                   return FAIL;
                break;
 
            // create a dict from items on the stack
@@ -2608,9 +2422,10 @@ call_def_function(
                    dict_T      *dict = dict_alloc();
                    dictitem_T  *item;
                    char_u      *key;
+                   int         idx;
 
                    if (dict == NULL)
-                       goto failed;
+                       return FAIL;
                    for (idx = 0; idx < count; ++idx)
                    {
                        // have already checked key type is VAR_STRING
@@ -2631,7 +2446,7 @@ call_def_function(
                        if (item == NULL)
                        {
                            dict_unref(dict);
-                           goto failed;
+                           return FAIL;
                        }
                        item->di_tv = *STACK_TV_BOT(2 * (idx - count) + 1);
                        item->di_tv.v_lock = 0;
@@ -2639,16 +2454,16 @@ call_def_function(
                        {
                            // can this ever happen?
                            dict_unref(dict);
-                           goto failed;
+                           return FAIL;
                        }
                    }
 
                    if (count > 0)
-                       ectx.ec_stack.ga_len -= 2 * count - 1;
-                   else if (GA_GROW(&ectx.ec_stack, 1) == FAIL)
-                       goto failed;
+                       ectx->ec_stack.ga_len -= 2 * count - 1;
+                   else if (GA_GROW(&ectx->ec_stack, 1) == FAIL)
+                       return FAIL;
                    else
-                       ++ectx.ec_stack.ga_len;
+                       ++ectx->ec_stack.ga_len;
                    tv = STACK_TV_BOT(-1);
                    tv->v_type = VAR_DICT;
                    tv->v_lock = 0;
@@ -2663,8 +2478,7 @@ call_def_function(
                if (call_dfunc(iptr->isn_arg.dfunc.cdf_idx,
                                NULL,
                                iptr->isn_arg.dfunc.cdf_argcount,
-                               &funclocal,
-                               &ectx) == FAIL)
+                               ectx) == FAIL)
                    goto on_error;
                break;
 
@@ -2673,7 +2487,7 @@ call_def_function(
                SOURCING_LNUM = iptr->isn_lnum;
                if (call_bfunc(iptr->isn_arg.bfunc.cbf_idx,
                              iptr->isn_arg.bfunc.cbf_argcount,
-                             &ectx) == FAIL)
+                             ectx) == FAIL)
                    goto on_error;
                break;
 
@@ -2693,12 +2507,11 @@ call_def_function(
                    else
                    {
                        // Get the funcref from the stack.
-                       --ectx.ec_stack.ga_len;
+                       --ectx->ec_stack.ga_len;
                        partial_tv = *STACK_TV_BOT(0);
                        tv = &partial_tv;
                    }
-                   r = call_partial(tv, pfunc->cpf_argcount,
-                                                           &funclocal, &ectx);
+                   r = call_partial(tv, pfunc->cpf_argcount, ectx);
                    if (tv == &partial_tv)
                        clear_tv(&partial_tv);
                    if (r == FAIL)
@@ -2710,7 +2523,7 @@ call_def_function(
                // PCALL finished, arguments have been consumed and replaced by
                // the return value.  Now clear the funcref from the stack,
                // and move the return value in its place.
-               --ectx.ec_stack.ga_len;
+               --ectx->ec_stack.ga_len;
                clear_tv(STACK_TV_BOT(-1));
                *STACK_TV_BOT(-1) = *STACK_TV_BOT(0);
                break;
@@ -2722,17 +2535,17 @@ call_def_function(
 
                    SOURCING_LNUM = iptr->isn_lnum;
                    if (call_eval_func(cufunc->cuf_name, cufunc->cuf_argcount,
-                                             &funclocal, &ectx, iptr) == FAIL)
+                                                          ectx, iptr) == FAIL)
                        goto on_error;
                }
                break;
 
            // return from a :def function call
            case ISN_RETURN_ZERO:
-               if (GA_GROW(&ectx.ec_stack, 1) == FAIL)
-                   goto failed;
+               if (GA_GROW(&ectx->ec_stack, 1) == FAIL)
+                   return FAIL;
                tv = STACK_TV_BOT(0);
-               ++ectx.ec_stack.ga_len;
+               ++ectx->ec_stack.ga_len;
                tv->v_type = VAR_NUMBER;
                tv->vval.v_number = 0;
                tv->v_lock = 0;
@@ -2740,20 +2553,20 @@ call_def_function(
 
            case ISN_RETURN:
                {
-                   garray_T    *trystack = &ectx.ec_trystack;
+                   garray_T    *trystack = &ectx->ec_trystack;
                    trycmd_T    *trycmd = NULL;
 
                    if (trystack->ga_len > 0)
                        trycmd = ((trycmd_T *)trystack->ga_data)
                                                        + trystack->ga_len - 1;
                    if (trycmd != NULL
-                                && trycmd->tcd_frame_idx == ectx.ec_frame_idx)
+                                && trycmd->tcd_frame_idx == ectx->ec_frame_idx)
                    {
                        // jump to ":finally" or ":endtry"
                        if (trycmd->tcd_finally_idx != 0)
-                           ectx.ec_iidx = trycmd->tcd_finally_idx;
+                           ectx->ec_iidx = trycmd->tcd_finally_idx;
                        else
-                           ectx.ec_iidx = trycmd->tcd_endtry_idx;
+                           ectx->ec_iidx = trycmd->tcd_endtry_idx;
                        trycmd->tcd_return = TRUE;
                    }
                    else
@@ -2769,18 +2582,18 @@ call_def_function(
                                               + iptr->isn_arg.funcref.fr_func;
 
                    if (pt == NULL)
-                       goto failed;
-                   if (GA_GROW(&ectx.ec_stack, 1) == FAIL)
+                       return FAIL;
+                   if (GA_GROW(&ectx->ec_stack, 1) == FAIL)
                    {
                        vim_free(pt);
-                       goto failed;
+                       return FAIL;
                    }
                    if (fill_partial_and_closure(pt, pt_dfunc->df_ufunc,
-                                                               &ectx) == FAIL)
-                       goto failed;
+                                                                ectx) == FAIL)
+                       return FAIL;
 
                    tv = STACK_TV_BOT(0);
-                   ++ectx.ec_stack.ga_len;
+                   ++ectx->ec_stack.ga_len;
                    tv->vval.v_partial = pt;
                    tv->v_type = VAR_PARTIAL;
                    tv->v_lock = 0;
@@ -2793,8 +2606,8 @@ call_def_function(
                    newfunc_T   *newfunc = &iptr->isn_arg.newfunc;
 
                    if (copy_func(newfunc->nf_lambda, newfunc->nf_global,
-                                                               &ectx) == FAIL)
-                       goto failed;
+                                                                ectx) == FAIL)
+                       return FAIL;
                }
                break;
 
@@ -2841,11 +2654,11 @@ call_def_function(
                        {
                            // drop the value from the stack
                            clear_tv(tv);
-                           --ectx.ec_stack.ga_len;
+                           --ectx->ec_stack.ga_len;
                        }
                    }
                    if (jump)
-                       ectx.ec_iidx = iptr->isn_arg.jump.jump_where;
+                       ectx->ec_iidx = iptr->isn_arg.jump.jump_where;
                }
                break;
 
@@ -2856,7 +2669,7 @@ call_def_function(
                if (tv->v_type != VAR_UNKNOWN
                        && !(tv->v_type == VAR_SPECIAL
                                            && tv->vval.v_number == VVAL_NONE))
-                   ectx.ec_iidx = iptr->isn_arg.jumparg.jump_where;
+                   ectx->ec_iidx = iptr->isn_arg.jumparg.jump_where;
                break;
 
            // top of a for loop
@@ -2866,8 +2679,8 @@ call_def_function(
                    typval_T    *idxtv =
                                   STACK_TV_VAR(iptr->isn_arg.forloop.for_idx);
 
-                   if (GA_GROW(&ectx.ec_stack, 1) == FAIL)
-                       goto failed;
+                   if (GA_GROW(&ectx->ec_stack, 1) == FAIL)
+                       return FAIL;
                    if (ltv->v_type == VAR_LIST)
                    {
                        list_T *list = ltv->vval.v_list;
@@ -2878,8 +2691,8 @@ call_def_function(
                                       || 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(&funclocal);
+                           ectx->ec_iidx = iptr->isn_arg.forloop.for_end;
+                           may_restore_cmdmod(&ectx->ec_funclocal);
                        }
                        else if (list->lv_first == &range_list_item)
                        {
@@ -2889,7 +2702,7 @@ call_def_function(
                            tv->v_lock = 0;
                            tv->vval.v_number = list_find_nr(
                                             list, idxtv->vval.v_number, NULL);
-                           ++ectx.ec_stack.ga_len;
+                           ++ectx->ec_stack.ga_len;
                        }
                        else
                        {
@@ -2897,7 +2710,7 @@ call_def_function(
                                                         idxtv->vval.v_number);
 
                            copy_tv(&li->li_tv, STACK_TV_BOT(0));
-                           ++ectx.ec_stack.ga_len;
+                           ++ectx->ec_stack.ga_len;
                        }
                    }
                    else if (ltv->v_type == VAR_STRING)
@@ -2910,8 +2723,8 @@ call_def_function(
                        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(&funclocal);
+                           ectx->ec_iidx = iptr->isn_arg.forloop.for_end;
+                           may_restore_cmdmod(&ectx->ec_funclocal);
                        }
                        else
                        {
@@ -2922,7 +2735,7 @@ call_def_function(
                            tv->v_type = VAR_STRING;
                            tv->vval.v_string = vim_strnsave(
                                             str + idxtv->vval.v_number, clen);
-                           ++ectx.ec_stack.ga_len;
+                           ++ectx->ec_stack.ga_len;
                            idxtv->vval.v_number += clen - 1;
                        }
                    }
@@ -2946,8 +2759,8 @@ call_def_function(
                                     || 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(&funclocal);
+                           ectx->ec_iidx = iptr->isn_arg.forloop.for_end;
+                           may_restore_cmdmod(&ectx->ec_funclocal);
                        }
                        else
                        {
@@ -2956,14 +2769,14 @@ call_def_function(
                            tv->v_type = VAR_NUMBER;
                            tv->vval.v_number = blob_get(blob,
                                                         idxtv->vval.v_number);
-                           ++ectx.ec_stack.ga_len;
+                           ++ectx->ec_stack.ga_len;
                        }
                    }
                    else
                    {
                        semsg(_(e_for_loop_on_str_not_supported),
                                                    vartype_name(ltv->v_type));
-                       goto failed;
+                       return FAIL;
                    }
                }
                break;
@@ -2973,15 +2786,15 @@ call_def_function(
                {
                    trycmd_T    *trycmd = NULL;
 
-                   if (GA_GROW(&ectx.ec_trystack, 1) == FAIL)
-                       goto failed;
-                   trycmd = ((trycmd_T *)ectx.ec_trystack.ga_data)
-                                                    + ectx.ec_trystack.ga_len;
-                   ++ectx.ec_trystack.ga_len;
+                   if (GA_GROW(&ectx->ec_trystack, 1) == FAIL)
+                       return FAIL;
+                   trycmd = ((trycmd_T *)ectx->ec_trystack.ga_data)
+                                                    + ectx->ec_trystack.ga_len;
+                   ++ectx->ec_trystack.ga_len;
                    ++trylevel;
                    CLEAR_POINTER(trycmd);
-                   trycmd->tcd_frame_idx = ectx.ec_frame_idx;
-                   trycmd->tcd_stack_len = ectx.ec_stack.ga_len;
+                   trycmd->tcd_frame_idx = ectx->ec_frame_idx;
+                   trycmd->tcd_stack_len = ectx->ec_stack.ga_len;
                    trycmd->tcd_catch_idx =
                                          iptr->isn_arg.try.try_ref->try_catch;
                    trycmd->tcd_finally_idx =
@@ -2996,12 +2809,12 @@ call_def_function(
                {
                    SOURCING_LNUM = iptr->isn_lnum;
                    iemsg("Evaluating catch while current_exception is NULL");
-                   goto failed;
+                   return FAIL;
                }
-               if (GA_GROW(&ectx.ec_stack, 1) == FAIL)
-                   goto failed;
+               if (GA_GROW(&ectx->ec_stack, 1) == FAIL)
+                   return FAIL;
                tv = STACK_TV_BOT(0);
-               ++ectx.ec_stack.ga_len;
+               ++ectx->ec_stack.ga_len;
                tv->v_type = VAR_STRING;
                tv->v_lock = 0;
                tv->vval.v_string = vim_strsave(
@@ -3010,9 +2823,9 @@ call_def_function(
 
            case ISN_CATCH:
                {
-                   garray_T    *trystack = &ectx.ec_trystack;
+                   garray_T    *trystack = &ectx->ec_trystack;
 
-                   may_restore_cmdmod(&funclocal);
+                   may_restore_cmdmod(&ectx->ec_funclocal);
                    if (trystack->ga_len > 0)
                    {
                        trycmd_T    *trycmd = ((trycmd_T *)trystack->ga_data)
@@ -3027,7 +2840,7 @@ call_def_function(
 
            case ISN_TRYCONT:
                {
-                   garray_T    *trystack = &ectx.ec_trystack;
+                   garray_T    *trystack = &ectx->ec_trystack;
                    trycont_T   *trycont = &iptr->isn_arg.trycont;
                    int         i;
                    trycmd_T    *trycmd;
@@ -3037,7 +2850,7 @@ call_def_function(
                    {
                        siemsg("TRYCONT: expected %d levels, found %d",
                                        trycont->tct_levels, trystack->ga_len);
-                       goto failed;
+                       return FAIL;
                    }
                    // Make :endtry jump to any outer try block and the last
                    // :endtry inside the loop to the loop start.
@@ -3052,13 +2865,13 @@ call_def_function(
                            ? trycmd->tcd_endtry_idx : trycmd->tcd_finally_idx;
                    }
                    // jump to :finally or :endtry of current try statement
-                   ectx.ec_iidx = iidx;
+                   ectx->ec_iidx = iidx;
                }
                break;
 
            case ISN_FINALLY:
                {
-                   garray_T    *trystack = &ectx.ec_trystack;
+                   garray_T    *trystack = &ectx->ec_trystack;
                    trycmd_T    *trycmd = ((trycmd_T *)trystack->ga_data)
                                                        + trystack->ga_len - 1;
 
@@ -3071,7 +2884,7 @@ call_def_function(
            // end of ":try" block
            case ISN_ENDTRY:
                {
-                   garray_T    *trystack = &ectx.ec_trystack;
+                   garray_T    *trystack = &ectx->ec_trystack;
 
                    if (trystack->ga_len > 0)
                    {
@@ -3079,7 +2892,7 @@ call_def_function(
 
                        --trystack->ga_len;
                        --trylevel;
-                       ectx.ec_in_catch = FALSE;
+                       ectx->ec_in_catch = FALSE;
                        trycmd = ((trycmd_T *)trystack->ga_data)
                                                            + trystack->ga_len;
                        if (trycmd->tcd_caught && current_exception != NULL)
@@ -3093,22 +2906,22 @@ call_def_function(
                        if (trycmd->tcd_return)
                            goto func_return;
 
-                       while (ectx.ec_stack.ga_len > trycmd->tcd_stack_len)
+                       while (ectx->ec_stack.ga_len > trycmd->tcd_stack_len)
                        {
-                           --ectx.ec_stack.ga_len;
+                           --ectx->ec_stack.ga_len;
                            clear_tv(STACK_TV_BOT(0));
                        }
                        if (trycmd->tcd_cont != 0)
                            // handling :continue: jump to outer try block or
                            // start of the loop
-                           ectx.ec_iidx = trycmd->tcd_cont - 1;
+                           ectx->ec_iidx = trycmd->tcd_cont - 1;
                    }
                }
                break;
 
            case ISN_THROW:
                {
-                   garray_T    *trystack = &ectx.ec_trystack;
+                   garray_T    *trystack = &ectx->ec_trystack;
 
                    if (trystack->ga_len == 0 && trylevel == 0 && emsg_silent)
                    {
@@ -3120,7 +2933,7 @@ call_def_function(
                        tv->vval.v_number = 0;
                        goto done;
                    }
-                   --ectx.ec_stack.ga_len;
+                   --ectx->ec_stack.ga_len;
                    tv = STACK_TV_BOT(0);
                    if (tv->vval.v_string == NULL
                                       || *skipwhite(tv->vval.v_string) == NUL)
@@ -3128,7 +2941,7 @@ call_def_function(
                        vim_free(tv->vval.v_string);
                        SOURCING_LNUM = iptr->isn_lnum;
                        emsg(_(e_throw_with_empty_string));
-                       goto failed;
+                       return FAIL;
                    }
 
                    // Inside a "catch" we need to first discard the caught
@@ -3151,7 +2964,7 @@ call_def_function(
                                                                       == FAIL)
                    {
                        vim_free(tv->vval.v_string);
-                       goto failed;
+                       return FAIL;
                    }
                    did_throw = TRUE;
                }
@@ -3174,7 +2987,7 @@ call_def_function(
                        default: res = 0; break;
                    }
 
-                   --ectx.ec_stack.ga_len;
+                   --ectx->ec_stack.ga_len;
                    tv1->v_type = VAR_BOOL;
                    tv1->vval.v_number = res ? VVAL_TRUE : VVAL_FALSE;
                }
@@ -3207,7 +3020,7 @@ call_def_function(
                        default: res = 0; break;
                    }
 
-                   --ectx.ec_stack.ga_len;
+                   --ectx->ec_stack.ga_len;
                    if (iptr->isn_type == ISN_COMPARENR)
                    {
                        tv1->v_type = VAR_BOOL;
@@ -3245,7 +3058,7 @@ call_def_function(
                        case EXPR_SEQUAL: cmp = arg1 <= arg2; break;
                        default: cmp = 0; break;
                    }
-                   --ectx.ec_stack.ga_len;
+                   --ectx->ec_stack.ga_len;
                    if (iptr->isn_type == ISN_COMPAREFLOAT)
                    {
                        tv1->v_type = VAR_BOOL;
@@ -3276,7 +3089,7 @@ call_def_function(
                        case EXPR_ISNOT: cmp = arg1 != arg2; break;
                        default: cmp = 0; break;
                    }
-                   --ectx.ec_stack.ga_len;
+                   --ectx->ec_stack.ga_len;
                    clear_tv(tv1);
                    clear_tv(tv2);
                    tv1->v_type = VAR_BOOL;
@@ -3300,7 +3113,7 @@ call_def_function(
                        case EXPR_ISNOT: cmp = arg1 != arg2; break;
                        default: cmp = 0; break;
                    }
-                   --ectx.ec_stack.ga_len;
+                   --ectx->ec_stack.ga_len;
                    clear_tv(tv1);
                    clear_tv(tv2);
                    tv1->v_type = VAR_BOOL;
@@ -3322,7 +3135,7 @@ call_def_function(
                    SOURCING_LNUM = iptr->isn_lnum;
                    typval_compare(tv1, tv2, exprtype, ic);
                    clear_tv(tv2);
-                   --ectx.ec_stack.ga_len;
+                   --ectx->ec_stack.ga_len;
                }
                break;
 
@@ -3338,7 +3151,7 @@ call_def_function(
                    else
                        eval_addblob(tv1, tv2);
                    clear_tv(tv2);
-                   --ectx.ec_stack.ga_len;
+                   --ectx->ec_stack.ga_len;
                }
                break;
 
@@ -3356,9 +3169,9 @@ call_def_function(
                        goto on_error;
                    }
                    if (list_append_tv(l, tv2) == FAIL)
-                       goto failed;
+                       return FAIL;
                    clear_tv(tv2);
-                   --ectx.ec_stack.ga_len;
+                   --ectx->ec_stack.ga_len;
                }
                break;
 
@@ -3381,7 +3194,7 @@ call_def_function(
                    if (error)
                        goto on_error;
                    ga_append(&b->bv_ga, (int)n);
-                   --ectx.ec_stack.ga_len;
+                   --ectx->ec_stack.ga_len;
                }
                break;
 
@@ -3402,7 +3215,7 @@ call_def_function(
                        {
                            eval_addlist(tv1, tv2);
                            clear_tv(tv2);
-                           --ectx.ec_stack.ga_len;
+                           --ectx->ec_stack.ga_len;
                            break;
                        }
                        else if (tv1->v_type == VAR_BLOB
@@ -3410,7 +3223,7 @@ call_def_function(
                        {
                            eval_addblob(tv1, tv2);
                            clear_tv(tv2);
-                           --ectx.ec_stack.ga_len;
+                           --ectx->ec_stack.ga_len;
                            break;
                        }
                    }
@@ -3467,7 +3280,7 @@ call_def_function(
                        clear_tv(tv2);
                        tv1->v_type = VAR_FLOAT;
                        tv1->vval.v_float = f1;
-                       --ectx.ec_stack.ga_len;
+                       --ectx->ec_stack.ga_len;
                    }
                    else
 #endif
@@ -3492,7 +3305,7 @@ call_def_function(
                        clear_tv(tv2);
                        tv1->v_type = VAR_NUMBER;
                        tv1->vval.v_number = n1;
-                       --ectx.ec_stack.ga_len;
+                       --ectx->ec_stack.ga_len;
                    }
                }
                break;
@@ -3506,7 +3319,7 @@ call_def_function(
                    res = concat_str(str1, str2);
                    clear_tv(STACK_TV_BOT(-2));
                    clear_tv(STACK_TV_BOT(-1));
-                   --ectx.ec_stack.ga_len;
+                   --ectx->ec_stack.ga_len;
                    STACK_TV_BOT(-1)->vval.v_string = res;
                }
                break;
@@ -3530,7 +3343,7 @@ call_def_function(
                    tv = STACK_TV_BOT(-1);
                    n2 = tv->vval.v_number;
 
-                   ectx.ec_stack.ga_len -= is_slice ? 2 : 1;
+                   ectx->ec_stack.ga_len -= is_slice ? 2 : 1;
                    tv = STACK_TV_BOT(-1);
                    if (is_slice)
                        // Slice: Select the characters from the string
@@ -3575,7 +3388,7 @@ call_def_function(
                        clear_tv(tv);
                    }
 
-                   ectx.ec_stack.ga_len -= is_slice ? 2 : 1;
+                   ectx->ec_stack.ga_len -= is_slice ? 2 : 1;
                    tv = STACK_TV_BOT(-1);
                    SOURCING_LNUM = iptr->isn_lnum;
                    if (is_blob)
@@ -3614,7 +3427,7 @@ call_def_function(
                    clear_tv(var1);
                    if (is_slice)
                        clear_tv(var2);
-                   ectx.ec_stack.ga_len -= is_slice ? 2 : 1;
+                   ectx->ec_stack.ga_len -= is_slice ? 2 : 1;
                    if (res == FAIL)
                        goto on_error;
                }
@@ -3655,15 +3468,15 @@ call_def_function(
                    tv = STACK_TV_BOT(-1);
                    li = list_find(tv->vval.v_list, index);
 
-                   if (GA_GROW(&ectx.ec_stack, 1) == FAIL)
-                       goto failed;
-                   ++ectx.ec_stack.ga_len;
+                   if (GA_GROW(&ectx->ec_stack, 1) == FAIL)
+                       return FAIL;
+                   ++ectx->ec_stack.ga_len;
                    copy_tv(&li->li_tv, STACK_TV_BOT(-1));
 
                    // Useful when used in unpack assignment.  Reset at
                    // ISN_DROP.
-                   where.wt_index = index + 1;
-                   where.wt_variable = TRUE;
+                   ectx->ec_where.wt_index = index + 1;
+                   ectx->ec_where.wt_variable = TRUE;
                }
                break;
 
@@ -3693,7 +3506,7 @@ call_def_function(
                        // If :silent! is used we will continue, make sure the
                        // stack contents makes sense.
                        clear_tv(tv);
-                       --ectx.ec_stack.ga_len;
+                       --ectx->ec_stack.ga_len;
                        tv = STACK_TV_BOT(-1);
                        clear_tv(tv);
                        tv->v_type = VAR_NUMBER;
@@ -3701,7 +3514,7 @@ call_def_function(
                        goto on_fatal_error;
                    }
                    clear_tv(tv);
-                   --ectx.ec_stack.ga_len;
+                   --ectx->ec_stack.ga_len;
                    // Clear the dict only after getting the item, to avoid
                    // that it makes the item invalid.
                    tv = STACK_TV_BOT(-1);
@@ -3782,12 +3595,13 @@ call_def_function(
 
                    tv = STACK_TV_BOT((int)ct->ct_off);
                    SOURCING_LNUM = iptr->isn_lnum;
-                   if (!where.wt_variable)
-                       where.wt_index = ct->ct_arg_idx;
-                   if (check_typval_type(ct->ct_type, tv, where) == FAIL)
+                   if (!ectx->ec_where.wt_variable)
+                       ectx->ec_where.wt_index = ct->ct_arg_idx;
+                   if (check_typval_type(ct->ct_type, tv, ectx->ec_where)
+                                                                      == FAIL)
                        goto on_error;
-                   if (!where.wt_variable)
-                       where.wt_index = 0;
+                   if (!ectx->ec_where.wt_variable)
+                       ectx->ec_where.wt_index = 0;
 
                    // number 0 is FALSE, number 1 is TRUE
                    if (tv->v_type == VAR_NUMBER
@@ -3887,9 +3701,9 @@ call_def_function(
                    if (parse_cmd_address(&ea, &errormsg, FALSE) == FAIL)
                        goto on_error;
 
-                   if (GA_GROW(&ectx.ec_stack, 1) == FAIL)
-                       goto failed;
-                   ++ectx.ec_stack.ga_len;
+                   if (GA_GROW(&ectx->ec_stack, 1) == FAIL)
+                       return FAIL;
+                   ++ectx->ec_stack.ga_len;
                    tv = STACK_TV_BOT(-1);
                    tv->v_type = VAR_NUMBER;
                    tv->v_lock = 0;
@@ -3914,7 +3728,7 @@ call_def_function(
                        curwin->w_cursor.lnum = tv->vval.v_number;
                        if (lnum == LNUM_VARIABLE_RANGE_ABOVE)
                            dir = BACKWARD;
-                       --ectx.ec_stack.ga_len;
+                       --ectx->ec_stack.ga_len;
                    }
                    else if (lnum == -2)
                        // :put! above cursor
@@ -3932,7 +3746,7 @@ call_def_function(
                            expr = typval2string(tv, TRUE); // allocates value
                            clear_tv(tv);
                        }
-                       --ectx.ec_stack.ga_len;
+                       --ectx->ec_stack.ga_len;
                    }
                    check_cursor();
                    do_put(regname, expr, dir, 1L, PUT_LINE|PUT_CURSLINE);
@@ -3941,9 +3755,10 @@ call_def_function(
                break;
 
            case ISN_CMDMOD:
-               funclocal.floc_save_cmdmod = cmdmod;
-               funclocal.floc_restore_cmdmod = TRUE;
-               funclocal.floc_restore_cmdmod_stacklen = ectx.ec_stack.ga_len;
+               ectx->ec_funclocal.floc_save_cmdmod = cmdmod;
+               ectx->ec_funclocal.floc_restore_cmdmod = TRUE;
+               ectx->ec_funclocal.floc_restore_cmdmod_stacklen =
+                                                        ectx->ec_stack.ga_len;
                cmdmod = *iptr->isn_arg.cmdmod.cf_cmdmod;
                apply_cmdmod(&cmdmod);
                break;
@@ -3952,8 +3767,8 @@ call_def_function(
                // filter regprog is owned by the instruction, don't free it
                cmdmod.cmod_filter_regmatch.regprog = NULL;
                undo_cmdmod(&cmdmod);
-               cmdmod = funclocal.floc_save_cmdmod;
-               funclocal.floc_restore_cmdmod = FALSE;
+               cmdmod = ectx->ec_funclocal.floc_save_cmdmod;
+               ectx->ec_funclocal.floc_restore_cmdmod = FALSE;
                break;
 
            case ISN_UNPACK:
@@ -3988,9 +3803,9 @@ call_def_function(
                    }
 
                    CHECK_LIST_MATERIALIZE(l);
-                   if (GA_GROW(&ectx.ec_stack, count - 1) == FAIL)
-                       goto failed;
-                   ectx.ec_stack.ga_len += count - 1;
+                   if (GA_GROW(&ectx->ec_stack, count - 1) == FAIL)
+                       return FAIL;
+                   ectx->ec_stack.ga_len += count - 1;
 
                    // Variable after semicolon gets a list with the remaining
                    // items.
@@ -4000,7 +3815,7 @@ call_def_function(
                                  list_alloc_with_items(l->lv_len - count + 1);
 
                        if (rem_list == NULL)
-                           goto failed;
+                           return FAIL;
                        tv = STACK_TV_BOT(-count);
                        tv->vval.v_list = rem_list;
                        ++rem_list->lv_refcount;
@@ -4036,7 +3851,7 @@ call_def_function(
                    funccall_T cookie;
                    ufunc_T         *cur_ufunc =
                                    (((dfunc_T *)def_functions.ga_data)
-                                                + ectx.ec_dfunc_idx)->df_ufunc;
+                                                + ectx->ec_dfunc_idx)->df_ufunc;
 
                    cookie.func = cur_ufunc;
                    if (iptr->isn_type == ISN_PROF_START)
@@ -4068,10 +3883,10 @@ call_def_function(
                break;
 
            case ISN_DROP:
-               --ectx.ec_stack.ga_len;
+               --ectx->ec_stack.ga_len;
                clear_tv(STACK_TV_BOT(0));
-               where.wt_index = 0;
-               where.wt_variable = FALSE;
+               ectx->ec_where.wt_index = 0;
+               ectx->ec_where.wt_variable = FALSE;
                break;
        }
        continue;
@@ -4079,269 +3894,504 @@ call_def_function(
 func_return:
        // Restore previous function. If the frame pointer is where we started
        // then there is none and we are done.
-       if (ectx.ec_frame_idx == initial_frame_idx)
+       if (ectx->ec_frame_idx == ectx->ec_initial_frame_idx)
            goto done;
 
-       if (func_return(&funclocal, &ectx) == FAIL)
+       if (func_return(ectx) == FAIL)
            // only fails when out of memory
-           goto failed;
+           return FAIL;
        continue;
 
 on_error:
        // Jump here for an error that does not require aborting execution.
        // If "emsg_silent" is set then ignore the error, unless it was set
        // when calling the function.
-       if (did_emsg_cumul + did_emsg == did_emsg_before
+       if (did_emsg_cumul + did_emsg == ectx->ec_did_emsg_before
                                           && emsg_silent && did_emsg_def == 0)
        {
            // If a sequence of instructions causes an error while ":silent!"
            // was used, restore the stack length and jump ahead to restoring
            // the cmdmod.
-           if (funclocal.floc_restore_cmdmod)
+           if (ectx->ec_funclocal.floc_restore_cmdmod)
            {
-               while (ectx.ec_stack.ga_len
-                                     > funclocal.floc_restore_cmdmod_stacklen)
+               while (ectx->ec_stack.ga_len
+                            > ectx->ec_funclocal.floc_restore_cmdmod_stacklen)
                {
-                   --ectx.ec_stack.ga_len;
+                   --ectx->ec_stack.ga_len;
                    clear_tv(STACK_TV_BOT(0));
                }
-               while (ectx.ec_instr[ectx.ec_iidx].isn_type != ISN_CMDMOD_REV)
-                   ++ectx.ec_iidx;
+               while (ectx->ec_instr[ectx->ec_iidx].isn_type != ISN_CMDMOD_REV)
+                   ++ectx->ec_iidx;
            }
            continue;
        }
 on_fatal_error:
        // Jump here for an error that messes up the stack.
        // If we are not inside a try-catch started here, abort execution.
-       if (trylevel <= trylevel_at_start)
-           goto failed;
+       if (trylevel <= ectx->ec_trylevel_at_start)
+           return FAIL;
     }
 
 done:
-    // function finished, get result from the stack.
-    if (ufunc->uf_ret_type == &t_void)
+    return OK;
+}
+
+/*
+ * Execute the instructions from an ISN_SUBSTITUTE command, which are in
+ * "substitute_instr".
+ */
+    char_u *
+exe_substitute_instr(void)
+{
+    ectx_T     *ectx = substitute_instr->subs_ectx;
+    isn_T      *save_instr = ectx->ec_instr;
+    int                save_iidx = ectx->ec_iidx;
+    char_u     *res;
+
+    ectx->ec_instr = substitute_instr->subs_instr;
+    if (exec_instructions(ectx) == OK)
     {
-       rettv->v_type = VAR_VOID;
+       typval_T *tv = STACK_TV_BOT(-1);
+
+       res = vim_strsave(tv_get_string(tv));
+       --ectx->ec_stack.ga_len;
+       clear_tv(tv);
     }
     else
     {
-       tv = STACK_TV_BOT(-1);
-       *rettv = *tv;
-       tv->v_type = VAR_UNKNOWN;
+       substitute_instr->subs_status = FAIL;
+       res = vim_strsave((char_u *)"");
     }
-    ret = OK;
 
-failed:
-    // When failed need to unwind the call stack.
-    while (ectx.ec_frame_idx != initial_frame_idx)
-       func_return(&funclocal, &ectx);
+    ectx->ec_instr = save_instr;
+    ectx->ec_iidx = save_iidx;
 
-    // Deal with any remaining closures, they may be in use somewhere.
-    if (ectx.ec_funcrefs.ga_len > 0)
-    {
-       handle_closure_in_use(&ectx, FALSE);
-       ga_clear(&ectx.ec_funcrefs);  // TODO: should not be needed?
-    }
+    return res;
+}
 
-    estack_pop();
-    current_sctx = save_current_sctx;
+/*
+ * Call a "def" function from old Vim script.
+ * Return OK or FAIL.
+ */
+    int
+call_def_function(
+    ufunc_T    *ufunc,
+    int                argc_arg,       // nr of arguments
+    typval_T   *argv,          // arguments
+    partial_T  *partial,       // optional partial for context
+    typval_T   *rettv)         // return value
+{
+    ectx_T     ectx;           // execution context
+    int                argc = argc_arg;
+    typval_T   *tv;
+    int                idx;
+    int                ret = FAIL;
+    int                defcount = ufunc->uf_args.ga_len - argc;
+    sctx_T     save_current_sctx = current_sctx;
+    int                did_emsg_before = did_emsg_cumul + did_emsg;
+    int                save_suppress_errthrow = suppress_errthrow;
+    msglist_T  **saved_msg_list = NULL;
+    msglist_T  *private_msg_list = NULL;
+    int                save_emsg_silent_def = emsg_silent_def;
+    int                save_did_emsg_def = did_emsg_def;
+    int                orig_funcdepth;
 
-    // TODO: when is it safe to delete the function if it is no longer used?
-    --ufunc->uf_calls;
+// Get pointer to item in the stack.
+#undef STACK_TV
+#define STACK_TV(idx) (((typval_T *)ectx.ec_stack.ga_data) + idx)
 
-    if (*msg_list != NULL && saved_msg_list != NULL)
-    {
-       msglist_T **plist = saved_msg_list;
+// Get pointer to item at the bottom of the stack, -1 is the bottom.
+#undef STACK_TV_BOT
+#define STACK_TV_BOT(idx) (((typval_T *)ectx.ec_stack.ga_data) + ectx.ec_stack.ga_len + idx)
 
-       // Append entries from the current msg_list (uncaught exceptions) to
-       // the saved msg_list.
-       while (*plist != NULL)
-           plist = &(*plist)->next;
+// Get pointer to a local variable on the stack.  Negative for arguments.
+#undef STACK_TV_VAR
+#define STACK_TV_VAR(idx) (((typval_T *)ectx.ec_stack.ga_data) + ectx.ec_frame_idx + STACK_FRAME_SIZE + idx)
 
-       *plist = *msg_list;
+    if (ufunc->uf_def_status == UF_NOT_COMPILED
+           || ufunc->uf_def_status == UF_COMPILE_ERROR
+           || (func_needs_compiling(ufunc, PROFILING(ufunc))
+               && compile_def_function(ufunc, FALSE, PROFILING(ufunc), NULL)
+                                                                     == FAIL))
+    {
+       if (did_emsg_cumul + did_emsg == did_emsg_before)
+           semsg(_(e_function_is_not_compiled_str),
+                                                  printable_func_name(ufunc));
+       return FAIL;
     }
-    msg_list = saved_msg_list;
 
-    if (funclocal.floc_restore_cmdmod)
     {
-       cmdmod.cmod_filter_regmatch.regprog = NULL;
-       undo_cmdmod(&cmdmod);
-       cmdmod = funclocal.floc_save_cmdmod;
+       // Check the function was really compiled.
+       dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data)
+                                                        + ufunc->uf_dfunc_idx;
+       if (INSTRUCTIONS(dfunc) == NULL)
+       {
+           iemsg("using call_def_function() on not compiled function");
+           return FAIL;
+       }
     }
-    emsg_silent_def = save_emsg_silent_def;
-    did_emsg_def += save_did_emsg_def;
-
-failed_early:
-    // Free all local variables, but not arguments.
-    for (idx = 0; idx < ectx.ec_stack.ga_len; ++idx)
-       clear_tv(STACK_TV(idx));
 
-    vim_free(ectx.ec_stack.ga_data);
-    vim_free(ectx.ec_trystack.ga_data);
+    // If depth of calling is getting too high, don't execute the function.
+    orig_funcdepth = funcdepth_get();
+    if (funcdepth_increment() == FAIL)
+       return FAIL;
 
-    while (ectx.ec_outer != NULL)
+    CLEAR_FIELD(ectx);
+    ectx.ec_dfunc_idx = ufunc->uf_dfunc_idx;
+    ga_init2(&ectx.ec_stack, sizeof(typval_T), 500);
+    if (ga_grow(&ectx.ec_stack, 20) == FAIL)
     {
-       outer_T     *up = ectx.ec_outer->out_up_is_copy
-                                               ? NULL : ectx.ec_outer->out_up;
+       funcdepth_decrement();
+       return FAIL;
+    }
+    ga_init2(&ectx.ec_trystack, sizeof(trycmd_T), 10);
+    ga_init2(&ectx.ec_funcrefs, sizeof(partial_T *), 10);
+    ectx.ec_did_emsg_before = did_emsg_before;
+    ectx.ec_trylevel_at_start = trylevel;
 
-       vim_free(ectx.ec_outer);
-       ectx.ec_outer = up;
+    idx = argc - ufunc->uf_args.ga_len;
+    if (idx > 0 && ufunc->uf_va_name == NULL)
+    {
+       if (idx == 1)
+           emsg(_(e_one_argument_too_many));
+       else
+           semsg(_(e_nr_arguments_too_many), idx);
+       goto failed_early;
     }
 
-    // Not sure if this is necessary.
-    suppress_errthrow = save_suppress_errthrow;
+    // Put arguments on the stack, but no more than what the function expects.
+    // A lambda can be called with more arguments than it uses.
+    for (idx = 0; idx < argc
+           && (ufunc->uf_va_name != NULL || idx < ufunc->uf_args.ga_len);
+                                                                        ++idx)
+    {
+       if (idx >= ufunc->uf_args.ga_len - ufunc->uf_def_args.ga_len
+               && argv[idx].v_type == VAR_SPECIAL
+               && argv[idx].vval.v_number == VVAL_NONE)
+       {
+           // Use the default value.
+           STACK_TV_BOT(0)->v_type = VAR_UNKNOWN;
+       }
+       else
+       {
+           if (ufunc->uf_arg_types != NULL && idx < ufunc->uf_args.ga_len
+                   && check_typval_arg_type(
+                       ufunc->uf_arg_types[idx], &argv[idx], idx + 1) == FAIL)
+               goto failed_early;
+           copy_tv(&argv[idx], STACK_TV_BOT(0));
+       }
+       ++ectx.ec_stack.ga_len;
+    }
 
-    if (ret != OK && did_emsg_cumul + did_emsg == did_emsg_before)
-       semsg(_(e_unknown_error_while_executing_str),
-                                                  printable_func_name(ufunc));
-    funcdepth_restore(orig_funcdepth);
-    return ret;
-}
+    // Turn varargs into a list.  Empty list if no args.
+    if (ufunc->uf_va_name != NULL)
+    {
+       int vararg_count = argc - ufunc->uf_args.ga_len;
 
-/*
- * ":disassemble".
- * We don't really need this at runtime, but we do have tests that require it,
- * so always include this.
- */
-    void
-ex_disassemble(exarg_T *eap)
-{
-    char_u     *arg = eap->arg;
-    char_u     *fname;
-    ufunc_T    *ufunc;
-    dfunc_T    *dfunc;
-    isn_T      *instr;
-    int                instr_count;
-    int                current;
-    int                line_idx = 0;
-    int                prev_current = 0;
-    int                is_global = FALSE;
+       if (vararg_count < 0)
+           vararg_count = 0;
+       else
+           argc -= vararg_count;
+       if (exe_newlist(vararg_count, &ectx) == FAIL)
+           goto failed_early;
+
+       // Check the type of the list items.
+       tv = STACK_TV_BOT(-1);
+       if (ufunc->uf_va_type != NULL
+               && ufunc->uf_va_type != &t_list_any
+               && ufunc->uf_va_type->tt_member != &t_any
+               && tv->vval.v_list != NULL)
+       {
+           type_T      *expected = ufunc->uf_va_type->tt_member;
+           listitem_T  *li = tv->vval.v_list->lv_first;
+
+           for (idx = 0; idx < vararg_count; ++idx)
+           {
+               if (check_typval_arg_type(expected, &li->li_tv,
+                                                      argc + idx + 1) == FAIL)
+                   goto failed_early;
+               li = li->li_next;
+           }
+       }
+
+       if (defcount > 0)
+           // Move varargs list to below missing default arguments.
+           *STACK_TV_BOT(defcount - 1) = *STACK_TV_BOT(-1);
+       --ectx.ec_stack.ga_len;
+    }
+
+    // Make space for omitted arguments, will store default value below.
+    // Any varargs list goes after them.
+    if (defcount > 0)
+       for (idx = 0; idx < defcount; ++idx)
+       {
+           STACK_TV_BOT(0)->v_type = VAR_UNKNOWN;
+           ++ectx.ec_stack.ga_len;
+       }
+    if (ufunc->uf_va_name != NULL)
+       ++ectx.ec_stack.ga_len;
+
+    // Frame pointer points to just after arguments.
+    ectx.ec_frame_idx = ectx.ec_stack.ga_len;
+    ectx.ec_initial_frame_idx = ectx.ec_frame_idx;
 
-    if (STRNCMP(arg, "<lambda>", 8) == 0)
     {
-       arg += 8;
-       (void)getdigits(&arg);
-       fname = vim_strnsave(eap->arg, arg - eap->arg);
+       dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data)
+                                                        + ufunc->uf_dfunc_idx;
+       ufunc_T *base_ufunc = dfunc->df_ufunc;
+
+       // "uf_partial" is on the ufunc that "df_ufunc" points to, as is done
+       // by copy_func().
+       if (partial != NULL || base_ufunc->uf_partial != NULL)
+       {
+           ectx.ec_outer = ALLOC_CLEAR_ONE(outer_T);
+           if (ectx.ec_outer == NULL)
+               goto failed_early;
+           if (partial != NULL)
+           {
+               if (partial->pt_outer.out_stack == NULL && current_ectx != NULL)
+               {
+                   if (current_ectx->ec_outer != NULL)
+                       *ectx.ec_outer = *current_ectx->ec_outer;
+               }
+               else
+                   *ectx.ec_outer = partial->pt_outer;
+           }
+           else
+               *ectx.ec_outer = base_ufunc->uf_partial->pt_outer;
+           ectx.ec_outer->out_up_is_copy = TRUE;
+       }
     }
-    else
-       fname = trans_function_name(&arg, &is_global, FALSE,
-                     TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD, NULL, NULL, NULL);
-    if (fname == NULL)
+
+    // dummy frame entries
+    for (idx = 0; idx < STACK_FRAME_SIZE; ++idx)
     {
-       semsg(_(e_invarg2), eap->arg);
-       return;
+       STACK_TV(ectx.ec_stack.ga_len)->v_type = VAR_UNKNOWN;
+       ++ectx.ec_stack.ga_len;
     }
 
-    ufunc = find_func(fname, is_global, NULL);
-    if (ufunc == NULL)
     {
-       char_u *p = untrans_function_name(fname);
+       // Reserve space for local variables and any closure reference count.
+       dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data)
+                                                        + ufunc->uf_dfunc_idx;
 
-       if (p != NULL)
-           // Try again without making it script-local.
-           ufunc = find_func(p, FALSE, NULL);
+       for (idx = 0; idx < dfunc->df_varcount; ++idx)
+           STACK_TV_VAR(idx)->v_type = VAR_UNKNOWN;
+       ectx.ec_stack.ga_len += dfunc->df_varcount;
+       if (dfunc->df_has_closure)
+       {
+           STACK_TV_VAR(idx)->v_type = VAR_NUMBER;
+           STACK_TV_VAR(idx)->vval.v_number = 0;
+           ++ectx.ec_stack.ga_len;
+       }
+
+       ectx.ec_instr = INSTRUCTIONS(dfunc);
     }
-    vim_free(fname);
-    if (ufunc == NULL)
+
+    // Following errors are in the function, not the caller.
+    // Commands behave like vim9script.
+    estack_push_ufunc(ufunc, 1);
+    current_sctx = ufunc->uf_script_ctx;
+    current_sctx.sc_version = SCRIPT_VERSION_VIM9;
+
+    // Use a specific location for storing error messages to be converted to an
+    // exception.
+    saved_msg_list = msg_list;
+    msg_list = &private_msg_list;
+
+    // Do turn errors into exceptions.
+    suppress_errthrow = FALSE;
+
+    // Do not delete the function while executing it.
+    ++ufunc->uf_calls;
+
+    // When ":silent!" was used before calling then we still abort the
+    // function.  If ":silent!" is used in the function then we don't.
+    emsg_silent_def = emsg_silent;
+    did_emsg_def = 0;
+
+    ectx.ec_where.wt_index = 0;
+    ectx.ec_where.wt_variable = FALSE;
+
+    // Execute the instructions until done.
+    ret = exec_instructions(&ectx);
+    if (ret == OK)
     {
-       semsg(_(e_cannot_find_function_str), eap->arg);
-       return;
+       // function finished, get result from the stack.
+       if (ufunc->uf_ret_type == &t_void)
+       {
+           rettv->v_type = VAR_VOID;
+       }
+       else
+       {
+           tv = STACK_TV_BOT(-1);
+           *rettv = *tv;
+           tv->v_type = VAR_UNKNOWN;
+       }
     }
-    if (func_needs_compiling(ufunc, eap->forceit)
-           && compile_def_function(ufunc, FALSE, eap->forceit, NULL) == FAIL)
-       return;
-    if (ufunc->uf_def_status != UF_COMPILED)
+
+    // When failed need to unwind the call stack.
+    while (ectx.ec_frame_idx != ectx.ec_initial_frame_idx)
+       func_return(&ectx);
+
+    // Deal with any remaining closures, they may be in use somewhere.
+    if (ectx.ec_funcrefs.ga_len > 0)
     {
-       semsg(_(e_function_is_not_compiled_str), eap->arg);
-       return;
+       handle_closure_in_use(&ectx, FALSE);
+       ga_clear(&ectx.ec_funcrefs);  // TODO: should not be needed?
     }
-    if (ufunc->uf_name_exp != NULL)
-       msg((char *)ufunc->uf_name_exp);
-    else
-       msg((char *)ufunc->uf_name);
 
-    dfunc = ((dfunc_T *)def_functions.ga_data) + ufunc->uf_dfunc_idx;
-#ifdef FEAT_PROFILE
-    instr = eap->forceit ? dfunc->df_instr_prof : dfunc->df_instr;
-    instr_count = eap->forceit ? dfunc->df_instr_prof_count
-                                                      : dfunc->df_instr_count;
-#else
-    instr = dfunc->df_instr;
-    instr_count = dfunc->df_instr_count;
-#endif
+    estack_pop();
+    current_sctx = save_current_sctx;
+
+    // TODO: when is it safe to delete the function if it is no longer used?
+    --ufunc->uf_calls;
+
+    if (*msg_list != NULL && saved_msg_list != NULL)
+    {
+       msglist_T **plist = saved_msg_list;
+
+       // Append entries from the current msg_list (uncaught exceptions) to
+       // the saved msg_list.
+       while (*plist != NULL)
+           plist = &(*plist)->next;
+
+       *plist = *msg_list;
+    }
+    msg_list = saved_msg_list;
+
+    if (ectx.ec_funclocal.floc_restore_cmdmod)
+    {
+       cmdmod.cmod_filter_regmatch.regprog = NULL;
+       undo_cmdmod(&cmdmod);
+       cmdmod = ectx.ec_funclocal.floc_save_cmdmod;
+    }
+    emsg_silent_def = save_emsg_silent_def;
+    did_emsg_def += save_did_emsg_def;
+
+failed_early:
+    // Free all local variables, but not arguments.
+    for (idx = 0; idx < ectx.ec_stack.ga_len; ++idx)
+       clear_tv(STACK_TV(idx));
+
+    vim_free(ectx.ec_stack.ga_data);
+    vim_free(ectx.ec_trystack.ga_data);
+
+    while (ectx.ec_outer != NULL)
+    {
+       outer_T     *up = ectx.ec_outer->out_up_is_copy
+                                               ? NULL : ectx.ec_outer->out_up;
+
+       vim_free(ectx.ec_outer);
+       ectx.ec_outer = up;
+    }
+
+    // Not sure if this is necessary.
+    suppress_errthrow = save_suppress_errthrow;
+
+    if (ret != OK && did_emsg_cumul + did_emsg == did_emsg_before)
+       semsg(_(e_unknown_error_while_executing_str),
+                                                  printable_func_name(ufunc));
+    funcdepth_restore(orig_funcdepth);
+    return ret;
+}
+
+/*
+ * List instructions "instr" up to "instr_count" or until ISN_FINISH.
+ * "ufunc" has the source lines, NULL for the instructions of ISN_SUBSTITUTE.
+ * "pfx" is prefixed to every line.
+ */
+    static void
+list_instructions(char *pfx, isn_T *instr, int instr_count, ufunc_T *ufunc)
+{
+    int                line_idx = 0;
+    int                prev_current = 0;
+    int                current;
+
     for (current = 0; current < instr_count; ++current)
     {
        isn_T       *iptr = &instr[current];
        char        *line;
 
-       while (line_idx < iptr->isn_lnum && line_idx < ufunc->uf_lines.ga_len)
-       {
-           if (current > prev_current)
+       if (ufunc != NULL)
+           while (line_idx < iptr->isn_lnum
+                                         && line_idx < ufunc->uf_lines.ga_len)
            {
-               msg_puts("\n\n");
-               prev_current = current;
+               if (current > prev_current)
+               {
+                   msg_puts("\n\n");
+                   prev_current = current;
+               }
+               line = ((char **)ufunc->uf_lines.ga_data)[line_idx++];
+               if (line != NULL)
+                   msg(line);
            }
-           line = ((char **)ufunc->uf_lines.ga_data)[line_idx++];
-           if (line != NULL)
-               msg(line);
-       }
 
        switch (iptr->isn_type)
        {
            case ISN_EXEC:
-               smsg("%4d EXEC %s", current, iptr->isn_arg.string);
+               smsg("%s%4d EXEC %s", pfx, current, iptr->isn_arg.string);
+               break;
+           case ISN_SUBSTITUTE:
+               {
+                   subs_T *subs = &iptr->isn_arg.subs;
+
+                   smsg("%s%4d SUBSTITUTE %s", pfx, current, subs->subs_cmd);
+                   list_instructions("    ", subs->subs_instr, INT_MAX, NULL);
+                   msg("     -------------");
+               }
                break;
            case ISN_EXECCONCAT:
-               smsg("%4d EXECCONCAT %lld", current,
+               smsg("%s%4d EXECCONCAT %lld", pfx, current,
                                              (varnumber_T)iptr->isn_arg.number);
                break;
            case ISN_ECHO:
                {
                    echo_T *echo = &iptr->isn_arg.echo;
 
-                   smsg("%4d %s %d", current,
+                   smsg("%s%4d %s %d", pfx, current,
                            echo->echo_with_white ? "ECHO" : "ECHON",
                            echo->echo_count);
                }
                break;
            case ISN_EXECUTE:
-               smsg("%4d EXECUTE %lld", current,
+               smsg("%s%4d EXECUTE %lld", pfx, current,
                                            (varnumber_T)(iptr->isn_arg.number));
                break;
            case ISN_ECHOMSG:
-               smsg("%4d ECHOMSG %lld", current,
+               smsg("%s%4d ECHOMSG %lld", pfx, current,
                                            (varnumber_T)(iptr->isn_arg.number));
                break;
            case ISN_ECHOERR:
-               smsg("%4d ECHOERR %lld", current,
+               smsg("%s%4d ECHOERR %lld", pfx, current,
                                            (varnumber_T)(iptr->isn_arg.number));
                break;
            case ISN_LOAD:
                {
                    if (iptr->isn_arg.number < 0)
-                       smsg("%4d LOAD arg[%lld]", current,
+                       smsg("%s%4d LOAD arg[%lld]", pfx, current,
                                (varnumber_T)(iptr->isn_arg.number
                                                          + STACK_FRAME_SIZE));
                    else
-                       smsg("%4d LOAD $%lld", current,
+                       smsg("%s%4d LOAD $%lld", pfx, current,
                                          (varnumber_T)(iptr->isn_arg.number));
                }
                break;
            case ISN_LOADOUTER:
                {
                    if (iptr->isn_arg.number < 0)
-                       smsg("%4d LOADOUTER level %d arg[%d]", current,
+                       smsg("%s%4d LOADOUTER level %d arg[%d]", pfx, current,
                                iptr->isn_arg.outer.outer_depth,
                                iptr->isn_arg.outer.outer_idx
                                                          + STACK_FRAME_SIZE);
                    else
-                       smsg("%4d LOADOUTER level %d $%d", current,
+                       smsg("%s%4d LOADOUTER level %d $%d", pfx, current,
                                              iptr->isn_arg.outer.outer_depth,
                                              iptr->isn_arg.outer.outer_idx);
                }
                break;
            case ISN_LOADV:
-               smsg("%4d LOADV v:%s", current,
+               smsg("%s%4d LOADV v:%s", pfx, current,
                                       get_vim_var_name(iptr->isn_arg.number));
                break;
            case ISN_LOADSCRIPT:
@@ -4351,7 +4401,7 @@ ex_disassemble(exarg_T *eap)
                    svar_T *sv = ((svar_T *)si->sn_var_vals.ga_data)
                                                              + sref->sref_idx;
 
-                   smsg("%4d LOADSCRIPT %s-%d from %s", current,
+                   smsg("%s%4d LOADSCRIPT %s-%d from %s", pfx, current,
                                            sv->sv_name,
                                            sref->sref_idx,
                                            si->sn_name);
@@ -4362,91 +4412,91 @@ ex_disassemble(exarg_T *eap)
                    scriptitem_T *si = SCRIPT_ITEM(
                                               iptr->isn_arg.loadstore.ls_sid);
 
-                   smsg("%4d LOADS s:%s from %s", current,
+                   smsg("%s%4d LOADS s:%s from %s", pfx, current,
                                 iptr->isn_arg.loadstore.ls_name, si->sn_name);
                }
                break;
            case ISN_LOADAUTO:
-               smsg("%4d LOADAUTO %s", current, iptr->isn_arg.string);
+               smsg("%s%4d LOADAUTO %s", pfx, current, iptr->isn_arg.string);
                break;
            case ISN_LOADG:
-               smsg("%4d LOADG g:%s", current, iptr->isn_arg.string);
+               smsg("%s%4d LOADG g:%s", pfx, current, iptr->isn_arg.string);
                break;
            case ISN_LOADB:
-               smsg("%4d LOADB b:%s", current, iptr->isn_arg.string);
+               smsg("%s%4d LOADB b:%s", pfx, current, iptr->isn_arg.string);
                break;
            case ISN_LOADW:
-               smsg("%4d LOADW w:%s", current, iptr->isn_arg.string);
+               smsg("%s%4d LOADW w:%s", pfx, current, iptr->isn_arg.string);
                break;
            case ISN_LOADT:
-               smsg("%4d LOADT t:%s", current, iptr->isn_arg.string);
+               smsg("%s%4d LOADT t:%s", pfx, current, iptr->isn_arg.string);
                break;
            case ISN_LOADGDICT:
-               smsg("%4d LOAD g:", current);
+               smsg("%s%4d LOAD g:", pfx, current);
                break;
            case ISN_LOADBDICT:
-               smsg("%4d LOAD b:", current);
+               smsg("%s%4d LOAD b:", pfx, current);
                break;
            case ISN_LOADWDICT:
-               smsg("%4d LOAD w:", current);
+               smsg("%s%4d LOAD w:", pfx, current);
                break;
            case ISN_LOADTDICT:
-               smsg("%4d LOAD t:", current);
+               smsg("%s%4d LOAD t:", pfx, current);
                break;
            case ISN_LOADOPT:
-               smsg("%4d LOADOPT %s", current, iptr->isn_arg.string);
+               smsg("%s%4d LOADOPT %s", pfx, current, iptr->isn_arg.string);
                break;
            case ISN_LOADENV:
-               smsg("%4d LOADENV %s", current, iptr->isn_arg.string);
+               smsg("%s%4d LOADENV %s", pfx, current, iptr->isn_arg.string);
                break;
            case ISN_LOADREG:
-               smsg("%4d LOADREG @%c", current, (int)(iptr->isn_arg.number));
+               smsg("%s%4d LOADREG @%c", pfx, current, (int)(iptr->isn_arg.number));
                break;
 
            case ISN_STORE:
                if (iptr->isn_arg.number < 0)
-                   smsg("%4d STORE arg[%lld]", current,
+                   smsg("%s%4d STORE arg[%lld]", pfx, current,
                                      iptr->isn_arg.number + STACK_FRAME_SIZE);
                else
-                   smsg("%4d STORE $%lld", current, iptr->isn_arg.number);
+                   smsg("%s%4d STORE $%lld", pfx, current, iptr->isn_arg.number);
                break;
            case ISN_STOREOUTER:
                {
                if (iptr->isn_arg.number < 0)
-                   smsg("%4d STOREOUTEr level %d arg[%d]", current,
+                   smsg("%s%4d STOREOUTEr level %d arg[%d]", pfx, current,
                            iptr->isn_arg.outer.outer_depth,
                            iptr->isn_arg.outer.outer_idx + STACK_FRAME_SIZE);
                else
-                   smsg("%4d STOREOUTER level %d $%d", current,
+                   smsg("%s%4d STOREOUTER level %d $%d", pfx, current,
                            iptr->isn_arg.outer.outer_depth,
                            iptr->isn_arg.outer.outer_idx);
                }
                break;
            case ISN_STOREV:
-               smsg("%4d STOREV v:%s", current,
+               smsg("%s%4d STOREV v:%s", pfx, current,
                                       get_vim_var_name(iptr->isn_arg.number));
                break;
            case ISN_STOREAUTO:
-               smsg("%4d STOREAUTO %s", current, iptr->isn_arg.string);
+               smsg("%s%4d STOREAUTO %s", pfx, current, iptr->isn_arg.string);
                break;
            case ISN_STOREG:
-               smsg("%4d STOREG %s", current, iptr->isn_arg.string);
+               smsg("%s%4d STOREG %s", pfx, current, iptr->isn_arg.string);
                break;
            case ISN_STOREB:
-               smsg("%4d STOREB %s", current, iptr->isn_arg.string);
+               smsg("%s%4d STOREB %s", pfx, current, iptr->isn_arg.string);
                break;
            case ISN_STOREW:
-               smsg("%4d STOREW %s", current, iptr->isn_arg.string);
+               smsg("%s%4d STOREW %s", pfx, current, iptr->isn_arg.string);
                break;
            case ISN_STORET:
-               smsg("%4d STORET %s", current, iptr->isn_arg.string);
+               smsg("%s%4d STORET %s", pfx, current, iptr->isn_arg.string);
                break;
            case ISN_STORES:
                {
                    scriptitem_T *si = SCRIPT_ITEM(
                                               iptr->isn_arg.loadstore.ls_sid);
 
-                   smsg("%4d STORES %s in %s", current,
+                   smsg("%s%4d STORES %s in %s", pfx, current,
                                 iptr->isn_arg.loadstore.ls_name, si->sn_name);
                }
                break;
@@ -4457,54 +4507,54 @@ ex_disassemble(exarg_T *eap)
                    svar_T *sv = ((svar_T *)si->sn_var_vals.ga_data)
                                                              + sref->sref_idx;
 
-                   smsg("%4d STORESCRIPT %s-%d in %s", current,
+                   smsg("%s%4d STORESCRIPT %s-%d in %s", pfx, current,
                                             sv->sv_name,
                                             sref->sref_idx,
                                             si->sn_name);
                }
                break;
            case ISN_STOREOPT:
-               smsg("%4d STOREOPT &%s", current,
+               smsg("%s%4d STOREOPT &%s", pfx, current,
                                               iptr->isn_arg.storeopt.so_name);
                break;
            case ISN_STOREENV:
-               smsg("%4d STOREENV $%s", current, iptr->isn_arg.string);
+               smsg("%s%4d STOREENV $%s", pfx, current, iptr->isn_arg.string);
                break;
            case ISN_STOREREG:
-               smsg("%4d STOREREG @%c", current, (int)iptr->isn_arg.number);
+               smsg("%s%4d STOREREG @%c", pfx, current, (int)iptr->isn_arg.number);
                break;
            case ISN_STORENR:
-               smsg("%4d STORE %lld in $%d", current,
+               smsg("%s%4d STORE %lld in $%d", pfx, current,
                                iptr->isn_arg.storenr.stnr_val,
                                iptr->isn_arg.storenr.stnr_idx);
                break;
 
            case ISN_STOREINDEX:
-               smsg("%4d STOREINDEX %s", current,
+               smsg("%s%4d STOREINDEX %s", pfx, current,
                                          vartype_name(iptr->isn_arg.vartype));
                break;
 
            case ISN_STORERANGE:
-               smsg("%4d STORERANGE", current);
+               smsg("%s%4d STORERANGE", pfx, current);
                break;
 
            // constants
            case ISN_PUSHNR:
-               smsg("%4d PUSHNR %lld", current,
+               smsg("%s%4d PUSHNR %lld", pfx, current,
                                            (varnumber_T)(iptr->isn_arg.number));
                break;
            case ISN_PUSHBOOL:
            case ISN_PUSHSPEC:
-               smsg("%4d PUSH %s", current,
+               smsg("%s%4d PUSH %s", pfx, current,
                                   get_var_special_name(iptr->isn_arg.number));
                break;
            case ISN_PUSHF:
 #ifdef FEAT_FLOAT
-               smsg("%4d PUSHF %g", current, iptr->isn_arg.fnumber);
+               smsg("%s%4d PUSHF %g", pfx, current, iptr->isn_arg.fnumber);
 #endif
                break;
            case ISN_PUSHS:
-               smsg("%4d PUSHS \"%s\"", current, iptr->isn_arg.string);
+               smsg("%s%4d PUSHS \"%s\"", pfx, current, iptr->isn_arg.string);
                break;
            case ISN_PUSHBLOB:
                {
@@ -4513,7 +4563,7 @@ ex_disassemble(exarg_T *eap)
                    char_u      *tofree;
 
                    r = blob2string(iptr->isn_arg.blob, &tofree, numbuf);
-                   smsg("%4d PUSHBLOB %s", current, r);
+                   smsg("%s%4d PUSHBLOB %s", pfx, current, r);
                    vim_free(tofree);
                }
                break;
@@ -4521,7 +4571,7 @@ ex_disassemble(exarg_T *eap)
                {
                    char *name = (char *)iptr->isn_arg.string;
 
-                   smsg("%4d PUSHFUNC \"%s\"", current,
+                   smsg("%s%4d PUSHFUNC \"%s\"", pfx, current,
                                               name == NULL ? "[none]" : name);
                }
                break;
@@ -4530,7 +4580,7 @@ ex_disassemble(exarg_T *eap)
                {
                    channel_T *channel = iptr->isn_arg.channel;
 
-                   smsg("%4d PUSHCHANNEL %d", current,
+                   smsg("%s%4d PUSHCHANNEL %d", pfx, current,
                                         channel == NULL ? 0 : channel->ch_id);
                }
 #endif
@@ -4544,38 +4594,38 @@ ex_disassemble(exarg_T *eap)
                    tv.v_type = VAR_JOB;
                    tv.vval.v_job = iptr->isn_arg.job;
                    name = tv_get_string(&tv);
-                   smsg("%4d PUSHJOB \"%s\"", current, name);
+                   smsg("%s%4d PUSHJOB \"%s\"", pfx, current, name);
                }
 #endif
                break;
            case ISN_PUSHEXC:
-               smsg("%4d PUSH v:exception", current);
+               smsg("%s%4d PUSH v:exception", pfx, current);
                break;
            case ISN_UNLET:
-               smsg("%4d UNLET%s %s", current,
+               smsg("%s%4d UNLET%s %s", pfx, current,
                        iptr->isn_arg.unlet.ul_forceit ? "!" : "",
                        iptr->isn_arg.unlet.ul_name);
                break;
            case ISN_UNLETENV:
-               smsg("%4d UNLETENV%s $%s", current,
+               smsg("%s%4d UNLETENV%s $%s", pfx, current,
                        iptr->isn_arg.unlet.ul_forceit ? "!" : "",
                        iptr->isn_arg.unlet.ul_name);
                break;
            case ISN_UNLETINDEX:
-               smsg("%4d UNLETINDEX", current);
+               smsg("%s%4d UNLETINDEX", pfx, current);
                break;
            case ISN_UNLETRANGE:
-               smsg("%4d UNLETRANGE", current);
+               smsg("%s%4d UNLETRANGE", pfx, current);
                break;
            case ISN_LOCKCONST:
-               smsg("%4d LOCKCONST", current);
+               smsg("%s%4d LOCKCONST", pfx, current);
                break;
            case ISN_NEWLIST:
-               smsg("%4d NEWLIST size %lld", current,
+               smsg("%s%4d NEWLIST size %lld", pfx, current,
                                            (varnumber_T)(iptr->isn_arg.number));
                break;
            case ISN_NEWDICT:
-               smsg("%4d NEWDICT size %lld", current,
+               smsg("%s%4d NEWDICT size %lld", pfx, current,
                                            (varnumber_T)(iptr->isn_arg.number));
                break;
 
@@ -4584,7 +4634,7 @@ ex_disassemble(exarg_T *eap)
                {
                    cbfunc_T    *cbfunc = &iptr->isn_arg.bfunc;
 
-                   smsg("%4d BCALL %s(argc %d)", current,
+                   smsg("%s%4d BCALL %s(argc %d)", pfx, current,
                            internal_func_name(cbfunc->cbf_idx),
                            cbfunc->cbf_argcount);
                }
@@ -4595,7 +4645,7 @@ ex_disassemble(exarg_T *eap)
                    dfunc_T     *df = ((dfunc_T *)def_functions.ga_data)
                                                             + cdfunc->cdf_idx;
 
-                   smsg("%4d DCALL %s(argc %d)", current,
+                   smsg("%s%4d DCALL %s(argc %d)", pfx, current,
                            df->df_ufunc->uf_name_exp != NULL
                                ? df->df_ufunc->uf_name_exp
                                : df->df_ufunc->uf_name, cdfunc->cdf_argcount);
@@ -4605,7 +4655,7 @@ ex_disassemble(exarg_T *eap)
                {
                    cufunc_T    *cufunc = &iptr->isn_arg.ufunc;
 
-                   smsg("%4d UCALL %s(argc %d)", current,
+                   smsg("%s%4d UCALL %s(argc %d)", pfx, current,
                                       cufunc->cuf_name, cufunc->cuf_argcount);
                }
                break;
@@ -4613,18 +4663,18 @@ ex_disassemble(exarg_T *eap)
                {
                    cpfunc_T    *cpfunc = &iptr->isn_arg.pfunc;
 
-                   smsg("%4d PCALL%s (argc %d)", current,
+                   smsg("%s%4d PCALL%s (argc %d)", pfx, current,
                           cpfunc->cpf_top ? " top" : "", cpfunc->cpf_argcount);
                }
                break;
            case ISN_PCALL_END:
-               smsg("%4d PCALL end", current);
+               smsg("%s%4d PCALL end", pfx, current);
                break;
            case ISN_RETURN:
-               smsg("%4d RETURN", current);
+               smsg("%s%4d RETURN", pfx, current);
                break;
            case ISN_RETURN_ZERO:
-               smsg("%4d RETURN 0", current);
+               smsg("%s%4d RETURN 0", pfx, current);
                break;
            case ISN_FUNCREF:
                {
@@ -4632,7 +4682,7 @@ ex_disassemble(exarg_T *eap)
                    dfunc_T     *df = ((dfunc_T *)def_functions.ga_data)
                                                            + funcref->fr_func;
 
-                   smsg("%4d FUNCREF %s", current, df->df_ufunc->uf_name);
+                   smsg("%s%4d FUNCREF %s", pfx, current, df->df_ufunc->uf_name);
                }
                break;
 
@@ -4640,7 +4690,7 @@ ex_disassemble(exarg_T *eap)
                {
                    newfunc_T   *newfunc = &iptr->isn_arg.newfunc;
 
-                   smsg("%4d NEWFUNC %s %s", current,
+                   smsg("%s%4d NEWFUNC %s %s", pfx, current,
                                       newfunc->nf_lambda, newfunc->nf_global);
                }
                break;
@@ -4649,7 +4699,7 @@ ex_disassemble(exarg_T *eap)
                {
                    char_u *name = iptr->isn_arg.string;
 
-                   smsg("%4d DEF %s", current,
+                   smsg("%s%4d DEF %s", pfx, current,
                                           name == NULL ? (char_u *)"" : name);
                }
                break;
@@ -4679,13 +4729,13 @@ ex_disassemble(exarg_T *eap)
                            when = "JUMP_IF_COND_TRUE";
                            break;
                    }
-                   smsg("%4d %s -> %d", current, when,
+                   smsg("%s%4d %s -> %d", pfx, current, when,
                                                iptr->isn_arg.jump.jump_where);
                }
                break;
 
            case ISN_JUMP_IF_ARG_SET:
-               smsg("%4d JUMP_IF_ARG_SET arg[%d] -> %d", current,
+               smsg("%s%4d JUMP_IF_ARG_SET arg[%d] -> %d", pfx, current,
                         iptr->isn_arg.jumparg.jump_arg_off + STACK_FRAME_SIZE,
                                                iptr->isn_arg.jump.jump_where);
                break;
@@ -4694,7 +4744,7 @@ ex_disassemble(exarg_T *eap)
                {
                    forloop_T *forloop = &iptr->isn_arg.forloop;
 
-                   smsg("%4d FOR $%d -> %d", current,
+                   smsg("%s%4d FOR $%d -> %d", pfx, current,
                                           forloop->for_idx, forloop->for_end);
                }
                break;
@@ -4704,13 +4754,13 @@ ex_disassemble(exarg_T *eap)
                    try_T *try = &iptr->isn_arg.try;
 
                    if (try->try_ref->try_finally == 0)
-                       smsg("%4d TRY catch -> %d, endtry -> %d",
-                               current,
+                       smsg("%s%4d TRY catch -> %d, endtry -> %d",
+                               pfx, current,
                                try->try_ref->try_catch,
                                try->try_ref->try_endtry);
                    else
-                       smsg("%4d TRY catch -> %d, finally -> %d, endtry -> %d",
-                               current,
+                       smsg("%s%4d TRY catch -> %d, finally -> %d, endtry -> %d",
+                               pfx, current,
                                try->try_ref->try_catch,
                                try->try_ref->try_finally,
                                try->try_ref->try_endtry);
@@ -4718,26 +4768,26 @@ ex_disassemble(exarg_T *eap)
                break;
            case ISN_CATCH:
                // TODO
-               smsg("%4d CATCH", current);
+               smsg("%s%4d CATCH", pfx, current);
                break;
            case ISN_TRYCONT:
                {
                    trycont_T *trycont = &iptr->isn_arg.trycont;
 
-                   smsg("%4d TRY-CONTINUE %d level%s -> %d", current,
+                   smsg("%s%4d TRY-CONTINUE %d level%s -> %d", pfx, current,
                                      trycont->tct_levels,
                                      trycont->tct_levels == 1 ? "" : "s",
                                      trycont->tct_where);
                }
                break;
            case ISN_FINALLY:
-               smsg("%4d FINALLY", current);
+               smsg("%s%4d FINALLY", pfx, current);
                break;
            case ISN_ENDTRY:
-               smsg("%4d ENDTRY", current);
+               smsg("%s%4d ENDTRY", pfx, current);
                break;
            case ISN_THROW:
-               smsg("%4d THROW", current);
+               smsg("%s%4d THROW", pfx, current);
                break;
 
            // expression operations on number
@@ -4764,7 +4814,7 @@ ex_disassemble(exarg_T *eap)
                        case ISN_OPANY: ins = "OPANY"; break;
                        default: ins = "???"; break;
                    }
-                   smsg("%4d %s %s", current, ins, what);
+                   smsg("%s%4d %s %s", pfx, current, ins, what);
                }
                break;
 
@@ -4817,53 +4867,53 @@ ex_disassemble(exarg_T *eap)
                           default: type = "???"; break;
                       }
 
-                      smsg("%4d %s %s", current, type, buf);
+                      smsg("%s%4d %s %s", pfx, current, type, buf);
                   }
                   break;
 
-           case ISN_ADDLIST: smsg("%4d ADDLIST", current); break;
-           case ISN_ADDBLOB: smsg("%4d ADDBLOB", current); break;
+           case ISN_ADDLIST: smsg("%s%4d ADDLIST", pfx, current); break;
+           case ISN_ADDBLOB: smsg("%s%4d ADDBLOB", pfx, current); break;
 
            // expression operations
-           case ISN_CONCAT: smsg("%4d CONCAT", current); break;
-           case ISN_STRINDEX: smsg("%4d STRINDEX", current); break;
-           case ISN_STRSLICE: smsg("%4d STRSLICE", current); break;
-           case ISN_BLOBINDEX: smsg("%4d BLOBINDEX", current); break;
-           case ISN_BLOBSLICE: smsg("%4d BLOBSLICE", current); break;
-           case ISN_LISTAPPEND: smsg("%4d LISTAPPEND", current); break;
-           case ISN_BLOBAPPEND: smsg("%4d BLOBAPPEND", current); break;
-           case ISN_LISTINDEX: smsg("%4d LISTINDEX", current); break;
-           case ISN_LISTSLICE: smsg("%4d LISTSLICE", current); break;
-           case ISN_ANYINDEX: smsg("%4d ANYINDEX", current); break;
-           case ISN_ANYSLICE: smsg("%4d ANYSLICE", current); break;
-           case ISN_SLICE: smsg("%4d SLICE %lld",
-                                        current, iptr->isn_arg.number); break;
-           case ISN_GETITEM: smsg("%4d ITEM %lld",
-                                        current, iptr->isn_arg.number); break;
-           case ISN_MEMBER: smsg("%4d MEMBER", current); break;
-           case ISN_STRINGMEMBER: smsg("%4d MEMBER %s", current,
+           case ISN_CONCAT: smsg("%s%4d CONCAT", pfx, current); break;
+           case ISN_STRINDEX: smsg("%s%4d STRINDEX", pfx, current); break;
+           case ISN_STRSLICE: smsg("%s%4d STRSLICE", pfx, current); break;
+           case ISN_BLOBINDEX: smsg("%s%4d BLOBINDEX", pfx, current); break;
+           case ISN_BLOBSLICE: smsg("%s%4d BLOBSLICE", pfx, current); break;
+           case ISN_LISTAPPEND: smsg("%s%4d LISTAPPEND", pfx, current); break;
+           case ISN_BLOBAPPEND: smsg("%s%4d BLOBAPPEND", pfx, current); break;
+           case ISN_LISTINDEX: smsg("%s%4d LISTINDEX", pfx, current); break;
+           case ISN_LISTSLICE: smsg("%s%4d LISTSLICE", pfx, current); break;
+           case ISN_ANYINDEX: smsg("%s%4d ANYINDEX", pfx, current); break;
+           case ISN_ANYSLICE: smsg("%s%4d ANYSLICE", pfx, current); break;
+           case ISN_SLICE: smsg("%s%4d SLICE %lld",
+                                        pfx, current, iptr->isn_arg.number); break;
+           case ISN_GETITEM: smsg("%s%4d ITEM %lld",
+                                        pfx, current, iptr->isn_arg.number); break;
+           case ISN_MEMBER: smsg("%s%4d MEMBER", pfx, current); break;
+           case ISN_STRINGMEMBER: smsg("%s%4d MEMBER %s", pfx, current,
                                                  iptr->isn_arg.string); break;
-           case ISN_NEGATENR: smsg("%4d NEGATENR", current); break;
+           case ISN_NEGATENR: smsg("%s%4d NEGATENR", pfx, current); break;
 
-           case ISN_CHECKNR: smsg("%4d CHECKNR", current); break;
+           case ISN_CHECKNR: smsg("%s%4d CHECKNR", pfx, current); break;
            case ISN_CHECKTYPE:
                  {
                      checktype_T *ct = &iptr->isn_arg.type;
                      char *tofree;
 
                      if (ct->ct_arg_idx == 0)
-                         smsg("%4d CHECKTYPE %s stack[%d]", current,
+                         smsg("%s%4d CHECKTYPE %s stack[%d]", pfx, current,
                                          type_name(ct->ct_type, &tofree),
                                          (int)ct->ct_off);
                      else
-                         smsg("%4d CHECKTYPE %s stack[%d] arg %d", current,
+                         smsg("%s%4d CHECKTYPE %s stack[%d] arg %d", pfx, current,
                                          type_name(ct->ct_type, &tofree),
                                          (int)ct->ct_off,
                                          (int)ct->ct_arg_idx);
                      vim_free(tofree);
                      break;
                  }
-           case ISN_CHECKLEN: smsg("%4d CHECKLEN %s%d", current,
+           case ISN_CHECKLEN: smsg("%s%4d CHECKLEN %s%d", pfx, current,
                                iptr->isn_arg.checklen.cl_more_OK ? ">= " : "",
                                iptr->isn_arg.checklen.cl_min_len);
                               break;
@@ -4871,34 +4921,34 @@ ex_disassemble(exarg_T *eap)
                  {
                      char *tofree;
 
-                     smsg("%4d SETTYPE %s", current,
+                     smsg("%s%4d SETTYPE %s", pfx, current,
                              type_name(iptr->isn_arg.type.ct_type, &tofree));
                      vim_free(tofree);
                      break;
                  }
-           case ISN_COND2BOOL: smsg("%4d COND2BOOL", current); break;
+           case ISN_COND2BOOL: smsg("%s%4d COND2BOOL", pfx, current); break;
            case ISN_2BOOL: if (iptr->isn_arg.number)
-                               smsg("%4d INVERT (!val)", current);
+                               smsg("%s%4d INVERT (!val)", pfx, current);
                            else
-                               smsg("%4d 2BOOL (!!val)", current);
+                               smsg("%s%4d 2BOOL (!!val)", pfx, current);
                            break;
-           case ISN_2STRING: smsg("%4d 2STRING stack[%lld]", current,
+           case ISN_2STRING: smsg("%s%4d 2STRING stack[%lld]", pfx, current,
                                         (varnumber_T)(iptr->isn_arg.number));
                              break;
-           case ISN_2STRING_ANY: smsg("%4d 2STRING_ANY stack[%lld]", current,
+           case ISN_2STRING_ANY: smsg("%s%4d 2STRING_ANY stack[%lld]", pfx, current,
                                         (varnumber_T)(iptr->isn_arg.number));
                              break;
-           case ISN_RANGE: smsg("%4d RANGE %s", current, iptr->isn_arg.string);
+           case ISN_RANGE: smsg("%s%4d RANGE %s", pfx, current, iptr->isn_arg.string);
                            break;
            case ISN_PUT:
                if (iptr->isn_arg.put.put_lnum == LNUM_VARIABLE_RANGE_ABOVE)
-                   smsg("%4d PUT %c above range",
-                                      current, iptr->isn_arg.put.put_regname);
+                   smsg("%s%4d PUT %c above range",
+                                      pfx, current, iptr->isn_arg.put.put_regname);
                else if (iptr->isn_arg.put.put_lnum == LNUM_VARIABLE_RANGE)
-                   smsg("%4d PUT %c range",
-                                      current, iptr->isn_arg.put.put_regname);
+                   smsg("%s%4d PUT %c range",
+                                      pfx, current, iptr->isn_arg.put.put_regname);
                else
-                   smsg("%4d PUT %c %ld", current,
+                   smsg("%s%4d PUT %c %ld", pfx, current,
                                                 iptr->isn_arg.put.put_regname,
                                             (long)iptr->isn_arg.put.put_lnum);
                break;
@@ -4915,30 +4965,33 @@ ex_disassemble(exarg_T *eap)
                    {
                        (void)produce_cmdmods(
                                   buf, iptr->isn_arg.cmdmod.cf_cmdmod, FALSE);
-                       smsg("%4d CMDMOD %s", current, buf);
+                       smsg("%s%4d CMDMOD %s", pfx, current, buf);
                        vim_free(buf);
                    }
                    break;
                }
-           case ISN_CMDMOD_REV: smsg("%4d CMDMOD_REV", current); break;
+           case ISN_CMDMOD_REV: smsg("%s%4d CMDMOD_REV", pfx, current); break;
 
            case ISN_PROF_START:
-                smsg("%4d PROFILE START line %d", current, iptr->isn_lnum);
+                smsg("%s%4d PROFILE START line %d", pfx, current, iptr->isn_lnum);
                 break;
 
            case ISN_PROF_END:
-               smsg("%4d PROFILE END", current);
+               smsg("%s%4d PROFILE END", pfx, current);
                break;
 
-           case ISN_UNPACK: smsg("%4d UNPACK %d%s", current,
+           case ISN_UNPACK: smsg("%s%4d UNPACK %d%s", pfx, current,
                        iptr->isn_arg.unpack.unp_count,
                        iptr->isn_arg.unpack.unp_semicolon ? " semicolon" : "");
                              break;
-           case ISN_SHUFFLE: smsg("%4d SHUFFLE %d up %d", current,
+           case ISN_SHUFFLE: smsg("%s%4d SHUFFLE %d up %d", pfx, current,
                                         iptr->isn_arg.shuffle.shfl_item,
                                         iptr->isn_arg.shuffle.shfl_up);
                              break;
-           case ISN_DROP: smsg("%4d DROP", current); break;
+           case ISN_DROP: smsg("%s%4d DROP", pfx, current); break;
+
+           case ISN_FINISH: // End of list of instructions for ISN_SUBSTITUTE.
+                          return;
        }
 
        out_flush();        // output one line at a time
@@ -4948,6 +5001,78 @@ ex_disassemble(exarg_T *eap)
     }
 }
 
+/*
+ * ":disassemble".
+ * We don't really need this at runtime, but we do have tests that require it,
+ * so always include this.
+ */
+    void
+ex_disassemble(exarg_T *eap)
+{
+    char_u     *arg = eap->arg;
+    char_u     *fname;
+    ufunc_T    *ufunc;
+    dfunc_T    *dfunc;
+    isn_T      *instr;
+    int                instr_count;
+    int                is_global = FALSE;
+
+    if (STRNCMP(arg, "<lambda>", 8) == 0)
+    {
+       arg += 8;
+       (void)getdigits(&arg);
+       fname = vim_strnsave(eap->arg, arg - eap->arg);
+    }
+    else
+       fname = trans_function_name(&arg, &is_global, FALSE,
+                     TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD, NULL, NULL, NULL);
+    if (fname == NULL)
+    {
+       semsg(_(e_invarg2), eap->arg);
+       return;
+    }
+
+    ufunc = find_func(fname, is_global, NULL);
+    if (ufunc == NULL)
+    {
+       char_u *p = untrans_function_name(fname);
+
+       if (p != NULL)
+           // Try again without making it script-local.
+           ufunc = find_func(p, FALSE, NULL);
+    }
+    vim_free(fname);
+    if (ufunc == NULL)
+    {
+       semsg(_(e_cannot_find_function_str), eap->arg);
+       return;
+    }
+    if (func_needs_compiling(ufunc, eap->forceit)
+           && compile_def_function(ufunc, FALSE, eap->forceit, NULL) == FAIL)
+       return;
+    if (ufunc->uf_def_status != UF_COMPILED)
+    {
+       semsg(_(e_function_is_not_compiled_str), eap->arg);
+       return;
+    }
+    if (ufunc->uf_name_exp != NULL)
+       msg((char *)ufunc->uf_name_exp);
+    else
+       msg((char *)ufunc->uf_name);
+
+    dfunc = ((dfunc_T *)def_functions.ga_data) + ufunc->uf_dfunc_idx;
+#ifdef FEAT_PROFILE
+    instr = eap->forceit ? dfunc->df_instr_prof : dfunc->df_instr;
+    instr_count = eap->forceit ? dfunc->df_instr_prof_count
+                                                      : dfunc->df_instr_count;
+#else
+    instr = dfunc->df_instr;
+    instr_count = dfunc->df_instr_count;
+#endif
+
+    list_instructions("", instr, instr_count, ufunc);
+}
+
 /*
  * Return TRUE when "tv" is not falsy: non-zero, non-empty string, non-empty
  * list, etc.  Mostly like what JavaScript does, except that empty list and