changeset 24488:f293bb501b30 v8.2.2784

patch 8.2.2784: Vim9: cannot use =expr in :substitute Commit: https://github.com/vim/vim/commit/4c13721482d7786f92f5a56e43b0f5c499264b7e Author: Bram Moolenaar <Bram@vim.org> Date: Mon Apr 19 16:48:48 2021 +0200 patch 8.2.2784: Vim9: cannot use \=expr in :substitute Problem: Vim9: cannot use \=expr in :substitute. Solution: Compile the expression into instructions and execute them when invoked.
author Bram Moolenaar <Bram@vim.org>
date Mon, 19 Apr 2021 17:00:04 +0200
parents 2bcec3ba6a08
children c2b93bfb68f8
files 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
diffstat 11 files changed, 1480 insertions(+), 1189 deletions(-) [+]
line wrap: on
line diff
--- a/src/ex_cmds.c
+++ b/src/ex_cmds.c
@@ -3604,6 +3604,29 @@ typedef struct {
 } 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)
 	{
--- a/src/globals.h
+++ b/src/globals.h
@@ -1379,6 +1379,9 @@ EXTERN char_u no_lines_msg[]	INIT(= N_("
 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];
 
--- a/src/proto/ex_cmds.pro
+++ b/src/proto/ex_cmds.pro
@@ -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);
--- a/src/proto/vim9execute.pro
+++ b/src/proto/vim9execute.pro
@@ -4,6 +4,7 @@ void funcstack_check_refcount(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);
--- a/src/regexp.c
+++ b/src/regexp.c
@@ -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);
 
--- a/src/testdir/test_vim9_cmd.vim
+++ b/src/testdir/test_vim9_cmd.vim
@@ -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
--- a/src/testdir/test_vim9_disassemble.vim
+++ b/src/testdir/test_vim9_disassemble.vim
@@ -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
--- a/src/version.c
+++ b/src/version.c
@@ -751,6 +751,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    2784,
+/**/
     2783,
 /**/
     2782,
--- a/src/vim9.h
+++ b/src/vim9.h
@@ -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;
 };
 
--- a/src/vim9compile.c
+++ b/src/vim9compile.c
@@ -2130,6 +2130,33 @@ generate_EXECCONCAT(cctx_T *cctx, int co
     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.
  */
@@ -8466,6 +8493,55 @@ theend:
 }
 
 /*
+ * :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;
     }
--- a/src/vim9execute.c
+++ b/src/vim9execute.c
@@ -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 *fu
  * 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
     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_
 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,255 +1257,35 @@ fill_partial_and_closure(partial_T *pt, 
     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;
-	}
-    }
-
-    // If depth of calling is getting too high, don't execute the function.
-    orig_funcdepth = funcdepth_get();
-    if (funcdepth_increment() == FAIL)
-	return FAIL;
-
-    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);
-
-    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;
-    }
-
-    // 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;
-    }
-
-    // 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)
-	{
-	    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;
-    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;
-
-	// "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;
-	}
-    }
-
-    // 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;
+#define STACK_TV_VAR(idx) (((typval_T *)ectx->ec_stack.ga_data) + ectx->ec_frame_idx + STACK_FRAME_SIZE + idx)
+
+/*
+ * Execute instructions in execution context "ectx".
+ * Return OK or FAIL;
+ */
+    static int
+exec_instructions(ectx_T *ectx)
+{
+    int		breakcheck_count = 0;
+    typval_T	*tv;
 
     // Start execution at the first instruction.
-    ectx.ec_iidx = 0;
+    ectx->ec_iidx = 0;
 
     for (;;)
     {
@@ -1521,7 +1301,7 @@ call_def_function(
 	    // Turn CTRL-C into an exception.
 	    got_int = FALSE;
 	    if (throw_exception("Vim:Interrupt", ET_INTERRUPT, NULL) == FAIL)
-		goto failed;
+		return FAIL;
 	    did_throw = TRUE;
 	}
 
@@ -1530,52 +1310,52 @@ call_def_function(
 	    // Turn an error message into an exception.
 	    did_emsg = FALSE;
 	    if (throw_exception(*msg_list, ET_ERROR, NULL) == FAIL)
-		goto failed;
+		return FAIL;
 	    did_throw = TRUE;
 	    *msg_list = NULL;
 	}
 
-	if (did_throw && !ectx.ec_in_catch)
+	if (did_throw && !ectx->ec_in_catch)
 	{
-	    garray_T	*trystack = &ectx.ec_trystack;
+	    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)
+	    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;
+		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;
+		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 == initial_frame_idx)
+		++ectx->ec_stack.ga_len;
+		if (ectx->ec_frame_idx == ectx->ec_initial_frame_idx)
 		{
 		    // At the toplevel we are done.
 		    need_rethrow = TRUE;
-		    if (handle_closure_in_use(&ectx, FALSE) == FAIL)
-			goto failed;
+		    if (handle_closure_in_use(ectx, FALSE) == FAIL)
+			return FAIL;
 		    goto done;
 		}
 
-		if (func_return(&funclocal, &ectx) == FAIL)
-		    goto failed;
+		if (func_return(ectx) == FAIL)
+		    return FAIL;
 	    }
 	    continue;
 	}
 
-	iptr = &ectx.ec_instr[ectx.ec_iidx++];
+	iptr = &ectx->ec_instr[ectx->ec_iidx++];
 	switch (iptr->isn_type)
 	{
 	    // execute Ex command line
@@ -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;
-
-		    may_restore_cmdmod(&funclocal);
+		    garray_T	*trystack = &ectx->ec_trystack;
+
+		    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,62 +3894,345 @@ 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 *)"");
+    }
+
+    ectx->ec_instr = save_instr;
+    ectx->ec_iidx = save_iidx;
+
+    return res;
+}
+
+/*
+ * 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;
+
+// Get pointer to item in the stack.
+#undef STACK_TV
+#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)
+
+// 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)
+
+    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;
+	}
+    }
+
+    // If depth of calling is getting too high, don't execute the function.
+    orig_funcdepth = funcdepth_get();
+    if (funcdepth_increment() == FAIL)
+	return FAIL;
+
+    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);
+    ectx.ec_did_emsg_before = did_emsg_before;
+    ectx.ec_trylevel_at_start = trylevel;
+
+    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;
+    }
+
+    // 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;
     }
-    ret = OK;
-
-failed:
+
+    // 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)
+	{
+	    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;
+
+    {
+	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;
+	}
+    }
+
+    // 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;
+
+    ectx.ec_where.wt_index = 0;
+    ectx.ec_where.wt_variable = FALSE;
+
+    // Execute the instructions until done.
+    ret = exec_instructions(&ectx);
+    if (ret == OK)
+    {
+	// 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;
+	}
+    }
+
     // When failed need to unwind the call stack.
-    while (ectx.ec_frame_idx != initial_frame_idx)
-	func_return(&funclocal, &ectx);
+    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)
@@ -4162,11 +4260,11 @@ failed:
     }
     msg_list = saved_msg_list;
 
-    if (funclocal.floc_restore_cmdmod)
+    if (ectx.ec_funclocal.floc_restore_cmdmod)
     {
 	cmdmod.cmod_filter_regmatch.regprog = NULL;
 	undo_cmdmod(&cmdmod);
-	cmdmod = funclocal.floc_save_cmdmod;
+	cmdmod = ectx.ec_funclocal.floc_save_cmdmod;
     }
     emsg_silent_def = save_emsg_silent_def;
     did_emsg_def += save_did_emsg_def;
@@ -4199,6 +4297,711 @@ failed_early:
 }
 
 /*
+ * 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;
+
+	if (ufunc != NULL)
+	    while (line_idx < iptr->isn_lnum
+					  && line_idx < ufunc->uf_lines.ga_len)
+	    {
+		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);
+	    }
+
+	switch (iptr->isn_type)
+	{
+	    case ISN_EXEC:
+		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("%s%4d EXECCONCAT %lld", pfx, current,
+					      (varnumber_T)iptr->isn_arg.number);
+		break;
+	    case ISN_ECHO:
+		{
+		    echo_T *echo = &iptr->isn_arg.echo;
+
+		    smsg("%s%4d %s %d", pfx, current,
+			    echo->echo_with_white ? "ECHO" : "ECHON",
+			    echo->echo_count);
+		}
+		break;
+	    case ISN_EXECUTE:
+		smsg("%s%4d EXECUTE %lld", pfx, current,
+					    (varnumber_T)(iptr->isn_arg.number));
+		break;
+	    case ISN_ECHOMSG:
+		smsg("%s%4d ECHOMSG %lld", pfx, current,
+					    (varnumber_T)(iptr->isn_arg.number));
+		break;
+	    case ISN_ECHOERR:
+		smsg("%s%4d ECHOERR %lld", pfx, current,
+					    (varnumber_T)(iptr->isn_arg.number));
+		break;
+	    case ISN_LOAD:
+		{
+		    if (iptr->isn_arg.number < 0)
+			smsg("%s%4d LOAD arg[%lld]", pfx, current,
+				(varnumber_T)(iptr->isn_arg.number
+							  + STACK_FRAME_SIZE));
+		    else
+			smsg("%s%4d LOAD $%lld", pfx, current,
+					  (varnumber_T)(iptr->isn_arg.number));
+		}
+		break;
+	    case ISN_LOADOUTER:
+		{
+		    if (iptr->isn_arg.number < 0)
+			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("%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("%s%4d LOADV v:%s", pfx, current,
+				       get_vim_var_name(iptr->isn_arg.number));
+		break;
+	    case ISN_LOADSCRIPT:
+		{
+		    scriptref_T	*sref = iptr->isn_arg.script.scriptref;
+		    scriptitem_T *si = SCRIPT_ITEM(sref->sref_sid);
+		    svar_T *sv = ((svar_T *)si->sn_var_vals.ga_data)
+							      + sref->sref_idx;
+
+		    smsg("%s%4d LOADSCRIPT %s-%d from %s", pfx, current,
+					    sv->sv_name,
+					    sref->sref_idx,
+					    si->sn_name);
+		}
+		break;
+	    case ISN_LOADS:
+		{
+		    scriptitem_T *si = SCRIPT_ITEM(
+					       iptr->isn_arg.loadstore.ls_sid);
+
+		    smsg("%s%4d LOADS s:%s from %s", pfx, current,
+				 iptr->isn_arg.loadstore.ls_name, si->sn_name);
+		}
+		break;
+	    case ISN_LOADAUTO:
+		smsg("%s%4d LOADAUTO %s", pfx, current, iptr->isn_arg.string);
+		break;
+	    case ISN_LOADG:
+		smsg("%s%4d LOADG g:%s", pfx, current, iptr->isn_arg.string);
+		break;
+	    case ISN_LOADB:
+		smsg("%s%4d LOADB b:%s", pfx, current, iptr->isn_arg.string);
+		break;
+	    case ISN_LOADW:
+		smsg("%s%4d LOADW w:%s", pfx, current, iptr->isn_arg.string);
+		break;
+	    case ISN_LOADT:
+		smsg("%s%4d LOADT t:%s", pfx, current, iptr->isn_arg.string);
+		break;
+	    case ISN_LOADGDICT:
+		smsg("%s%4d LOAD g:", pfx, current);
+		break;
+	    case ISN_LOADBDICT:
+		smsg("%s%4d LOAD b:", pfx, current);
+		break;
+	    case ISN_LOADWDICT:
+		smsg("%s%4d LOAD w:", pfx, current);
+		break;
+	    case ISN_LOADTDICT:
+		smsg("%s%4d LOAD t:", pfx, current);
+		break;
+	    case ISN_LOADOPT:
+		smsg("%s%4d LOADOPT %s", pfx, current, iptr->isn_arg.string);
+		break;
+	    case ISN_LOADENV:
+		smsg("%s%4d LOADENV %s", pfx, current, iptr->isn_arg.string);
+		break;
+	    case ISN_LOADREG:
+		smsg("%s%4d LOADREG @%c", pfx, current, (int)(iptr->isn_arg.number));
+		break;
+
+	    case ISN_STORE:
+		if (iptr->isn_arg.number < 0)
+		    smsg("%s%4d STORE arg[%lld]", pfx, current,
+				      iptr->isn_arg.number + STACK_FRAME_SIZE);
+		else
+		    smsg("%s%4d STORE $%lld", pfx, current, iptr->isn_arg.number);
+		break;
+	    case ISN_STOREOUTER:
+		{
+		if (iptr->isn_arg.number < 0)
+		    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("%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("%s%4d STOREV v:%s", pfx, current,
+				       get_vim_var_name(iptr->isn_arg.number));
+		break;
+	    case ISN_STOREAUTO:
+		smsg("%s%4d STOREAUTO %s", pfx, current, iptr->isn_arg.string);
+		break;
+	    case ISN_STOREG:
+		smsg("%s%4d STOREG %s", pfx, current, iptr->isn_arg.string);
+		break;
+	    case ISN_STOREB:
+		smsg("%s%4d STOREB %s", pfx, current, iptr->isn_arg.string);
+		break;
+	    case ISN_STOREW:
+		smsg("%s%4d STOREW %s", pfx, current, iptr->isn_arg.string);
+		break;
+	    case ISN_STORET:
+		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("%s%4d STORES %s in %s", pfx, current,
+				 iptr->isn_arg.loadstore.ls_name, si->sn_name);
+		}
+		break;
+	    case ISN_STORESCRIPT:
+		{
+		    scriptref_T	*sref = iptr->isn_arg.script.scriptref;
+		    scriptitem_T *si = SCRIPT_ITEM(sref->sref_sid);
+		    svar_T *sv = ((svar_T *)si->sn_var_vals.ga_data)
+							      + sref->sref_idx;
+
+		    smsg("%s%4d STORESCRIPT %s-%d in %s", pfx, current,
+					     sv->sv_name,
+					     sref->sref_idx,
+					     si->sn_name);
+		}
+		break;
+	    case ISN_STOREOPT:
+		smsg("%s%4d STOREOPT &%s", pfx, current,
+					       iptr->isn_arg.storeopt.so_name);
+		break;
+	    case ISN_STOREENV:
+		smsg("%s%4d STOREENV $%s", pfx, current, iptr->isn_arg.string);
+		break;
+	    case ISN_STOREREG:
+		smsg("%s%4d STOREREG @%c", pfx, current, (int)iptr->isn_arg.number);
+		break;
+	    case ISN_STORENR:
+		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("%s%4d STOREINDEX %s", pfx, current,
+					  vartype_name(iptr->isn_arg.vartype));
+		break;
+
+	    case ISN_STORERANGE:
+		smsg("%s%4d STORERANGE", pfx, current);
+		break;
+
+	    // constants
+	    case ISN_PUSHNR:
+		smsg("%s%4d PUSHNR %lld", pfx, current,
+					    (varnumber_T)(iptr->isn_arg.number));
+		break;
+	    case ISN_PUSHBOOL:
+	    case ISN_PUSHSPEC:
+		smsg("%s%4d PUSH %s", pfx, current,
+				   get_var_special_name(iptr->isn_arg.number));
+		break;
+	    case ISN_PUSHF:
+#ifdef FEAT_FLOAT
+		smsg("%s%4d PUSHF %g", pfx, current, iptr->isn_arg.fnumber);
+#endif
+		break;
+	    case ISN_PUSHS:
+		smsg("%s%4d PUSHS \"%s\"", pfx, current, iptr->isn_arg.string);
+		break;
+	    case ISN_PUSHBLOB:
+		{
+		    char_u	*r;
+		    char_u	numbuf[NUMBUFLEN];
+		    char_u	*tofree;
+
+		    r = blob2string(iptr->isn_arg.blob, &tofree, numbuf);
+		    smsg("%s%4d PUSHBLOB %s", pfx, current, r);
+		    vim_free(tofree);
+		}
+		break;
+	    case ISN_PUSHFUNC:
+		{
+		    char *name = (char *)iptr->isn_arg.string;
+
+		    smsg("%s%4d PUSHFUNC \"%s\"", pfx, current,
+					       name == NULL ? "[none]" : name);
+		}
+		break;
+	    case ISN_PUSHCHANNEL:
+#ifdef FEAT_JOB_CHANNEL
+		{
+		    channel_T *channel = iptr->isn_arg.channel;
+
+		    smsg("%s%4d PUSHCHANNEL %d", pfx, current,
+					 channel == NULL ? 0 : channel->ch_id);
+		}
+#endif
+		break;
+	    case ISN_PUSHJOB:
+#ifdef FEAT_JOB_CHANNEL
+		{
+		    typval_T	tv;
+		    char_u	*name;
+
+		    tv.v_type = VAR_JOB;
+		    tv.vval.v_job = iptr->isn_arg.job;
+		    name = tv_get_string(&tv);
+		    smsg("%s%4d PUSHJOB \"%s\"", pfx, current, name);
+		}
+#endif
+		break;
+	    case ISN_PUSHEXC:
+		smsg("%s%4d PUSH v:exception", pfx, current);
+		break;
+	    case ISN_UNLET:
+		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("%s%4d UNLETENV%s $%s", pfx, current,
+			iptr->isn_arg.unlet.ul_forceit ? "!" : "",
+			iptr->isn_arg.unlet.ul_name);
+		break;
+	    case ISN_UNLETINDEX:
+		smsg("%s%4d UNLETINDEX", pfx, current);
+		break;
+	    case ISN_UNLETRANGE:
+		smsg("%s%4d UNLETRANGE", pfx, current);
+		break;
+	    case ISN_LOCKCONST:
+		smsg("%s%4d LOCKCONST", pfx, current);
+		break;
+	    case ISN_NEWLIST:
+		smsg("%s%4d NEWLIST size %lld", pfx, current,
+					    (varnumber_T)(iptr->isn_arg.number));
+		break;
+	    case ISN_NEWDICT:
+		smsg("%s%4d NEWDICT size %lld", pfx, current,
+					    (varnumber_T)(iptr->isn_arg.number));
+		break;
+
+	    // function call
+	    case ISN_BCALL:
+		{
+		    cbfunc_T	*cbfunc = &iptr->isn_arg.bfunc;
+
+		    smsg("%s%4d BCALL %s(argc %d)", pfx, current,
+			    internal_func_name(cbfunc->cbf_idx),
+			    cbfunc->cbf_argcount);
+		}
+		break;
+	    case ISN_DCALL:
+		{
+		    cdfunc_T	*cdfunc = &iptr->isn_arg.dfunc;
+		    dfunc_T	*df = ((dfunc_T *)def_functions.ga_data)
+							     + cdfunc->cdf_idx;
+
+		    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);
+		}
+		break;
+	    case ISN_UCALL:
+		{
+		    cufunc_T	*cufunc = &iptr->isn_arg.ufunc;
+
+		    smsg("%s%4d UCALL %s(argc %d)", pfx, current,
+				       cufunc->cuf_name, cufunc->cuf_argcount);
+		}
+		break;
+	    case ISN_PCALL:
+		{
+		    cpfunc_T	*cpfunc = &iptr->isn_arg.pfunc;
+
+		    smsg("%s%4d PCALL%s (argc %d)", pfx, current,
+			   cpfunc->cpf_top ? " top" : "", cpfunc->cpf_argcount);
+		}
+		break;
+	    case ISN_PCALL_END:
+		smsg("%s%4d PCALL end", pfx, current);
+		break;
+	    case ISN_RETURN:
+		smsg("%s%4d RETURN", pfx, current);
+		break;
+	    case ISN_RETURN_ZERO:
+		smsg("%s%4d RETURN 0", pfx, current);
+		break;
+	    case ISN_FUNCREF:
+		{
+		    funcref_T	*funcref = &iptr->isn_arg.funcref;
+		    dfunc_T	*df = ((dfunc_T *)def_functions.ga_data)
+							    + funcref->fr_func;
+
+		    smsg("%s%4d FUNCREF %s", pfx, current, df->df_ufunc->uf_name);
+		}
+		break;
+
+	    case ISN_NEWFUNC:
+		{
+		    newfunc_T	*newfunc = &iptr->isn_arg.newfunc;
+
+		    smsg("%s%4d NEWFUNC %s %s", pfx, current,
+				       newfunc->nf_lambda, newfunc->nf_global);
+		}
+		break;
+
+	    case ISN_DEF:
+		{
+		    char_u *name = iptr->isn_arg.string;
+
+		    smsg("%s%4d DEF %s", pfx, current,
+					   name == NULL ? (char_u *)"" : name);
+		}
+		break;
+
+	    case ISN_JUMP:
+		{
+		    char *when = "?";
+
+		    switch (iptr->isn_arg.jump.jump_when)
+		    {
+			case JUMP_ALWAYS:
+			    when = "JUMP";
+			    break;
+			case JUMP_AND_KEEP_IF_TRUE:
+			    when = "JUMP_AND_KEEP_IF_TRUE";
+			    break;
+			case JUMP_IF_FALSE:
+			    when = "JUMP_IF_FALSE";
+			    break;
+			case JUMP_AND_KEEP_IF_FALSE:
+			    when = "JUMP_AND_KEEP_IF_FALSE";
+			    break;
+			case JUMP_IF_COND_FALSE:
+			    when = "JUMP_IF_COND_FALSE";
+			    break;
+			case JUMP_IF_COND_TRUE:
+			    when = "JUMP_IF_COND_TRUE";
+			    break;
+		    }
+		    smsg("%s%4d %s -> %d", pfx, current, when,
+						iptr->isn_arg.jump.jump_where);
+		}
+		break;
+
+	    case ISN_JUMP_IF_ARG_SET:
+		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;
+
+	    case ISN_FOR:
+		{
+		    forloop_T *forloop = &iptr->isn_arg.forloop;
+
+		    smsg("%s%4d FOR $%d -> %d", pfx, current,
+					   forloop->for_idx, forloop->for_end);
+		}
+		break;
+
+	    case ISN_TRY:
+		{
+		    try_T *try = &iptr->isn_arg.try;
+
+		    if (try->try_ref->try_finally == 0)
+			smsg("%s%4d TRY catch -> %d, endtry -> %d",
+				pfx, current,
+				try->try_ref->try_catch,
+				try->try_ref->try_endtry);
+		    else
+			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);
+		}
+		break;
+	    case ISN_CATCH:
+		// TODO
+		smsg("%s%4d CATCH", pfx, current);
+		break;
+	    case ISN_TRYCONT:
+		{
+		    trycont_T *trycont = &iptr->isn_arg.trycont;
+
+		    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("%s%4d FINALLY", pfx, current);
+		break;
+	    case ISN_ENDTRY:
+		smsg("%s%4d ENDTRY", pfx, current);
+		break;
+	    case ISN_THROW:
+		smsg("%s%4d THROW", pfx, current);
+		break;
+
+	    // expression operations on number
+	    case ISN_OPNR:
+	    case ISN_OPFLOAT:
+	    case ISN_OPANY:
+		{
+		    char *what;
+		    char *ins;
+
+		    switch (iptr->isn_arg.op.op_type)
+		    {
+			case EXPR_MULT: what = "*"; break;
+			case EXPR_DIV: what = "/"; break;
+			case EXPR_REM: what = "%"; break;
+			case EXPR_SUB: what = "-"; break;
+			case EXPR_ADD: what = "+"; break;
+			default:       what = "???"; break;
+		    }
+		    switch (iptr->isn_type)
+		    {
+			case ISN_OPNR: ins = "OPNR"; break;
+			case ISN_OPFLOAT: ins = "OPFLOAT"; break;
+			case ISN_OPANY: ins = "OPANY"; break;
+			default: ins = "???"; break;
+		    }
+		    smsg("%s%4d %s %s", pfx, current, ins, what);
+		}
+		break;
+
+	    case ISN_COMPAREBOOL:
+	    case ISN_COMPARESPECIAL:
+	    case ISN_COMPARENR:
+	    case ISN_COMPAREFLOAT:
+	    case ISN_COMPARESTRING:
+	    case ISN_COMPAREBLOB:
+	    case ISN_COMPARELIST:
+	    case ISN_COMPAREDICT:
+	    case ISN_COMPAREFUNC:
+	    case ISN_COMPAREANY:
+		   {
+		       char *p;
+		       char buf[10];
+		       char *type;
+
+		       switch (iptr->isn_arg.op.op_type)
+		       {
+			   case EXPR_EQUAL:	 p = "=="; break;
+			   case EXPR_NEQUAL:    p = "!="; break;
+			   case EXPR_GREATER:   p = ">"; break;
+			   case EXPR_GEQUAL:    p = ">="; break;
+			   case EXPR_SMALLER:   p = "<"; break;
+			   case EXPR_SEQUAL:    p = "<="; break;
+			   case EXPR_MATCH:	 p = "=~"; break;
+			   case EXPR_IS:	 p = "is"; break;
+			   case EXPR_ISNOT:	 p = "isnot"; break;
+			   case EXPR_NOMATCH:	 p = "!~"; break;
+			   default:  p = "???"; break;
+		       }
+		       STRCPY(buf, p);
+		       if (iptr->isn_arg.op.op_ic == TRUE)
+			   strcat(buf, "?");
+		       switch(iptr->isn_type)
+		       {
+			   case ISN_COMPAREBOOL: type = "COMPAREBOOL"; break;
+			   case ISN_COMPARESPECIAL:
+						 type = "COMPARESPECIAL"; break;
+			   case ISN_COMPARENR: type = "COMPARENR"; break;
+			   case ISN_COMPAREFLOAT: type = "COMPAREFLOAT"; break;
+			   case ISN_COMPARESTRING:
+						  type = "COMPARESTRING"; break;
+			   case ISN_COMPAREBLOB: type = "COMPAREBLOB"; break;
+			   case ISN_COMPARELIST: type = "COMPARELIST"; break;
+			   case ISN_COMPAREDICT: type = "COMPAREDICT"; break;
+			   case ISN_COMPAREFUNC: type = "COMPAREFUNC"; break;
+			   case ISN_COMPAREANY: type = "COMPAREANY"; break;
+			   default: type = "???"; break;
+		       }
+
+		       smsg("%s%4d %s %s", pfx, current, type, buf);
+		   }
+		   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("%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("%s%4d NEGATENR", pfx, 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("%s%4d CHECKTYPE %s stack[%d]", pfx, current,
+					  type_name(ct->ct_type, &tofree),
+					  (int)ct->ct_off);
+		      else
+			  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("%s%4d CHECKLEN %s%d", pfx, current,
+				iptr->isn_arg.checklen.cl_more_OK ? ">= " : "",
+				iptr->isn_arg.checklen.cl_min_len);
+			       break;
+	    case ISN_SETTYPE:
+		  {
+		      char *tofree;
+
+		      smsg("%s%4d SETTYPE %s", pfx, current,
+			      type_name(iptr->isn_arg.type.ct_type, &tofree));
+		      vim_free(tofree);
+		      break;
+		  }
+	    case ISN_COND2BOOL: smsg("%s%4d COND2BOOL", pfx, current); break;
+	    case ISN_2BOOL: if (iptr->isn_arg.number)
+				smsg("%s%4d INVERT (!val)", pfx, current);
+			    else
+				smsg("%s%4d 2BOOL (!!val)", pfx, current);
+			    break;
+	    case ISN_2STRING: smsg("%s%4d 2STRING stack[%lld]", pfx, current,
+					 (varnumber_T)(iptr->isn_arg.number));
+			      break;
+	    case ISN_2STRING_ANY: smsg("%s%4d 2STRING_ANY stack[%lld]", pfx, current,
+					 (varnumber_T)(iptr->isn_arg.number));
+			      break;
+	    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("%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("%s%4d PUT %c range",
+				       pfx, current, iptr->isn_arg.put.put_regname);
+		else
+		    smsg("%s%4d PUT %c %ld", pfx, current,
+						 iptr->isn_arg.put.put_regname,
+					     (long)iptr->isn_arg.put.put_lnum);
+		break;
+
+		// TODO: summarize modifiers
+	    case ISN_CMDMOD:
+		{
+		    char_u  *buf;
+		    size_t  len = produce_cmdmods(
+				  NULL, iptr->isn_arg.cmdmod.cf_cmdmod, FALSE);
+
+		    buf = alloc(len + 1);
+		    if (buf != NULL)
+		    {
+			(void)produce_cmdmods(
+				   buf, iptr->isn_arg.cmdmod.cf_cmdmod, FALSE);
+			smsg("%s%4d CMDMOD %s", pfx, current, buf);
+			vim_free(buf);
+		    }
+		    break;
+		}
+	    case ISN_CMDMOD_REV: smsg("%s%4d CMDMOD_REV", pfx, current); break;
+
+	    case ISN_PROF_START:
+		 smsg("%s%4d PROFILE START line %d", pfx, current, iptr->isn_lnum);
+		 break;
+
+	    case ISN_PROF_END:
+		smsg("%s%4d PROFILE END", pfx, current);
+		break;
+
+	    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("%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("%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
+	ui_breakcheck();
+	if (got_int)
+	    break;
+    }
+}
+
+/*
  * ":disassemble".
  * We don't really need this at runtime, but we do have tests that require it,
  * so always include this.
@@ -4212,9 +5015,6 @@ ex_disassemble(exarg_T *eap)
     dfunc_T	*dfunc;
     isn_T	*instr;
     int		instr_count;
-    int		current;
-    int		line_idx = 0;
-    int		prev_current = 0;
     int		is_global = FALSE;
 
     if (STRNCMP(arg, "<lambda>", 8) == 0)
@@ -4269,683 +5069,8 @@ ex_disassemble(exarg_T *eap)
     instr = dfunc->df_instr;
     instr_count = dfunc->df_instr_count;
 #endif
-    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)
-	    {
-		msg_puts("\n\n");
-		prev_current = current;
-	    }
-	    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);
-		break;
-	    case ISN_EXECCONCAT:
-		smsg("%4d EXECCONCAT %lld", current,
-					      (varnumber_T)iptr->isn_arg.number);
-		break;
-	    case ISN_ECHO:
-		{
-		    echo_T *echo = &iptr->isn_arg.echo;
-
-		    smsg("%4d %s %d", current,
-			    echo->echo_with_white ? "ECHO" : "ECHON",
-			    echo->echo_count);
-		}
-		break;
-	    case ISN_EXECUTE:
-		smsg("%4d EXECUTE %lld", current,
-					    (varnumber_T)(iptr->isn_arg.number));
-		break;
-	    case ISN_ECHOMSG:
-		smsg("%4d ECHOMSG %lld", current,
-					    (varnumber_T)(iptr->isn_arg.number));
-		break;
-	    case ISN_ECHOERR:
-		smsg("%4d ECHOERR %lld", current,
-					    (varnumber_T)(iptr->isn_arg.number));
-		break;
-	    case ISN_LOAD:
-		{
-		    if (iptr->isn_arg.number < 0)
-			smsg("%4d LOAD arg[%lld]", current,
-				(varnumber_T)(iptr->isn_arg.number
-							  + STACK_FRAME_SIZE));
-		    else
-			smsg("%4d LOAD $%lld", 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,
-				iptr->isn_arg.outer.outer_depth,
-				iptr->isn_arg.outer.outer_idx
-							  + STACK_FRAME_SIZE);
-		    else
-			smsg("%4d LOADOUTER level %d $%d", current,
-					      iptr->isn_arg.outer.outer_depth,
-					      iptr->isn_arg.outer.outer_idx);
-		}
-		break;
-	    case ISN_LOADV:
-		smsg("%4d LOADV v:%s", current,
-				       get_vim_var_name(iptr->isn_arg.number));
-		break;
-	    case ISN_LOADSCRIPT:
-		{
-		    scriptref_T	*sref = iptr->isn_arg.script.scriptref;
-		    scriptitem_T *si = SCRIPT_ITEM(sref->sref_sid);
-		    svar_T *sv = ((svar_T *)si->sn_var_vals.ga_data)
-							      + sref->sref_idx;
-
-		    smsg("%4d LOADSCRIPT %s-%d from %s", current,
-					    sv->sv_name,
-					    sref->sref_idx,
-					    si->sn_name);
-		}
-		break;
-	    case ISN_LOADS:
-		{
-		    scriptitem_T *si = SCRIPT_ITEM(
-					       iptr->isn_arg.loadstore.ls_sid);
-
-		    smsg("%4d LOADS s:%s from %s", current,
-				 iptr->isn_arg.loadstore.ls_name, si->sn_name);
-		}
-		break;
-	    case ISN_LOADAUTO:
-		smsg("%4d LOADAUTO %s", current, iptr->isn_arg.string);
-		break;
-	    case ISN_LOADG:
-		smsg("%4d LOADG g:%s", current, iptr->isn_arg.string);
-		break;
-	    case ISN_LOADB:
-		smsg("%4d LOADB b:%s", current, iptr->isn_arg.string);
-		break;
-	    case ISN_LOADW:
-		smsg("%4d LOADW w:%s", current, iptr->isn_arg.string);
-		break;
-	    case ISN_LOADT:
-		smsg("%4d LOADT t:%s", current, iptr->isn_arg.string);
-		break;
-	    case ISN_LOADGDICT:
-		smsg("%4d LOAD g:", current);
-		break;
-	    case ISN_LOADBDICT:
-		smsg("%4d LOAD b:", current);
-		break;
-	    case ISN_LOADWDICT:
-		smsg("%4d LOAD w:", current);
-		break;
-	    case ISN_LOADTDICT:
-		smsg("%4d LOAD t:", current);
-		break;
-	    case ISN_LOADOPT:
-		smsg("%4d LOADOPT %s", current, iptr->isn_arg.string);
-		break;
-	    case ISN_LOADENV:
-		smsg("%4d LOADENV %s", current, iptr->isn_arg.string);
-		break;
-	    case ISN_LOADREG:
-		smsg("%4d LOADREG @%c", current, (int)(iptr->isn_arg.number));
-		break;
-
-	    case ISN_STORE:
-		if (iptr->isn_arg.number < 0)
-		    smsg("%4d STORE arg[%lld]", current,
-				      iptr->isn_arg.number + STACK_FRAME_SIZE);
-		else
-		    smsg("%4d STORE $%lld", current, iptr->isn_arg.number);
-		break;
-	    case ISN_STOREOUTER:
-		{
-		if (iptr->isn_arg.number < 0)
-		    smsg("%4d STOREOUTEr level %d arg[%d]", current,
-			    iptr->isn_arg.outer.outer_depth,
-			    iptr->isn_arg.outer.outer_idx + STACK_FRAME_SIZE);
-		else
-		    smsg("%4d STOREOUTER level %d $%d", current,
-			    iptr->isn_arg.outer.outer_depth,
-			    iptr->isn_arg.outer.outer_idx);
-		}
-		break;
-	    case ISN_STOREV:
-		smsg("%4d STOREV v:%s", current,
-				       get_vim_var_name(iptr->isn_arg.number));
-		break;
-	    case ISN_STOREAUTO:
-		smsg("%4d STOREAUTO %s", current, iptr->isn_arg.string);
-		break;
-	    case ISN_STOREG:
-		smsg("%4d STOREG %s", current, iptr->isn_arg.string);
-		break;
-	    case ISN_STOREB:
-		smsg("%4d STOREB %s", current, iptr->isn_arg.string);
-		break;
-	    case ISN_STOREW:
-		smsg("%4d STOREW %s", current, iptr->isn_arg.string);
-		break;
-	    case ISN_STORET:
-		smsg("%4d STORET %s", 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,
-				 iptr->isn_arg.loadstore.ls_name, si->sn_name);
-		}
-		break;
-	    case ISN_STORESCRIPT:
-		{
-		    scriptref_T	*sref = iptr->isn_arg.script.scriptref;
-		    scriptitem_T *si = SCRIPT_ITEM(sref->sref_sid);
-		    svar_T *sv = ((svar_T *)si->sn_var_vals.ga_data)
-							      + sref->sref_idx;
-
-		    smsg("%4d STORESCRIPT %s-%d in %s", current,
-					     sv->sv_name,
-					     sref->sref_idx,
-					     si->sn_name);
-		}
-		break;
-	    case ISN_STOREOPT:
-		smsg("%4d STOREOPT &%s", current,
-					       iptr->isn_arg.storeopt.so_name);
-		break;
-	    case ISN_STOREENV:
-		smsg("%4d STOREENV $%s", current, iptr->isn_arg.string);
-		break;
-	    case ISN_STOREREG:
-		smsg("%4d STOREREG @%c", current, (int)iptr->isn_arg.number);
-		break;
-	    case ISN_STORENR:
-		smsg("%4d STORE %lld in $%d", current,
-				iptr->isn_arg.storenr.stnr_val,
-				iptr->isn_arg.storenr.stnr_idx);
-		break;
-
-	    case ISN_STOREINDEX:
-		smsg("%4d STOREINDEX %s", current,
-					  vartype_name(iptr->isn_arg.vartype));
-		break;
-
-	    case ISN_STORERANGE:
-		smsg("%4d STORERANGE", current);
-		break;
-
-	    // constants
-	    case ISN_PUSHNR:
-		smsg("%4d PUSHNR %lld", current,
-					    (varnumber_T)(iptr->isn_arg.number));
-		break;
-	    case ISN_PUSHBOOL:
-	    case ISN_PUSHSPEC:
-		smsg("%4d PUSH %s", 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);
-#endif
-		break;
-	    case ISN_PUSHS:
-		smsg("%4d PUSHS \"%s\"", current, iptr->isn_arg.string);
-		break;
-	    case ISN_PUSHBLOB:
-		{
-		    char_u	*r;
-		    char_u	numbuf[NUMBUFLEN];
-		    char_u	*tofree;
-
-		    r = blob2string(iptr->isn_arg.blob, &tofree, numbuf);
-		    smsg("%4d PUSHBLOB %s", current, r);
-		    vim_free(tofree);
-		}
-		break;
-	    case ISN_PUSHFUNC:
-		{
-		    char *name = (char *)iptr->isn_arg.string;
-
-		    smsg("%4d PUSHFUNC \"%s\"", current,
-					       name == NULL ? "[none]" : name);
-		}
-		break;
-	    case ISN_PUSHCHANNEL:
-#ifdef FEAT_JOB_CHANNEL
-		{
-		    channel_T *channel = iptr->isn_arg.channel;
-
-		    smsg("%4d PUSHCHANNEL %d", current,
-					 channel == NULL ? 0 : channel->ch_id);
-		}
-#endif
-		break;
-	    case ISN_PUSHJOB:
-#ifdef FEAT_JOB_CHANNEL
-		{
-		    typval_T	tv;
-		    char_u	*name;
-
-		    tv.v_type = VAR_JOB;
-		    tv.vval.v_job = iptr->isn_arg.job;
-		    name = tv_get_string(&tv);
-		    smsg("%4d PUSHJOB \"%s\"", current, name);
-		}
-#endif
-		break;
-	    case ISN_PUSHEXC:
-		smsg("%4d PUSH v:exception", current);
-		break;
-	    case ISN_UNLET:
-		smsg("%4d UNLET%s %s", current,
-			iptr->isn_arg.unlet.ul_forceit ? "!" : "",
-			iptr->isn_arg.unlet.ul_name);
-		break;
-	    case ISN_UNLETENV:
-		smsg("%4d UNLETENV%s $%s", current,
-			iptr->isn_arg.unlet.ul_forceit ? "!" : "",
-			iptr->isn_arg.unlet.ul_name);
-		break;
-	    case ISN_UNLETINDEX:
-		smsg("%4d UNLETINDEX", current);
-		break;
-	    case ISN_UNLETRANGE:
-		smsg("%4d UNLETRANGE", current);
-		break;
-	    case ISN_LOCKCONST:
-		smsg("%4d LOCKCONST", current);
-		break;
-	    case ISN_NEWLIST:
-		smsg("%4d NEWLIST size %lld", current,
-					    (varnumber_T)(iptr->isn_arg.number));
-		break;
-	    case ISN_NEWDICT:
-		smsg("%4d NEWDICT size %lld", current,
-					    (varnumber_T)(iptr->isn_arg.number));
-		break;
-
-	    // function call
-	    case ISN_BCALL:
-		{
-		    cbfunc_T	*cbfunc = &iptr->isn_arg.bfunc;
-
-		    smsg("%4d BCALL %s(argc %d)", current,
-			    internal_func_name(cbfunc->cbf_idx),
-			    cbfunc->cbf_argcount);
-		}
-		break;
-	    case ISN_DCALL:
-		{
-		    cdfunc_T	*cdfunc = &iptr->isn_arg.dfunc;
-		    dfunc_T	*df = ((dfunc_T *)def_functions.ga_data)
-							     + cdfunc->cdf_idx;
-
-		    smsg("%4d DCALL %s(argc %d)", current,
-			    df->df_ufunc->uf_name_exp != NULL
-				? df->df_ufunc->uf_name_exp
-				: df->df_ufunc->uf_name, cdfunc->cdf_argcount);
-		}
-		break;
-	    case ISN_UCALL:
-		{
-		    cufunc_T	*cufunc = &iptr->isn_arg.ufunc;
-
-		    smsg("%4d UCALL %s(argc %d)", current,
-				       cufunc->cuf_name, cufunc->cuf_argcount);
-		}
-		break;
-	    case ISN_PCALL:
-		{
-		    cpfunc_T	*cpfunc = &iptr->isn_arg.pfunc;
-
-		    smsg("%4d PCALL%s (argc %d)", current,
-			   cpfunc->cpf_top ? " top" : "", cpfunc->cpf_argcount);
-		}
-		break;
-	    case ISN_PCALL_END:
-		smsg("%4d PCALL end", current);
-		break;
-	    case ISN_RETURN:
-		smsg("%4d RETURN", current);
-		break;
-	    case ISN_RETURN_ZERO:
-		smsg("%4d RETURN 0", current);
-		break;
-	    case ISN_FUNCREF:
-		{
-		    funcref_T	*funcref = &iptr->isn_arg.funcref;
-		    dfunc_T	*df = ((dfunc_T *)def_functions.ga_data)
-							    + funcref->fr_func;
-
-		    smsg("%4d FUNCREF %s", current, df->df_ufunc->uf_name);
-		}
-		break;
-
-	    case ISN_NEWFUNC:
-		{
-		    newfunc_T	*newfunc = &iptr->isn_arg.newfunc;
-
-		    smsg("%4d NEWFUNC %s %s", current,
-				       newfunc->nf_lambda, newfunc->nf_global);
-		}
-		break;
-
-	    case ISN_DEF:
-		{
-		    char_u *name = iptr->isn_arg.string;
-
-		    smsg("%4d DEF %s", current,
-					   name == NULL ? (char_u *)"" : name);
-		}
-		break;
-
-	    case ISN_JUMP:
-		{
-		    char *when = "?";
-
-		    switch (iptr->isn_arg.jump.jump_when)
-		    {
-			case JUMP_ALWAYS:
-			    when = "JUMP";
-			    break;
-			case JUMP_AND_KEEP_IF_TRUE:
-			    when = "JUMP_AND_KEEP_IF_TRUE";
-			    break;
-			case JUMP_IF_FALSE:
-			    when = "JUMP_IF_FALSE";
-			    break;
-			case JUMP_AND_KEEP_IF_FALSE:
-			    when = "JUMP_AND_KEEP_IF_FALSE";
-			    break;
-			case JUMP_IF_COND_FALSE:
-			    when = "JUMP_IF_COND_FALSE";
-			    break;
-			case JUMP_IF_COND_TRUE:
-			    when = "JUMP_IF_COND_TRUE";
-			    break;
-		    }
-		    smsg("%4d %s -> %d", 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,
-			 iptr->isn_arg.jumparg.jump_arg_off + STACK_FRAME_SIZE,
-						iptr->isn_arg.jump.jump_where);
-		break;
-
-	    case ISN_FOR:
-		{
-		    forloop_T *forloop = &iptr->isn_arg.forloop;
-
-		    smsg("%4d FOR $%d -> %d", current,
-					   forloop->for_idx, forloop->for_end);
-		}
-		break;
-
-	    case ISN_TRY:
-		{
-		    try_T *try = &iptr->isn_arg.try;
-
-		    if (try->try_ref->try_finally == 0)
-			smsg("%4d TRY catch -> %d, endtry -> %d",
-				current,
-				try->try_ref->try_catch,
-				try->try_ref->try_endtry);
-		    else
-			smsg("%4d TRY catch -> %d, finally -> %d, endtry -> %d",
-				current,
-				try->try_ref->try_catch,
-				try->try_ref->try_finally,
-				try->try_ref->try_endtry);
-		}
-		break;
-	    case ISN_CATCH:
-		// TODO
-		smsg("%4d CATCH", current);
-		break;
-	    case ISN_TRYCONT:
-		{
-		    trycont_T *trycont = &iptr->isn_arg.trycont;
-
-		    smsg("%4d TRY-CONTINUE %d level%s -> %d", current,
-				      trycont->tct_levels,
-				      trycont->tct_levels == 1 ? "" : "s",
-				      trycont->tct_where);
-		}
-		break;
-	    case ISN_FINALLY:
-		smsg("%4d FINALLY", current);
-		break;
-	    case ISN_ENDTRY:
-		smsg("%4d ENDTRY", current);
-		break;
-	    case ISN_THROW:
-		smsg("%4d THROW", current);
-		break;
-
-	    // expression operations on number
-	    case ISN_OPNR:
-	    case ISN_OPFLOAT:
-	    case ISN_OPANY:
-		{
-		    char *what;
-		    char *ins;
-
-		    switch (iptr->isn_arg.op.op_type)
-		    {
-			case EXPR_MULT: what = "*"; break;
-			case EXPR_DIV: what = "/"; break;
-			case EXPR_REM: what = "%"; break;
-			case EXPR_SUB: what = "-"; break;
-			case EXPR_ADD: what = "+"; break;
-			default:       what = "???"; break;
-		    }
-		    switch (iptr->isn_type)
-		    {
-			case ISN_OPNR: ins = "OPNR"; break;
-			case ISN_OPFLOAT: ins = "OPFLOAT"; break;
-			case ISN_OPANY: ins = "OPANY"; break;
-			default: ins = "???"; break;
-		    }
-		    smsg("%4d %s %s", current, ins, what);
-		}
-		break;
-
-	    case ISN_COMPAREBOOL:
-	    case ISN_COMPARESPECIAL:
-	    case ISN_COMPARENR:
-	    case ISN_COMPAREFLOAT:
-	    case ISN_COMPARESTRING:
-	    case ISN_COMPAREBLOB:
-	    case ISN_COMPARELIST:
-	    case ISN_COMPAREDICT:
-	    case ISN_COMPAREFUNC:
-	    case ISN_COMPAREANY:
-		   {
-		       char *p;
-		       char buf[10];
-		       char *type;
-
-		       switch (iptr->isn_arg.op.op_type)
-		       {
-			   case EXPR_EQUAL:	 p = "=="; break;
-			   case EXPR_NEQUAL:    p = "!="; break;
-			   case EXPR_GREATER:   p = ">"; break;
-			   case EXPR_GEQUAL:    p = ">="; break;
-			   case EXPR_SMALLER:   p = "<"; break;
-			   case EXPR_SEQUAL:    p = "<="; break;
-			   case EXPR_MATCH:	 p = "=~"; break;
-			   case EXPR_IS:	 p = "is"; break;
-			   case EXPR_ISNOT:	 p = "isnot"; break;
-			   case EXPR_NOMATCH:	 p = "!~"; break;
-			   default:  p = "???"; break;
-		       }
-		       STRCPY(buf, p);
-		       if (iptr->isn_arg.op.op_ic == TRUE)
-			   strcat(buf, "?");
-		       switch(iptr->isn_type)
-		       {
-			   case ISN_COMPAREBOOL: type = "COMPAREBOOL"; break;
-			   case ISN_COMPARESPECIAL:
-						 type = "COMPARESPECIAL"; break;
-			   case ISN_COMPARENR: type = "COMPARENR"; break;
-			   case ISN_COMPAREFLOAT: type = "COMPAREFLOAT"; break;
-			   case ISN_COMPARESTRING:
-						  type = "COMPARESTRING"; break;
-			   case ISN_COMPAREBLOB: type = "COMPAREBLOB"; break;
-			   case ISN_COMPARELIST: type = "COMPARELIST"; break;
-			   case ISN_COMPAREDICT: type = "COMPAREDICT"; break;
-			   case ISN_COMPAREFUNC: type = "COMPAREFUNC"; break;
-			   case ISN_COMPAREANY: type = "COMPAREANY"; break;
-			   default: type = "???"; break;
-		       }
-
-		       smsg("%4d %s %s", current, type, buf);
-		   }
-		   break;
-
-	    case ISN_ADDLIST: smsg("%4d ADDLIST", current); break;
-	    case ISN_ADDBLOB: smsg("%4d ADDBLOB", 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,
-						  iptr->isn_arg.string); break;
-	    case ISN_NEGATENR: smsg("%4d NEGATENR", current); break;
-
-	    case ISN_CHECKNR: smsg("%4d CHECKNR", 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,
-					  type_name(ct->ct_type, &tofree),
-					  (int)ct->ct_off);
-		      else
-			  smsg("%4d CHECKTYPE %s stack[%d] arg %d", 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,
-				iptr->isn_arg.checklen.cl_more_OK ? ">= " : "",
-				iptr->isn_arg.checklen.cl_min_len);
-			       break;
-	    case ISN_SETTYPE:
-		  {
-		      char *tofree;
-
-		      smsg("%4d SETTYPE %s", current,
-			      type_name(iptr->isn_arg.type.ct_type, &tofree));
-		      vim_free(tofree);
-		      break;
-		  }
-	    case ISN_COND2BOOL: smsg("%4d COND2BOOL", current); break;
-	    case ISN_2BOOL: if (iptr->isn_arg.number)
-				smsg("%4d INVERT (!val)", current);
-			    else
-				smsg("%4d 2BOOL (!!val)", current);
-			    break;
-	    case ISN_2STRING: smsg("%4d 2STRING stack[%lld]", current,
-					 (varnumber_T)(iptr->isn_arg.number));
-			      break;
-	    case ISN_2STRING_ANY: smsg("%4d 2STRING_ANY stack[%lld]", current,
-					 (varnumber_T)(iptr->isn_arg.number));
-			      break;
-	    case ISN_RANGE: smsg("%4d RANGE %s", 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);
-		else if (iptr->isn_arg.put.put_lnum == LNUM_VARIABLE_RANGE)
-		    smsg("%4d PUT %c range",
-				       current, iptr->isn_arg.put.put_regname);
-		else
-		    smsg("%4d PUT %c %ld", current,
-						 iptr->isn_arg.put.put_regname,
-					     (long)iptr->isn_arg.put.put_lnum);
-		break;
-
-		// TODO: summarize modifiers
-	    case ISN_CMDMOD:
-		{
-		    char_u  *buf;
-		    size_t  len = produce_cmdmods(
-				  NULL, iptr->isn_arg.cmdmod.cf_cmdmod, FALSE);
-
-		    buf = alloc(len + 1);
-		    if (buf != NULL)
-		    {
-			(void)produce_cmdmods(
-				   buf, iptr->isn_arg.cmdmod.cf_cmdmod, FALSE);
-			smsg("%4d CMDMOD %s", current, buf);
-			vim_free(buf);
-		    }
-		    break;
-		}
-	    case ISN_CMDMOD_REV: smsg("%4d CMDMOD_REV", current); break;
-
-	    case ISN_PROF_START:
-		 smsg("%4d PROFILE START line %d", current, iptr->isn_lnum);
-		 break;
-
-	    case ISN_PROF_END:
-		smsg("%4d PROFILE END", current);
-		break;
-
-	    case ISN_UNPACK: smsg("%4d UNPACK %d%s", 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,
-					 iptr->isn_arg.shuffle.shfl_item,
-					 iptr->isn_arg.shuffle.shfl_up);
-			      break;
-	    case ISN_DROP: smsg("%4d DROP", current); break;
-	}
-
-	out_flush();	    // output one line at a time
-	ui_breakcheck();
-	if (got_int)
-	    break;
-    }
+
+    list_instructions("", instr, instr_count, ufunc);
 }
 
 /*