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