Skip to main content

runmat_vm/interpreter/dispatch/
mod.rs

1mod arithmetic;
2mod arrays;
3mod calls;
4mod control_flow;
5mod exceptions;
6mod indexing;
7mod object;
8mod stack;
9
10use crate::bytecode::Instr;
11use crate::interpreter::debug;
12use crate::runtime::workspace::refresh_workspace_state;
13use runmat_builtins::Value;
14use runmat_runtime::dispatcher::gather_if_needed_async;
15use runmat_runtime::RuntimeError;
16use std::collections::HashMap;
17use std::future::Future;
18use std::pin::Pin;
19
20pub use arrays::{
21    create_matrix, create_matrix_dynamic, create_range, pack_to_col, pack_to_row, unpack,
22};
23pub use calls::{
24    build_builtin_expand_at_args, build_builtin_expand_last_args, build_builtin_expand_multi_args,
25    build_feval_expand_multi_args, build_user_function_expand_multi_args, handle_builtin_call,
26    handle_builtin_expand_at_call, handle_builtin_expand_last_call,
27    handle_builtin_expand_multi_call, handle_builtin_outcome, handle_create_closure,
28    handle_feval_dispatch, handle_load_method, handle_load_static_property, handle_method_call,
29    handle_method_or_member_index_call, handle_prepared_user_function_call, handle_register_class,
30    handle_static_method_call, handle_user_function_call, output_list_for_user_call,
31    prepare_named_user_dispatch, push_single_result, push_user_call_outputs,
32    unpack_prepared_user_call, BuiltinHandling, FevalHandling, MethodHandling,
33    PreparedUserDispatch, UserCallHandling,
34};
35pub use control_flow::{apply_control_flow_action, DispatchDecision};
36pub use exceptions::{
37    parse_exception, prepare_vm_error, redirect_exception_to_catch, ExceptionHandling,
38};
39pub use stack::{
40    emit_stack_top, emit_var, load_bool, load_char_row, load_complex, load_const, load_local,
41    load_string, load_var, store_local, store_var,
42};
43
44pub enum DispatchHandled {
45    Generic(DispatchDecision),
46    ReturnValue(DispatchDecision),
47    Return(DispatchDecision),
48}
49
50pub type InvokeUserForEndExpr<'a> = dyn for<'b> Fn(
51        &'b str,
52        Vec<Value>,
53        &'b HashMap<String, crate::bytecode::UserFunction>,
54        &'b [Value],
55    ) -> Pin<Box<dyn Future<Output = Result<Value, RuntimeError>> + 'b>>
56    + 'a;
57
58pub type BuiltinFallbackUserCall<'a> = dyn Fn(
59        String,
60        Vec<Value>,
61        usize,
62    ) -> Pin<Box<dyn Future<Output = Result<Option<Value>, RuntimeError>>>>
63    + 'a;
64
65pub type InterpretFunctionCounts<'a> = dyn Fn(
66        crate::bytecode::Bytecode,
67        Vec<Value>,
68        String,
69        usize,
70        usize,
71    ) -> Pin<Box<dyn Future<Output = Result<Vec<Value>, RuntimeError>>>>
72    + 'a;
73
74pub struct DispatchMeta<'a> {
75    pub instr: &'a Instr,
76    pub var_names: &'a HashMap<usize, String>,
77    pub bytecode_functions: &'a HashMap<String, crate::bytecode::UserFunction>,
78    pub source_id: Option<runmat_hir::SourceId>,
79    pub call_arg_spans: Option<Vec<runmat_hir::Span>>,
80    pub call_counts: &'a [(usize, usize)],
81    pub current_function_name: &'a str,
82    pub next_instr: Option<&'a Instr>,
83}
84
85pub struct DispatchState<'a> {
86    pub stack: &'a mut Vec<Value>,
87    pub vars: &'a mut Vec<Value>,
88    pub context: &'a mut crate::bytecode::ExecutionContext,
89    pub try_stack: &'a mut Vec<(usize, Option<usize>)>,
90    pub last_exception: &'a mut Option<runmat_builtins::MException>,
91    pub imports: &'a mut Vec<(Vec<String>, bool)>,
92    pub global_aliases: &'a mut HashMap<usize, String>,
93    pub persistent_aliases: &'a mut HashMap<usize, String>,
94    pub pc: &'a mut usize,
95}
96
97pub struct DispatchHooks<'a> {
98    pub clear_value_residency: &'a mut dyn FnMut(&Value),
99    pub invoke_user_for_end_expr: &'a InvokeUserForEndExpr<'a>,
100    pub builtin_fallback_user_call: &'a BuiltinFallbackUserCall<'a>,
101    pub interpret_function_counts: &'a InterpretFunctionCounts<'a>,
102    pub store_var_before_overwrite: &'a mut dyn FnMut(&Value, &Value),
103    pub store_var_after_store: &'a mut dyn FnMut(usize, &Value),
104    pub store_local_before_local_overwrite: &'a mut dyn FnMut(&Value, &Value),
105    pub store_local_before_var_overwrite: &'a mut dyn FnMut(&Value, &Value),
106    pub store_local_after_fallback_store: &'a mut dyn FnMut(&str, usize, &Value),
107}
108
109pub async fn logical_truth_from_value(value: &Value, label: &str) -> Result<bool, RuntimeError> {
110    match value {
111        Value::Bool(flag) => Ok(*flag),
112        Value::Int(i) => Ok(!i.is_zero()),
113        Value::Num(n) => Ok(*n != 0.0),
114        Value::LogicalArray(array) if array.data.len() == 1 => Ok(array.data[0] != 0),
115        Value::LogicalArray(array) => Err(crate::interpreter::errors::mex(
116            "InvalidConditionType",
117            &format!(
118                "{label}: expected scalar logical or numeric value, got logical array with {} elements",
119                array.data.len()
120            ),
121        )),
122        Value::Tensor(tensor) if tensor.data.len() == 1 => Ok(tensor.data[0] != 0.0),
123        Value::Tensor(tensor) => Err(crate::interpreter::errors::mex(
124            "InvalidConditionType",
125            &format!(
126                "{label}: expected scalar logical or numeric value, got numeric array with {} elements",
127                tensor.data.len()
128            ),
129        )),
130        Value::GpuTensor(_) => {
131            let gathered = gather_if_needed_async(value)
132                .await
133                .map_err(|e| format!("{label}: {e}"))?;
134            Box::pin(logical_truth_from_value(&gathered, label)).await
135        }
136        other => Err(crate::interpreter::errors::mex(
137            "InvalidConditionType",
138            &format!("{label}: expected scalar logical or numeric value, got {other:?}"),
139        )),
140    }
141}
142
143pub async fn dispatch_instruction(
144    meta: DispatchMeta<'_>,
145    state: DispatchState<'_>,
146    hooks: DispatchHooks<'_>,
147) -> Result<Option<DispatchHandled>, RuntimeError> {
148    let DispatchMeta {
149        instr,
150        var_names,
151        bytecode_functions,
152        source_id,
153        call_arg_spans,
154        call_counts,
155        current_function_name,
156        next_instr,
157    } = meta;
158    let DispatchState {
159        stack,
160        vars,
161        context,
162        try_stack,
163        last_exception,
164        imports,
165        global_aliases,
166        persistent_aliases,
167        pc,
168    } = state;
169    let DispatchHooks {
170        clear_value_residency,
171        invoke_user_for_end_expr,
172        builtin_fallback_user_call,
173        interpret_function_counts,
174        store_var_before_overwrite,
175        store_var_after_store,
176        store_local_before_local_overwrite,
177        store_local_before_var_overwrite,
178        store_local_after_fallback_store,
179    } = hooks;
180    match instr {
181        _ if indexing::dispatch_indexing(
182            instr,
183            stack,
184            vars,
185            &context.functions,
186            *pc,
187            &mut *clear_value_residency,
188            &invoke_user_for_end_expr,
189        )
190        .await? =>
191        {
192            Ok(Some(DispatchHandled::Generic(
193                DispatchDecision::FallThrough,
194            )))
195        }
196        _ if object::dispatch_object(instr, stack).await? => Ok(Some(DispatchHandled::Generic(
197            DispatchDecision::FallThrough,
198        ))),
199        _ if arithmetic::dispatch_arithmetic(instr, stack).await? => Ok(Some(
200            DispatchHandled::Generic(DispatchDecision::FallThrough),
201        )),
202        Instr::EmitStackTop { label } => {
203            emit_stack_top(stack, label, var_names).await?;
204            Ok(Some(DispatchHandled::Generic(
205                DispatchDecision::FallThrough,
206            )))
207        }
208        Instr::EmitVar { var_index, label } => {
209            emit_var(vars, *var_index, label, var_names).await?;
210            Ok(Some(DispatchHandled::Generic(
211                DispatchDecision::FallThrough,
212            )))
213        }
214        Instr::LoadConst(value) => {
215            load_const(stack, *value);
216            Ok(Some(DispatchHandled::Generic(
217                DispatchDecision::FallThrough,
218            )))
219        }
220        Instr::LoadComplex(re, im) => {
221            load_complex(stack, *re, *im);
222            Ok(Some(DispatchHandled::Generic(
223                DispatchDecision::FallThrough,
224            )))
225        }
226        Instr::LoadBool(value) => {
227            load_bool(stack, *value);
228            Ok(Some(DispatchHandled::Generic(
229                DispatchDecision::FallThrough,
230            )))
231        }
232        Instr::LoadString(value) => {
233            load_string(stack, value.clone());
234            Ok(Some(DispatchHandled::Generic(
235                DispatchDecision::FallThrough,
236            )))
237        }
238        Instr::LoadCharRow(value) => {
239            load_char_row(stack, value.clone())?;
240            Ok(Some(DispatchHandled::Generic(
241                DispatchDecision::FallThrough,
242            )))
243        }
244        Instr::LoadLocal(offset) => {
245            load_local(stack, context, vars, *offset)?;
246            Ok(Some(DispatchHandled::Generic(
247                DispatchDecision::FallThrough,
248            )))
249        }
250        Instr::LoadVar(index) => {
251            let value = vars[*index].clone();
252            debug::trace_load_var(*pc, *index, &value);
253            load_var(stack, vars, *index);
254            Ok(Some(DispatchHandled::Generic(
255                DispatchDecision::FallThrough,
256            )))
257        }
258        Instr::StoreVar(index) => {
259            let preview = stack
260                .last()
261                .cloned()
262                .ok_or(crate::interpreter::errors::mex(
263                    "StackUnderflow",
264                    "stack underflow",
265                ))?;
266            debug::trace_store_var(*pc, *index, &preview);
267            store_var(
268                stack,
269                vars,
270                *index,
271                var_names,
272                store_var_before_overwrite,
273                store_var_after_store,
274            )?;
275            Ok(Some(DispatchHandled::Generic(
276                DispatchDecision::FallThrough,
277            )))
278        }
279        Instr::StoreLocal(offset) => {
280            store_local(
281                stack,
282                context,
283                vars,
284                *offset,
285                store_local_before_local_overwrite,
286                store_local_before_var_overwrite,
287                store_local_after_fallback_store,
288            )?;
289            Ok(Some(DispatchHandled::Generic(
290                DispatchDecision::FallThrough,
291            )))
292        }
293        Instr::Swap => {
294            crate::ops::stack::swap(stack)?;
295            Ok(Some(DispatchHandled::Generic(
296                DispatchDecision::FallThrough,
297            )))
298        }
299        Instr::Pop => {
300            crate::ops::stack::pop(stack);
301            Ok(Some(DispatchHandled::Generic(
302                DispatchDecision::FallThrough,
303            )))
304        }
305        Instr::AndAnd(target) => Ok(Some(DispatchHandled::Generic(apply_control_flow_action(
306            crate::ops::control_flow::and_and(stack, *target)?,
307            pc,
308        )))),
309        Instr::OrOr(target) => Ok(Some(DispatchHandled::Generic(apply_control_flow_action(
310            crate::ops::control_flow::or_or(stack, *target)?,
311            pc,
312        )))),
313        Instr::JumpIfFalse(target) => {
314            let cond = crate::interpreter::stack::pop_value(stack)?;
315            let truth = logical_truth_from_value(&cond, "if condition").await?;
316            Ok(Some(DispatchHandled::Generic(apply_control_flow_action(
317                crate::ops::control_flow::jump_if_false(truth, *target),
318                pc,
319            ))))
320        }
321        Instr::Jump(target) => Ok(Some(DispatchHandled::Generic(apply_control_flow_action(
322            crate::ops::control_flow::jump(*target),
323            pc,
324        )))),
325        Instr::EnterTry(catch_pc, catch_var) => {
326            crate::ops::control_flow::enter_try(try_stack, *catch_pc, *catch_var);
327            Ok(Some(DispatchHandled::Generic(
328                DispatchDecision::FallThrough,
329            )))
330        }
331        Instr::PopTry => {
332            crate::ops::control_flow::pop_try(try_stack);
333            Ok(Some(DispatchHandled::Generic(
334                DispatchDecision::FallThrough,
335            )))
336        }
337        Instr::ReturnValue => Ok(Some(DispatchHandled::ReturnValue(
338            apply_control_flow_action(crate::ops::control_flow::return_value(stack)?, pc),
339        ))),
340        Instr::Return => Ok(Some(DispatchHandled::Return(apply_control_flow_action(
341            crate::ops::control_flow::return_void(),
342            pc,
343        )))),
344        Instr::EnterScope(local_count) => {
345            crate::ops::control_flow::enter_scope(&mut context.locals, *local_count);
346            Ok(Some(DispatchHandled::Generic(
347                DispatchDecision::FallThrough,
348            )))
349        }
350        Instr::ExitScope(local_count) => {
351            crate::ops::control_flow::exit_scope(&mut context.locals, *local_count, |val| {
352                clear_value_residency(val);
353            });
354            Ok(Some(DispatchHandled::Generic(
355                DispatchDecision::FallThrough,
356            )))
357        }
358        Instr::RegisterImport { path, wildcard } => {
359            imports.push((path.clone(), *wildcard));
360            Ok(Some(DispatchHandled::Generic(
361                DispatchDecision::FallThrough,
362            )))
363        }
364        Instr::DeclareGlobal(indices) => {
365            crate::runtime::globals::declare_global(indices.clone(), vars);
366            Ok(Some(DispatchHandled::Generic(
367                DispatchDecision::FallThrough,
368            )))
369        }
370        Instr::DeclareGlobalNamed(indices, names) => {
371            crate::runtime::globals::declare_global_named(
372                indices.clone(),
373                names.clone(),
374                vars,
375                global_aliases,
376            );
377            Ok(Some(DispatchHandled::Generic(
378                DispatchDecision::FallThrough,
379            )))
380        }
381        Instr::DeclarePersistent(indices) => {
382            crate::runtime::globals::declare_persistent(
383                current_function_name,
384                indices.clone(),
385                vars,
386            );
387            Ok(Some(DispatchHandled::Generic(
388                DispatchDecision::FallThrough,
389            )))
390        }
391        Instr::DeclarePersistentNamed(indices, names) => {
392            crate::runtime::globals::declare_persistent_named(
393                current_function_name,
394                indices.clone(),
395                names.clone(),
396                vars,
397                persistent_aliases,
398            );
399            Ok(Some(DispatchHandled::Generic(
400                DispatchDecision::FallThrough,
401            )))
402        }
403        Instr::PackToRow(count) => {
404            pack_to_row(stack, *count)?;
405            Ok(Some(DispatchHandled::Generic(
406                DispatchDecision::FallThrough,
407            )))
408        }
409        Instr::PackToCol(count) => {
410            pack_to_col(stack, *count)?;
411            Ok(Some(DispatchHandled::Generic(
412                DispatchDecision::FallThrough,
413            )))
414        }
415        Instr::Unpack(out_count) => {
416            if *out_count > 0 {
417                unpack(stack, *out_count)?;
418            }
419            Ok(Some(DispatchHandled::Generic(
420                DispatchDecision::FallThrough,
421            )))
422        }
423        Instr::CreateMatrix(rows, cols) => {
424            create_matrix(stack, *rows, *cols)?;
425            Ok(Some(DispatchHandled::Generic(
426                DispatchDecision::FallThrough,
427            )))
428        }
429        Instr::CreateMatrixDynamic(num_rows) => {
430            create_matrix_dynamic(stack, *num_rows, |rows_data| async move {
431                runmat_runtime::create_matrix_from_values(&rows_data).await
432            })
433            .await?;
434            Ok(Some(DispatchHandled::Generic(
435                DispatchDecision::FallThrough,
436            )))
437        }
438        Instr::CreateRange(has_step) => {
439            create_range(stack, *has_step, |args| async move {
440                runmat_runtime::call_builtin_async("colon", &args).await
441            })
442            .await?;
443            Ok(Some(DispatchHandled::Generic(
444                DispatchDecision::FallThrough,
445            )))
446        }
447        Instr::CreateCell2D(rows, cols) => {
448            let mut elems = Vec::with_capacity(*rows * *cols);
449            for _ in 0..(*rows * *cols) {
450                elems.push(stack.pop().ok_or(crate::interpreter::errors::mex(
451                    "StackUnderflow",
452                    "stack underflow",
453                ))?);
454            }
455            elems.reverse();
456            stack.push(crate::ops::cells::create_cell_2d(elems, *rows, *cols)?);
457            Ok(Some(DispatchHandled::Generic(
458                DispatchDecision::FallThrough,
459            )))
460        }
461        Instr::CallBuiltin(name, arg_count) => {
462            match handle_builtin_call(
463                calls::BuiltinCallContext {
464                    stack,
465                    name,
466                    arg_count: *arg_count,
467                    next_instr,
468                    source_id,
469                    call_arg_spans,
470                    imports,
471                    call_counts,
472                    exception: calls::ExceptionRouteContext {
473                        try_stack,
474                        vars,
475                        last_exception,
476                        pc,
477                    },
478                },
479                refresh_workspace_state,
480            )
481            .await?
482            {
483                BuiltinHandling::Completed => {}
484                BuiltinHandling::Caught => {
485                    return Ok(Some(DispatchHandled::Generic(
486                        DispatchDecision::ContinueLoop,
487                    )))
488                }
489                BuiltinHandling::Uncaught(err) => return Err(*err),
490            }
491            Ok(Some(DispatchHandled::Generic(
492                DispatchDecision::FallThrough,
493            )))
494        }
495        Instr::CallFeval(argc) => {
496            let args = crate::call::builtins::collect_call_args(stack, *argc)?;
497            let func_val = crate::interpreter::stack::pop_value(stack)?;
498            match handle_feval_dispatch(
499                crate::call::feval::execute_feval(
500                    func_val,
501                    args,
502                    &context.functions,
503                    bytecode_functions,
504                )
505                .await,
506                stack,
507            )? {
508                FevalHandling::Completed => {}
509                FevalHandling::InvokeUser {
510                    name,
511                    args,
512                    functions,
513                } => {
514                    let value = invoke_user_for_end_expr(&name, args, &functions, vars).await?;
515                    stack.push(value);
516                }
517            }
518            Ok(Some(DispatchHandled::Generic(
519                DispatchDecision::FallThrough,
520            )))
521        }
522        Instr::CallFevalExpandMulti(specs) => {
523            let args = build_feval_expand_multi_args(stack, specs).await?;
524            let func_val = crate::interpreter::stack::pop_value(stack)?;
525            match handle_feval_dispatch(
526                crate::call::feval::execute_feval(
527                    func_val,
528                    args,
529                    &context.functions,
530                    bytecode_functions,
531                )
532                .await,
533                stack,
534            )? {
535                FevalHandling::Completed => {}
536                FevalHandling::InvokeUser {
537                    name,
538                    args,
539                    functions,
540                } => {
541                    let value = invoke_user_for_end_expr(&name, args, &functions, vars).await?;
542                    stack.push(value);
543                }
544            }
545            Ok(Some(DispatchHandled::Generic(
546                DispatchDecision::FallThrough,
547            )))
548        }
549        Instr::CallFunction(name, arg_count) => {
550            match handle_user_function_call(
551                calls::UserCallContext {
552                    stack,
553                    name,
554                    out_count: 1,
555                    bytecode_functions,
556                    caller_functions: &mut context.functions,
557                    exception: calls::ExceptionRouteContext {
558                        try_stack,
559                        vars,
560                        last_exception,
561                        pc,
562                    },
563                },
564                *arg_count,
565                refresh_workspace_state,
566                builtin_fallback_user_call,
567                |bc, vars, name, out_count, in_count| {
568                    interpret_function_counts(bc, vars, name, out_count, in_count)
569                },
570            )
571            .await?
572            {
573                UserCallHandling::Completed => {}
574                UserCallHandling::Caught => {
575                    return Ok(Some(DispatchHandled::Generic(
576                        DispatchDecision::ContinueLoop,
577                    )))
578                }
579                UserCallHandling::Uncaught(err) => return Err(*err),
580            }
581            Ok(Some(DispatchHandled::Generic(
582                DispatchDecision::FallThrough,
583            )))
584        }
585        Instr::CallFunctionMulti(name, arg_count, out_count) => {
586            match handle_user_function_call(
587                calls::UserCallContext {
588                    stack,
589                    name,
590                    out_count: *out_count,
591                    bytecode_functions,
592                    caller_functions: &mut context.functions,
593                    exception: calls::ExceptionRouteContext {
594                        try_stack,
595                        vars,
596                        last_exception,
597                        pc,
598                    },
599                },
600                *arg_count,
601                refresh_workspace_state,
602                builtin_fallback_user_call,
603                |bc, vars, name, out_count, in_count| {
604                    interpret_function_counts(bc, vars, name, out_count, in_count)
605                },
606            )
607            .await?
608            {
609                UserCallHandling::Completed => {}
610                UserCallHandling::Caught => {
611                    return Ok(Some(DispatchHandled::Generic(
612                        DispatchDecision::ContinueLoop,
613                    )))
614                }
615                UserCallHandling::Uncaught(err) => return Err(*err),
616            }
617            Ok(Some(DispatchHandled::Generic(
618                DispatchDecision::FallThrough,
619            )))
620        }
621        Instr::CallBuiltinExpandLast(name, fixed_argc, num_indices) => {
622            handle_builtin_expand_last_call(
623                stack,
624                name,
625                *fixed_argc,
626                *num_indices,
627                next_instr,
628                |base, indices| async move {
629                    let obj = match base {
630                        Value::Object(obj) => obj,
631                        _ => unreachable!(),
632                    };
633                    let cell = crate::call::shared::subsref_paren_index_cell(&indices)?;
634                    let v = runmat_runtime::call_builtin_async(
635                        "call_method",
636                        &[
637                            Value::Object(obj),
638                            Value::String("subsref".to_string()),
639                            Value::String("()".to_string()),
640                            cell,
641                        ],
642                    )
643                    .await?;
644                    Ok(vec![v])
645                },
646            )
647            .await?;
648            Ok(Some(DispatchHandled::Generic(
649                DispatchDecision::FallThrough,
650            )))
651        }
652        Instr::CallBuiltinExpandAt(name, before_count, num_indices, after_count) => {
653            handle_builtin_expand_at_call(
654                stack,
655                name,
656                *before_count,
657                *num_indices,
658                *after_count,
659                next_instr,
660                |base, indices| async move {
661                    let obj = match base {
662                        Value::Object(obj) => obj,
663                        _ => unreachable!(),
664                    };
665                    let idx_vals =
666                        crate::call::shared::subsref_brace_numeric_index_values(&indices);
667                    let cell = runmat_runtime::call_builtin_async("__make_cell", &idx_vals).await?;
668                    let v = runmat_runtime::call_builtin_async(
669                        "call_method",
670                        &[
671                            Value::Object(obj),
672                            Value::String("subsref".to_string()),
673                            Value::String("{}".to_string()),
674                            cell,
675                        ],
676                    )
677                    .await?;
678                    Ok(vec![v])
679                },
680            )
681            .await?;
682            Ok(Some(DispatchHandled::Generic(
683                DispatchDecision::FallThrough,
684            )))
685        }
686        Instr::CallBuiltinExpandMulti(name, specs) => {
687            handle_builtin_expand_multi_call(stack, name, specs, next_instr).await?;
688            Ok(Some(DispatchHandled::Generic(
689                DispatchDecision::FallThrough,
690            )))
691        }
692        Instr::CallFunctionExpandAt(name, before_count, num_indices, after_count) => {
693            let args = build_builtin_expand_at_args(
694                stack,
695                *before_count,
696                *num_indices,
697                *after_count,
698                "CallFunctionExpandAt requires cell or object cell access",
699                |base, indices| async move {
700                    let obj = match base {
701                        Value::Object(obj) => obj,
702                        _ => unreachable!(),
703                    };
704                    let cell = crate::call::shared::subsref_brace_index_cell_raw(&indices)?;
705                    let v = runmat_runtime::call_builtin_async(
706                        "call_method",
707                        &[
708                            Value::Object(obj),
709                            Value::String("subsref".to_string()),
710                            Value::String("{}".to_string()),
711                            cell,
712                        ],
713                    )
714                    .await?;
715                    Ok(vec![v])
716                },
717            )
718            .await?;
719            let value = invoke_user_for_end_expr(name, args, bytecode_functions, vars).await?;
720            stack.push(value);
721            Ok(Some(DispatchHandled::Generic(
722                DispatchDecision::FallThrough,
723            )))
724        }
725        Instr::CallFunctionExpandMulti(name, specs) => {
726            let args = build_user_function_expand_multi_args(stack, specs).await?;
727            match handle_prepared_user_function_call(
728                calls::UserCallContext {
729                    stack,
730                    name,
731                    out_count: 1,
732                    bytecode_functions,
733                    caller_functions: &mut context.functions,
734                    exception: calls::ExceptionRouteContext {
735                        try_stack,
736                        vars,
737                        last_exception,
738                        pc,
739                    },
740                },
741                args,
742                refresh_workspace_state,
743                builtin_fallback_user_call,
744                |bc, vars, name, out_count, in_count| {
745                    interpret_function_counts(bc, vars, name, out_count, in_count)
746                },
747            )
748            .await?
749            {
750                UserCallHandling::Completed => {}
751                UserCallHandling::Caught => {
752                    return Ok(Some(DispatchHandled::Generic(
753                        DispatchDecision::ContinueLoop,
754                    )))
755                }
756                UserCallHandling::Uncaught(err) => return Err(*err),
757            }
758            Ok(Some(DispatchHandled::Generic(
759                DispatchDecision::FallThrough,
760            )))
761        }
762        Instr::CallMethod(name, arg_count) => {
763            handle_method_call(stack, name, *arg_count).await?;
764            Ok(Some(DispatchHandled::Generic(
765                DispatchDecision::FallThrough,
766            )))
767        }
768        Instr::CallMethodOrMemberIndex(name, arg_count) => {
769            handle_method_or_member_index_call(stack, name.clone(), *arg_count).await?;
770            Ok(Some(DispatchHandled::Generic(
771                DispatchDecision::FallThrough,
772            )))
773        }
774        Instr::LoadMethod(name) => {
775            handle_load_method(stack, name.clone())?;
776            Ok(Some(DispatchHandled::Generic(
777                DispatchDecision::FallThrough,
778            )))
779        }
780        Instr::CreateClosure(func_name, capture_count) => {
781            handle_create_closure(stack, func_name.clone(), *capture_count)?;
782            Ok(Some(DispatchHandled::Generic(
783                DispatchDecision::FallThrough,
784            )))
785        }
786        Instr::LoadStaticProperty(class_name, prop) => {
787            handle_load_static_property(stack, class_name, prop)?;
788            Ok(Some(DispatchHandled::Generic(
789                DispatchDecision::FallThrough,
790            )))
791        }
792        Instr::CallStaticMethod(class_name, method, arg_count) => {
793            handle_static_method_call(stack, class_name, method, *arg_count).await?;
794            Ok(Some(DispatchHandled::Generic(
795                DispatchDecision::FallThrough,
796            )))
797        }
798        Instr::RegisterClass {
799            name,
800            super_class,
801            properties,
802            methods,
803        } => {
804            handle_register_class(
805                name.clone(),
806                super_class.clone(),
807                properties.clone(),
808                methods.clone(),
809            )?;
810            Ok(Some(DispatchHandled::Generic(
811                DispatchDecision::FallThrough,
812            )))
813        }
814        _ => Ok(None),
815    }
816}