Skip to main content

runmat_vm/interpreter/
runner.rs

1use crate::accel::fusion as accel_fusion;
2use crate::accel::residency as accel_residency;
3use crate::bytecode::{Bytecode, FunctionRegistry, Instr};
4use crate::interpreter::api::{InterpreterOutcome, InterpreterState};
5use crate::interpreter::dispatch::{self as interp_dispatch, DispatchDecision};
6use crate::interpreter::engine as interp_engine;
7use crate::interpreter::errors::{attach_span_from_pc, mex, set_vm_pc};
8use crate::interpreter::timing::InterpreterTiming;
9use crate::runtime::call_stack::attach_call_frames;
10use crate::runtime::globals as runtime_globals;
11use crate::runtime::workspace::{
12    refresh_workspace_state, workspace_assign, workspace_clear, workspace_lookup, workspace_remove,
13    workspace_snapshot,
14};
15use runmat_builtins::{CellArray, Value};
16use runmat_runtime::{
17    user_functions,
18    workspace::{self as runtime_workspace, WorkspaceResolver},
19    RuntimeError,
20};
21use std::cell::RefCell;
22use std::collections::{HashMap, HashSet};
23use std::sync::Arc;
24use std::sync::Once;
25use tracing::{debug, info_span};
26
27#[cfg(feature = "native-accel")]
28use runmat_accelerate::{
29    activate_fusion_plan, active_group_plan_clone, deactivate_fusion_plan, set_current_pc,
30};
31
32#[cfg(feature = "native-accel")]
33struct FusionPlanGuard;
34
35#[cfg(feature = "native-accel")]
36impl Drop for FusionPlanGuard {
37    fn drop(&mut self) {
38        deactivate_fusion_plan();
39    }
40}
41
42type VmResult<T> = Result<T, RuntimeError>;
43runmat_thread_local::runmat_thread_local! {
44    static CALL_COUNTS: RefCell<Vec<(usize, usize)>> = const { RefCell::new(Vec::new()) };
45}
46
47fn sync_initial_vars(initial: &mut Vec<Value>, vars: &[Value]) {
48    initial.clear();
49    initial.extend_from_slice(vars);
50}
51
52fn ensure_workspace_resolver_registered() {
53    static REGISTER: Once = Once::new();
54    REGISTER.call_once(|| {
55        runtime_workspace::register_workspace_resolver(WorkspaceResolver {
56            lookup: workspace_lookup,
57            snapshot: workspace_snapshot,
58            globals: runtime_globals::workspace_global_names,
59            assign: Some(workspace_assign),
60            clear: Some(workspace_clear),
61            remove: Some(workspace_remove),
62        });
63    });
64}
65
66fn ensure_wasm_builtins_registered() {
67    #[cfg(target_arch = "wasm32")]
68    {
69        static REGISTER: Once = Once::new();
70        REGISTER.call_once(|| {
71            runmat_runtime::builtins::wasm_registry::register_all();
72        });
73    }
74}
75
76#[cfg(feature = "native-accel")]
77fn clear_residency(value: &Value) {
78    accel_residency::clear_value(value);
79}
80
81pub async fn invoke_semantic_function_value(
82    function: usize,
83    args: &[Value],
84    requested_outputs: usize,
85    function_registry: &FunctionRegistry,
86) -> Result<Value, RuntimeError> {
87    let (value, _) = invoke_semantic_function_value_with_capture_updates(
88        function,
89        args,
90        requested_outputs,
91        function_registry,
92    )
93    .await?;
94    Ok(value)
95}
96
97pub(crate) async fn invoke_semantic_function_value_with_capture_updates(
98    function: usize,
99    args: &[Value],
100    requested_outputs: usize,
101    function_registry: &FunctionRegistry,
102) -> Result<(Value, Vec<Value>), RuntimeError> {
103    let function_id = runmat_hir::FunctionId(function);
104    let func = function_registry.get(function_id).ok_or_else(|| {
105        let message = format!("Undefined semantic function: {function}");
106        mex("UndefinedSemanticFunction", &message)
107    })?;
108    if args.len() < func.capture_slots.len() {
109        let message = format!(
110            "semantic function {} received too few arguments",
111            func.display_name
112        );
113        return Err(mex("SemanticFunctionArity", &message));
114    }
115    let runtime_arg_count = args.len() - func.capture_slots.len();
116    if runtime_arg_count > func.input_slots.len() && func.varargin_slot.is_none() {
117        let message = format!(
118            "semantic function {} expected {} inputs, got {}",
119            func.display_name,
120            func.input_slots.len(),
121            runtime_arg_count
122        );
123        return Err(mex("TooManyInputs", &message));
124    }
125    if requested_outputs > func.output_slots.len() && func.varargout_slot.is_none() {
126        let message = format!(
127            "semantic function {} expected {} outputs, got {}",
128            func.display_name,
129            func.output_slots.len(),
130            requested_outputs
131        );
132        return Err(mex("TooManyOutputs", &message));
133    }
134
135    let mut vars = vec![Value::Num(0.0); func.var_count];
136    let mut missing_input_slots = HashSet::new();
137    for (slot, value) in func.capture_slots.iter().zip(args.iter()) {
138        if *slot < vars.len() {
139            vars[*slot] = value.clone();
140        }
141    }
142    for (slot, value) in func
143        .input_slots
144        .iter()
145        .take(runtime_arg_count)
146        .zip(args.iter().skip(func.capture_slots.len()))
147    {
148        if *slot < vars.len() {
149            vars[*slot] = value.clone();
150        }
151    }
152    let default_values_by_slot: HashMap<usize, Value> = func
153        .argument_validations
154        .iter()
155        .filter_map(|validation| {
156            validation.default_value.as_ref().map(|value| {
157                let lowered = match value {
158                    crate::bytecode::program::FunctionArgDefaultValue::Number(value) => {
159                        Value::Num(*value)
160                    }
161                    crate::bytecode::program::FunctionArgDefaultValue::Bool(value) => {
162                        Value::Bool(*value)
163                    }
164                    crate::bytecode::program::FunctionArgDefaultValue::String(value) => {
165                        Value::String(value.clone())
166                    }
167                    crate::bytecode::program::FunctionArgDefaultValue::EmptyArray => Value::Tensor(
168                        runmat_builtins::Tensor::new(Vec::new(), vec![0, 0])
169                            .expect("empty default tensor"),
170                    ),
171                };
172                (validation.input_slot, lowered)
173            })
174        })
175        .collect();
176    if runtime_arg_count < func.input_slots.len() {
177        for slot in func.input_slots.iter().skip(runtime_arg_count) {
178            if let Some(default_value) = default_values_by_slot.get(slot) {
179                if *slot < vars.len() {
180                    vars[*slot] = default_value.clone();
181                }
182            } else {
183                missing_input_slots.insert(*slot);
184            }
185        }
186    }
187    validate_function_arguments(func, &vars, &missing_input_slots)?;
188    if let Some(slot) = func.varargin_slot {
189        let fixed_count = func.input_slots.len();
190        let rest = if runtime_arg_count > fixed_count {
191            args[func.capture_slots.len() + fixed_count..].to_vec()
192        } else {
193            Vec::new()
194        };
195        let cols = rest.len();
196        let cell = CellArray::new(rest, 1, cols)
197            .map_err(|err| mex("VararginPack", &format!("varargin: {err}")))?;
198        if slot < vars.len() {
199            vars[slot] = Value::Cell(cell);
200        }
201    }
202    if let Some(slot) = func.varargout_slot {
203        if slot < vars.len() {
204            let cell = CellArray::new(Vec::new(), 1, 0)
205                .map_err(|err| mex("VarargoutPack", &format!("varargout: {err}")))?;
206            vars[slot] = Value::Cell(cell);
207        }
208    }
209    if let Some(slot) = func.implicit_nargin_slot {
210        if slot < vars.len() {
211            vars[slot] = Value::Num(runtime_arg_count as f64);
212        }
213    }
214    if let Some(slot) = func.implicit_nargout_slot {
215        if slot < vars.len() {
216            vars[slot] = Value::Num(requested_outputs as f64);
217        }
218    }
219
220    let _active_semantic_function_guard =
221        user_functions::push_active_semantic_function(function_id.0);
222    let mut bytecode = Bytecode::with_instructions(func.instructions.clone(), func.var_count);
223    bytecode.instr_spans = func.instr_spans.clone();
224    bytecode.call_arg_spans = func.call_arg_spans.clone();
225    bytecode.source_id = func.source_id;
226    bytecode.var_names = func.var_names.clone();
227    let mut initially_unassigned_slots = func.initially_unassigned_slots.clone();
228    for slot in &func.capture_slots {
229        initially_unassigned_slots.remove(slot);
230    }
231    for slot in func.input_slots.iter().take(runtime_arg_count) {
232        initially_unassigned_slots.remove(slot);
233    }
234    for slot in func.input_slots.iter().skip(runtime_arg_count) {
235        if default_values_by_slot.contains_key(slot) {
236            initially_unassigned_slots.remove(slot);
237        }
238    }
239    if let Some(slot) = func.varargin_slot {
240        initially_unassigned_slots.remove(&slot);
241    }
242    if let Some(slot) = func.varargout_slot {
243        initially_unassigned_slots.remove(&slot);
244    }
245    if let Some(slot) = func.implicit_nargin_slot {
246        initially_unassigned_slots.remove(&slot);
247    }
248    if let Some(slot) = func.implicit_nargout_slot {
249        initially_unassigned_slots.remove(&slot);
250    }
251    bytecode.initially_unassigned_slots = initially_unassigned_slots;
252    bytecode.bound_functions = function_registry.functions.clone();
253    bytecode.function_registry = function_registry.clone();
254    let result_vars = interpret_function_with_counts(
255        &bytecode,
256        vars,
257        &func.display_name,
258        requested_outputs,
259        runtime_arg_count,
260        missing_input_slots,
261    )
262    .await?;
263    let output_values = collect_semantic_outputs(func, &result_vars, requested_outputs)?;
264    let updated_captures = func
265        .capture_slots
266        .iter()
267        .map(|slot| result_vars.get(*slot).cloned().unwrap_or(Value::Num(0.0)))
268        .collect::<Vec<_>>();
269    #[cfg(feature = "native-accel")]
270    clear_semantic_function_temp_residency(&result_vars, &output_values);
271    Ok((
272        output_value(output_values, requested_outputs),
273        updated_captures,
274    ))
275}
276
277fn validate_function_arguments(
278    func: &crate::bytecode::program::FunctionBytecode,
279    vars: &[Value],
280    missing_input_slots: &HashSet<usize>,
281) -> Result<(), RuntimeError> {
282    for validation in &func.argument_validations {
283        if missing_input_slots.contains(&validation.input_slot) {
284            continue;
285        }
286        let Some(input_index) = func
287            .input_slots
288            .iter()
289            .position(|slot| *slot == validation.input_slot)
290        else {
291            continue;
292        };
293        let value = vars
294            .get(validation.input_slot)
295            .ok_or_else(|| mex("InvalidInputSlot", "function argument slot out of bounds"))?;
296
297        if let Some(size) = &validation.size {
298            let (rows, cols) = value_shape_2d(value);
299            if !dim_matches(&size.rows, rows) || !dim_matches(&size.cols, cols) {
300                return Err(mex(
301                    "ArgumentValidationSize",
302                    &format!(
303                        "Function '{}' argument #{} failed size validation",
304                        func.display_name,
305                        input_index + 1
306                    ),
307                ));
308            }
309        }
310
311        if let Some(class_name) = &validation.class_name {
312            if !value_matches_class(value, class_name) {
313                return Err(mex(
314                    "ArgumentValidationClass",
315                    &format!(
316                        "Function '{}' argument #{} failed class validation (expected {})",
317                        func.display_name,
318                        input_index + 1,
319                        class_name
320                    ),
321                ));
322            }
323        }
324        for validator in &validation.validators {
325            match validator {
326                crate::bytecode::program::FunctionArgValidator::Finite => {
327                    if !value_is_finite(value) {
328                        return Err(mex(
329                            "ArgumentValidationFunction",
330                            &format!(
331                                "Function '{}' argument #{} failed mustBeFinite validation",
332                                func.display_name,
333                                input_index + 1
334                            ),
335                        ));
336                    }
337                }
338                crate::bytecode::program::FunctionArgValidator::NumericOrLogical => {
339                    if !value_is_numeric_or_logical(value) {
340                        return Err(mex(
341                            "ArgumentValidationFunction",
342                            &format!(
343                                "Function '{}' argument #{} failed mustBeNumericOrLogical validation",
344                                func.display_name,
345                                input_index + 1
346                            ),
347                        ));
348                    }
349                }
350                crate::bytecode::program::FunctionArgValidator::Text => {
351                    if !value_is_text(value) {
352                        return Err(mex(
353                            "ArgumentValidationFunction",
354                            &format!(
355                                "Function '{}' argument #{} failed mustBeText validation",
356                                func.display_name,
357                                input_index + 1
358                            ),
359                        ));
360                    }
361                }
362                crate::bytecode::program::FunctionArgValidator::Nonempty => {
363                    if value_is_empty(value) {
364                        return Err(mex(
365                            "ArgumentValidationFunction",
366                            &format!(
367                                "Function '{}' argument #{} failed mustBeNonempty validation",
368                                func.display_name,
369                                input_index + 1
370                            ),
371                        ));
372                    }
373                }
374                crate::bytecode::program::FunctionArgValidator::ScalarOrEmpty => {
375                    if !value_is_scalar_or_empty(value) {
376                        return Err(mex(
377                            "ArgumentValidationFunction",
378                            &format!(
379                                "Function '{}' argument #{} failed mustBeScalarOrEmpty validation",
380                                func.display_name,
381                                input_index + 1
382                            ),
383                        ));
384                    }
385                }
386                crate::bytecode::program::FunctionArgValidator::Real => {
387                    if !value_is_real(value) {
388                        return Err(mex(
389                            "ArgumentValidationFunction",
390                            &format!(
391                                "Function '{}' argument #{} failed mustBeReal validation",
392                                func.display_name,
393                                input_index + 1
394                            ),
395                        ));
396                    }
397                }
398                crate::bytecode::program::FunctionArgValidator::Integer => {
399                    if !value_is_integer(value) {
400                        return Err(mex(
401                            "ArgumentValidationFunction",
402                            &format!(
403                                "Function '{}' argument #{} failed mustBeInteger validation",
404                                func.display_name,
405                                input_index + 1
406                            ),
407                        ));
408                    }
409                }
410                crate::bytecode::program::FunctionArgValidator::Positive => {
411                    if !value_is_positive(value) {
412                        return Err(mex(
413                            "ArgumentValidationFunction",
414                            &format!(
415                                "Function '{}' argument #{} failed mustBePositive validation",
416                                func.display_name,
417                                input_index + 1
418                            ),
419                        ));
420                    }
421                }
422                crate::bytecode::program::FunctionArgValidator::Negative => {
423                    if !value_is_negative(value) {
424                        return Err(mex(
425                            "ArgumentValidationFunction",
426                            &format!(
427                                "Function '{}' argument #{} failed mustBeNegative validation",
428                                func.display_name,
429                                input_index + 1
430                            ),
431                        ));
432                    }
433                }
434                crate::bytecode::program::FunctionArgValidator::Nonnegative => {
435                    if !value_is_nonnegative(value) {
436                        return Err(mex(
437                            "ArgumentValidationFunction",
438                            &format!(
439                                "Function '{}' argument #{} failed mustBeNonnegative validation",
440                                func.display_name,
441                                input_index + 1
442                            ),
443                        ));
444                    }
445                }
446                crate::bytecode::program::FunctionArgValidator::Nonzero => {
447                    if !value_is_nonzero(value) {
448                        return Err(mex(
449                            "ArgumentValidationFunction",
450                            &format!(
451                                "Function '{}' argument #{} failed mustBeNonzero validation",
452                                func.display_name,
453                                input_index + 1
454                            ),
455                        ));
456                    }
457                }
458                crate::bytecode::program::FunctionArgValidator::Nonpositive => {
459                    if !value_is_nonpositive(value) {
460                        return Err(mex(
461                            "ArgumentValidationFunction",
462                            &format!(
463                                "Function '{}' argument #{} failed mustBeNonpositive validation",
464                                func.display_name,
465                                input_index + 1
466                            ),
467                        ));
468                    }
469                }
470                crate::bytecode::program::FunctionArgValidator::GreaterThanOrEqual(threshold) => {
471                    if !value_is_greater_than_or_equal(value, *threshold) {
472                        return Err(mex(
473                            "ArgumentValidationFunction",
474                            &format!(
475                                "Function '{}' argument #{} failed mustBeGreaterThanOrEqual validation",
476                                func.display_name,
477                                input_index + 1
478                            ),
479                        ));
480                    }
481                }
482                crate::bytecode::program::FunctionArgValidator::LessThanOrEqual(threshold) => {
483                    if !value_is_less_than_or_equal(value, *threshold) {
484                        return Err(mex(
485                            "ArgumentValidationFunction",
486                            &format!(
487                                "Function '{}' argument #{} failed mustBeLessThanOrEqual validation",
488                                func.display_name,
489                                input_index + 1
490                            ),
491                        ));
492                    }
493                }
494                crate::bytecode::program::FunctionArgValidator::GreaterThan(threshold) => {
495                    if !value_is_greater_than(value, *threshold) {
496                        return Err(mex(
497                            "ArgumentValidationFunction",
498                            &format!(
499                                "Function '{}' argument #{} failed mustBeGreaterThan validation",
500                                func.display_name,
501                                input_index + 1
502                            ),
503                        ));
504                    }
505                }
506                crate::bytecode::program::FunctionArgValidator::LessThan(threshold) => {
507                    if !value_is_less_than(value, *threshold) {
508                        return Err(mex(
509                            "ArgumentValidationFunction",
510                            &format!(
511                                "Function '{}' argument #{} failed mustBeLessThan validation",
512                                func.display_name,
513                                input_index + 1
514                            ),
515                        ));
516                    }
517                }
518            }
519        }
520    }
521    Ok(())
522}
523
524fn dim_matches(dim: &crate::bytecode::program::FunctionArgDim, actual: usize) -> bool {
525    match dim {
526        crate::bytecode::program::FunctionArgDim::Any => true,
527        crate::bytecode::program::FunctionArgDim::Exact(expected) => *expected == actual,
528    }
529}
530
531fn value_shape_2d(value: &Value) -> (usize, usize) {
532    match value {
533        Value::Tensor(t) => {
534            let rows = t.shape.first().copied().unwrap_or(0);
535            let cols = t.shape.get(1).copied().unwrap_or(1);
536            (rows, cols)
537        }
538        Value::ComplexTensor(t) => {
539            let rows = t.shape.first().copied().unwrap_or(0);
540            let cols = t.shape.get(1).copied().unwrap_or(1);
541            (rows, cols)
542        }
543        Value::LogicalArray(a) => {
544            let rows = a.shape.first().copied().unwrap_or(0);
545            let cols = a.shape.get(1).copied().unwrap_or(1);
546            (rows, cols)
547        }
548        Value::Cell(c) => (c.rows, c.cols),
549        Value::CharArray(c) => (c.rows, c.cols),
550        Value::StringArray(s) => {
551            let rows = s.shape.first().copied().unwrap_or(0);
552            let cols = s.shape.get(1).copied().unwrap_or(1);
553            (rows, cols)
554        }
555        _ => (1, 1),
556    }
557}
558
559fn value_matches_class(value: &Value, class_name: &str) -> bool {
560    match class_name {
561        "double" => match value {
562            Value::Num(_) => true,
563            Value::Tensor(t) => t.dtype.class_name() == "double",
564            _ => false,
565        },
566        "single" => matches!(value, Value::Tensor(t) if t.dtype.class_name() == "single"),
567        "logical" => matches!(value, Value::Bool(_) | Value::LogicalArray(_)),
568        "char" => matches!(value, Value::CharArray(_) | Value::String(_)),
569        "string" => matches!(value, Value::String(_) | Value::StringArray(_)),
570        "cell" => matches!(value, Value::Cell(_)),
571        "struct" => matches!(value, Value::Struct(_)),
572        other => match value {
573            Value::Object(obj) => obj.class_name == other,
574            Value::HandleObject(handle) => handle.class_name == other,
575            Value::ClassRef(name) => name == other,
576            _ => false,
577        },
578    }
579}
580
581fn value_is_finite(value: &Value) -> bool {
582    match value {
583        Value::Num(v) => v.is_finite(),
584        Value::Int(_) | Value::Bool(_) => true,
585        Value::Complex(re, im) => re.is_finite() && im.is_finite(),
586        Value::Tensor(t) => t.data.iter().all(|v| v.is_finite()),
587        Value::ComplexTensor(t) => t
588            .data
589            .iter()
590            .all(|(re, im)| re.is_finite() && im.is_finite()),
591        Value::LogicalArray(_) | Value::CharArray(_) => true,
592        _ => false,
593    }
594}
595
596fn value_is_numeric_or_logical(value: &Value) -> bool {
597    matches!(
598        value,
599        Value::Num(_)
600            | Value::Int(_)
601            | Value::Complex(_, _)
602            | Value::Tensor(_)
603            | Value::ComplexTensor(_)
604            | Value::Bool(_)
605            | Value::LogicalArray(_)
606    )
607}
608
609fn value_is_text(value: &Value) -> bool {
610    match value {
611        Value::String(_) | Value::StringArray(_) => true,
612        Value::CharArray(chars) => chars.rows == 1,
613        Value::Cell(cell) => cell.data.iter().all(|entry| match &**entry {
614            Value::CharArray(chars) => chars.rows == 1,
615            Value::String(_) => true,
616            _ => false,
617        }),
618        _ => false,
619    }
620}
621
622fn value_is_empty(value: &Value) -> bool {
623    match value {
624        Value::Tensor(t) => t.shape.iter().product::<usize>() == 0,
625        Value::ComplexTensor(t) => t.shape.iter().product::<usize>() == 0,
626        Value::LogicalArray(a) => a.shape.iter().product::<usize>() == 0,
627        Value::StringArray(s) => s.shape.iter().product::<usize>() == 0,
628        Value::CharArray(c) => c.rows * c.cols == 0,
629        Value::Cell(c) => c.shape.iter().product::<usize>() == 0,
630        _ => false,
631    }
632}
633
634fn value_is_scalar_or_empty(value: &Value) -> bool {
635    let (rows, cols) = value_shape_2d(value);
636    (rows == 1 && cols == 1) || (rows == 0 || cols == 0)
637}
638
639fn value_is_real(value: &Value) -> bool {
640    match value {
641        Value::Complex(_, im) => *im == 0.0,
642        Value::ComplexTensor(t) => t.data.iter().all(|(_, im)| *im == 0.0),
643        _ => true,
644    }
645}
646
647fn value_is_integer(value: &Value) -> bool {
648    match value {
649        Value::Int(_) => true,
650        Value::Num(v) => v.is_finite() && v.fract() == 0.0,
651        Value::Tensor(t) => t.data.iter().all(|v| v.is_finite() && v.fract() == 0.0),
652        Value::Complex(re, im) => *im == 0.0 && re.is_finite() && re.fract() == 0.0,
653        Value::ComplexTensor(t) => t
654            .data
655            .iter()
656            .all(|(re, im)| *im == 0.0 && re.is_finite() && re.fract() == 0.0),
657        _ => false,
658    }
659}
660
661fn value_is_positive(value: &Value) -> bool {
662    match value {
663        Value::Num(v) => v.is_finite() && *v > 0.0,
664        Value::Int(v) => v.to_i64() > 0,
665        Value::Tensor(t) => t.data.iter().all(|v| v.is_finite() && *v > 0.0),
666        Value::Complex(re, im) => *im == 0.0 && re.is_finite() && *re > 0.0,
667        Value::ComplexTensor(t) => t
668            .data
669            .iter()
670            .all(|(re, im)| *im == 0.0 && re.is_finite() && *re > 0.0),
671        _ => false,
672    }
673}
674
675fn value_is_negative(value: &Value) -> bool {
676    match value {
677        Value::Num(v) => v.is_finite() && *v < 0.0,
678        Value::Int(v) => v.to_i64() < 0,
679        Value::Tensor(t) => t.data.iter().all(|v| v.is_finite() && *v < 0.0),
680        Value::Complex(re, im) => *im == 0.0 && re.is_finite() && *re < 0.0,
681        Value::ComplexTensor(t) => t
682            .data
683            .iter()
684            .all(|(re, im)| *im == 0.0 && re.is_finite() && *re < 0.0),
685        _ => false,
686    }
687}
688
689fn value_is_nonnegative(value: &Value) -> bool {
690    match value {
691        Value::Num(v) => v.is_finite() && *v >= 0.0,
692        Value::Int(v) => v.to_i64() >= 0,
693        Value::Tensor(t) => t.data.iter().all(|v| v.is_finite() && *v >= 0.0),
694        Value::Complex(re, im) => *im == 0.0 && re.is_finite() && *re >= 0.0,
695        Value::ComplexTensor(t) => t
696            .data
697            .iter()
698            .all(|(re, im)| *im == 0.0 && re.is_finite() && *re >= 0.0),
699        _ => false,
700    }
701}
702
703fn value_is_nonzero(value: &Value) -> bool {
704    match value {
705        Value::Num(v) => v.is_finite() && *v != 0.0,
706        Value::Int(v) => v.to_i64() != 0,
707        Value::Tensor(t) => t.data.iter().all(|v| v.is_finite() && *v != 0.0),
708        Value::Complex(re, im) => re.is_finite() && im.is_finite() && (*re != 0.0 || *im != 0.0),
709        Value::ComplexTensor(t) => t
710            .data
711            .iter()
712            .all(|(re, im)| re.is_finite() && im.is_finite() && (*re != 0.0 || *im != 0.0)),
713        _ => false,
714    }
715}
716
717fn value_is_nonpositive(value: &Value) -> bool {
718    match value {
719        Value::Num(v) => v.is_finite() && *v <= 0.0,
720        Value::Int(v) => v.to_i64() <= 0,
721        Value::Tensor(t) => t.data.iter().all(|v| v.is_finite() && *v <= 0.0),
722        Value::Complex(re, im) => *im == 0.0 && re.is_finite() && *re <= 0.0,
723        Value::ComplexTensor(t) => t
724            .data
725            .iter()
726            .all(|(re, im)| *im == 0.0 && re.is_finite() && *re <= 0.0),
727        _ => false,
728    }
729}
730
731fn value_is_greater_than_or_equal(value: &Value, threshold: f64) -> bool {
732    match value {
733        Value::Num(v) => v.is_finite() && *v >= threshold,
734        Value::Int(v) => (v.to_i64() as f64) >= threshold,
735        Value::Tensor(t) => t.data.iter().all(|v| v.is_finite() && *v >= threshold),
736        Value::Complex(re, im) => *im == 0.0 && re.is_finite() && *re >= threshold,
737        Value::ComplexTensor(t) => t
738            .data
739            .iter()
740            .all(|(re, im)| *im == 0.0 && re.is_finite() && *re >= threshold),
741        _ => false,
742    }
743}
744
745fn value_is_less_than_or_equal(value: &Value, threshold: f64) -> bool {
746    match value {
747        Value::Num(v) => v.is_finite() && *v <= threshold,
748        Value::Int(v) => (v.to_i64() as f64) <= threshold,
749        Value::Tensor(t) => t.data.iter().all(|v| v.is_finite() && *v <= threshold),
750        Value::Complex(re, im) => *im == 0.0 && re.is_finite() && *re <= threshold,
751        Value::ComplexTensor(t) => t
752            .data
753            .iter()
754            .all(|(re, im)| *im == 0.0 && re.is_finite() && *re <= threshold),
755        _ => false,
756    }
757}
758
759fn value_is_greater_than(value: &Value, threshold: f64) -> bool {
760    match value {
761        Value::Num(v) => v.is_finite() && *v > threshold,
762        Value::Int(v) => (v.to_i64() as f64) > threshold,
763        Value::Tensor(t) => t.data.iter().all(|v| v.is_finite() && *v > threshold),
764        Value::Complex(re, im) => *im == 0.0 && re.is_finite() && *re > threshold,
765        Value::ComplexTensor(t) => t
766            .data
767            .iter()
768            .all(|(re, im)| *im == 0.0 && re.is_finite() && *re > threshold),
769        _ => false,
770    }
771}
772
773fn value_is_less_than(value: &Value, threshold: f64) -> bool {
774    match value {
775        Value::Num(v) => v.is_finite() && *v < threshold,
776        Value::Int(v) => (v.to_i64() as f64) < threshold,
777        Value::Tensor(t) => t.data.iter().all(|v| v.is_finite() && *v < threshold),
778        Value::Complex(re, im) => *im == 0.0 && re.is_finite() && *re < threshold,
779        Value::ComplexTensor(t) => t
780            .data
781            .iter()
782            .all(|(re, im)| *im == 0.0 && re.is_finite() && *re < threshold),
783        _ => false,
784    }
785}
786
787fn collect_semantic_outputs(
788    func: &crate::bytecode::program::FunctionBytecode,
789    result_vars: &[Value],
790    requested_outputs: usize,
791) -> Result<Vec<Value>, RuntimeError> {
792    let mut values = Vec::with_capacity(requested_outputs.max(1));
793    for slot in func.output_slots.iter().take(requested_outputs) {
794        values.push(result_vars.get(*slot).cloned().unwrap_or(Value::Num(0.0)));
795    }
796    if values.len() < requested_outputs {
797        if let Some(slot) = func.varargout_slot {
798            let available = match result_vars.get(slot) {
799                Some(Value::Cell(cell)) => {
800                    let expanded = crate::call::shared::expand_all_cell(cell)?;
801                    let available = expanded.len();
802                    for value in expanded {
803                        if values.len() >= requested_outputs {
804                            break;
805                        }
806                        values.push(value);
807                    }
808                    available
809                }
810                _ => 0,
811            };
812            if values.len() < requested_outputs {
813                let need = requested_outputs - func.output_slots.len();
814                let message = format!(
815                    "Function '{}' returned {available} varargout values, {need} requested",
816                    func.display_name
817                );
818                return Err(mex("VarargoutMismatch", &message));
819            }
820        }
821    }
822    while values.len() < requested_outputs {
823        values.push(Value::Num(0.0));
824    }
825    Ok(values)
826}
827
828fn output_value(output_values: Vec<Value>, requested_outputs: usize) -> Value {
829    match requested_outputs {
830        0 => Value::OutputList(Vec::new()),
831        1 => output_values.into_iter().next().unwrap_or(Value::Num(0.0)),
832        _ => Value::OutputList(output_values.into_iter().take(requested_outputs).collect()),
833    }
834}
835
836#[cfg(feature = "native-accel")]
837fn clear_semantic_function_temp_residency(result_vars: &[Value], output_values: &[Value]) {
838    let mut keep_values = output_values.to_vec();
839    keep_values.extend(runtime_globals::collect_thread_roots());
840    let keep = Value::OutputList(keep_values);
841    for value in result_vars {
842        accel_residency::clear_value_excluding(value, &keep);
843    }
844}
845
846pub async fn interpret_with_vars(
847    bytecode: &Bytecode,
848    initial_vars: &mut Vec<Value>,
849    current_function_name: Option<&str>,
850) -> VmResult<InterpreterOutcome> {
851    let call_counts = CALL_COUNTS.with(|cc| cc.borrow().clone());
852    let state = Box::new(InterpreterState::new(
853        bytecode.clone(),
854        initial_vars,
855        current_function_name,
856        call_counts,
857    ));
858    match Box::pin(run_interpreter(state, initial_vars)).await {
859        Ok(outcome) => Ok(outcome),
860        Err(err) => {
861            let err = attach_span_from_pc(bytecode, err);
862            let current_name = current_function_name.unwrap_or("<main>");
863            Err(attach_call_frames(bytecode, current_name, err))
864        }
865    }
866}
867
868async fn run_interpreter(
869    state: Box<InterpreterState>,
870    initial_vars: &mut Vec<Value>,
871) -> VmResult<InterpreterOutcome> {
872    let state = *state;
873    Box::pin(run_interpreter_inner(state, initial_vars)).await
874}
875
876async fn run_interpreter_inner(
877    state: InterpreterState,
878    initial_vars: &mut Vec<Value>,
879) -> VmResult<InterpreterOutcome> {
880    let run_span = info_span!(
881        "interpreter.run",
882        function = state.current_function_name.as_str()
883    );
884    let _run_guard = run_span.enter();
885    ensure_wasm_builtins_registered();
886    ensure_workspace_resolver_registered();
887    #[cfg(feature = "native-accel")]
888    activate_fusion_plan(state.fusion_plan.clone());
889    #[cfg(feature = "native-accel")]
890    let _fusion_guard = FusionPlanGuard;
891    let InterpreterState {
892        mut stack,
893        mut vars,
894        mut pc,
895        mut context,
896        mut try_stack,
897        mut last_exception,
898        mut imports,
899        mut global_aliases,
900        mut persistent_aliases,
901        mut missing_input_slots,
902        current_function_name,
903        call_counts,
904        initial_assigned_var_count,
905        #[cfg(feature = "native-accel")]
906            fusion_plan: _,
907        #[cfg(feature = "native-accel")]
908        fusion_accel_graph,
909        bytecode,
910    } = state;
911    let _source_context_guard =
912        runmat_runtime::source_context::replace_current_source_id(bytecode.source_id);
913    let _arity_call_counts_guard =
914        runmat_runtime::builtins::introspection::arity_check::replace_call_counts(
915            call_counts.clone(),
916        );
917    let function_registry = Arc::new(bytecode.function_registry());
918    let previous_semantic_invoker = user_functions::current_semantic_function_invoker();
919    let registry_for_function_invoker = Arc::clone(&function_registry);
920    let _semantic_function_guard =
921        user_functions::install_semantic_function_invoker(Some(Arc::new(
922            move |function: usize, args: &[Value], requested_outputs: usize| {
923                let args = args.to_vec();
924                let previous_invoker = previous_semantic_invoker.clone();
925                let function_registry = Arc::clone(&registry_for_function_invoker);
926                Box::pin(async move {
927                    let local_function = function_registry
928                        .get(runmat_hir::FunctionId(function))
929                        .is_some();
930                    if !local_function {
931                        if let Some(invoker) = previous_invoker {
932                            return invoker(function, &args, requested_outputs).await;
933                        }
934                    }
935                    invoke_semantic_function_value(
936                        function,
937                        &args,
938                        requested_outputs,
939                        &function_registry,
940                    )
941                    .await
942                })
943            },
944        )));
945    let previous_semantic_resolver = user_functions::current_semantic_function_resolver();
946    let registry_for_function_resolver = Arc::clone(&function_registry);
947    let _semantic_resolver_guard =
948        user_functions::install_semantic_function_resolver(Some(Arc::new(move |name: &str| {
949            if let Some(active_function) = user_functions::current_active_semantic_function() {
950                if let Some(function) =
951                    registry_for_function_resolver.get(runmat_hir::FunctionId(active_function))
952                {
953                    if let Some(scoped_function) = registry_for_function_resolver
954                        .resolve_name_in_private_scope(&function.private_owner_scope, name)
955                    {
956                        return Some(scoped_function.0);
957                    }
958                }
959            }
960            if let Some(function) = registry_for_function_resolver.resolve_name(name) {
961                return Some(function.0);
962            }
963            previous_semantic_resolver
964                .as_ref()
965                .and_then(|resolver| resolver(name))
966        })));
967    let mut source_function_catalog = function_registry
968        .functions
969        .values()
970        .filter_map(|function| {
971            function.source_id.map(
972                |source_id| runmat_runtime::user_functions::SourceFunctionInfo {
973                    source_id,
974                    name: function.display_name.clone(),
975                    function: function.function.0,
976                },
977            )
978        })
979        .collect::<Vec<_>>();
980    source_function_catalog.sort_by_key(|info| info.function);
981    let _source_function_catalog_guard =
982        user_functions::install_source_function_catalog(Some(Arc::new(source_function_catalog)));
983    CALL_COUNTS.with(|cc| {
984        *cc.borrow_mut() = call_counts.clone();
985    });
986    let _workspace_guard = interp_engine::prepare_workspace_guard(
987        &bytecode.var_names,
988        &mut vars,
989        initial_assigned_var_count,
990        &bytecode.initially_unassigned_slots,
991    );
992    let thread_roots: Vec<Value> = runtime_globals::collect_thread_roots();
993    let mut _gc_context = interp_engine::create_gc_context(&stack, &vars, thread_roots)?;
994    let debug_stack = interp_engine::debug_stack_enabled();
995    let mut interpreter_timing = InterpreterTiming::new();
996    while pc < bytecode.instructions.len() {
997        set_vm_pc(pc);
998        #[cfg(feature = "native-accel")]
999        set_current_pc(pc);
1000        if let Err(err) = interp_engine::check_cancelled() {
1001            #[cfg(feature = "native-accel")]
1002            {
1003                for value in &stack {
1004                    clear_residency(value);
1005                }
1006                for value in &vars {
1007                    clear_residency(value);
1008                }
1009            }
1010            return Err(err);
1011        }
1012        #[cfg(feature = "native-accel")]
1013        if let (Some(plan), Some(graph)) = (active_group_plan_clone(), fusion_accel_graph.as_ref())
1014        {
1015            if plan.group.span.start == pc {
1016                #[cfg(feature = "native-accel")]
1017                {
1018                    interp_engine::note_fusion_gate(
1019                        &mut interpreter_timing,
1020                        &plan,
1021                        &bytecode,
1022                        pc,
1023                        accel_fusion::fusion_span_has_vm_barrier(
1024                            &bytecode.instructions,
1025                            &plan.group.span,
1026                        ),
1027                        accel_fusion::fusion_span_live_result_count(
1028                            &bytecode.instructions,
1029                            &plan.group.span,
1030                        ),
1031                    );
1032                }
1033                let span = plan.group.span.clone();
1034                let has_barrier =
1035                    accel_fusion::fusion_span_has_vm_barrier(&bytecode.instructions, &span);
1036                let _fusion_span = info_span!(
1037                    "fusion.execute",
1038                    span_start = plan.group.span.start,
1039                    span_end = plan.group.span.end,
1040                    kind = ?plan.group.kind
1041                )
1042                .entered();
1043                if !has_barrier {
1044                    match accel_fusion::try_execute_fusion_group(
1045                        &plan,
1046                        graph,
1047                        &mut stack,
1048                        &mut vars,
1049                        &mut context,
1050                    )
1051                    .await
1052                    {
1053                        Ok(result) => {
1054                            stack.push(result);
1055                            pc = plan.group.span.end + 1;
1056                            continue;
1057                        }
1058                        Err(err) => {
1059                            log::debug!("fusion fallback at pc {}: {}", pc, err);
1060                        }
1061                    }
1062                } else {
1063                    interp_engine::note_fusion_skip(pc, &span);
1064                }
1065            }
1066        }
1067        interp_engine::note_pre_dispatch(
1068            &mut interpreter_timing,
1069            debug_stack,
1070            pc,
1071            &bytecode.instructions[pc],
1072            stack.len(),
1073        );
1074        let call_counts_snapshot = CALL_COUNTS.with(|cc| cc.borrow().clone());
1075        let store_var_global_aliases = match &bytecode.instructions[pc] {
1076            Instr::StoreVar(_) => Some(global_aliases.clone()),
1077            _ => None,
1078        };
1079        let store_local_global_aliases = match &bytecode.instructions[pc] {
1080            Instr::StoreLocal(_) => Some(global_aliases.clone()),
1081            _ => None,
1082        };
1083        let mut clear_value_residency = |value: &Value| {
1084            #[cfg(feature = "native-accel")]
1085            clear_residency(value);
1086        };
1087        let mut store_var_before_overwrite = |_current: &Value, _incoming: &Value| {};
1088        let mut store_var_after_store = |stored_index: usize, stored_value: &Value| {
1089            if let Some(ref aliases) = store_var_global_aliases {
1090                runtime_globals::update_global_store(stored_index, stored_value, aliases);
1091            }
1092        };
1093        let mut store_local_before_local_overwrite = |_current: &Value, _incoming: &Value| {};
1094        let mut store_local_before_var_overwrite = |_current: &Value, _incoming: &Value| {};
1095        let mut store_local_after_store = |stored_offset: usize, stored_value: &Value| {
1096            if let Some(ref aliases) = store_local_global_aliases {
1097                runtime_globals::update_global_store(stored_offset, stored_value, aliases);
1098            }
1099        };
1100        let mut store_local_after_fallback_store =
1101            |func_name: &str, stored_offset: usize, stored_value: &Value| {
1102                if let Some(ref aliases) = store_local_global_aliases {
1103                    runtime_globals::update_global_store(stored_offset, stored_value, aliases);
1104                }
1105                runtime_globals::update_persistent_local_store(
1106                    func_name,
1107                    stored_offset,
1108                    stored_value,
1109                );
1110            };
1111        let dispatch_result = interp_dispatch::dispatch_instruction(
1112            interp_dispatch::DispatchMeta {
1113                instr: &bytecode.instructions[pc],
1114                var_names: &bytecode.var_names,
1115                function_registry: &function_registry,
1116                source_id: bytecode.source_id,
1117                call_arg_spans: bytecode.call_arg_spans.get(pc).cloned().flatten(),
1118                call_counts: &call_counts_snapshot,
1119                current_function_name: &current_function_name,
1120            },
1121            interp_dispatch::DispatchState {
1122                stack: &mut stack,
1123                vars: &mut vars,
1124                context: &mut context,
1125                try_stack: &mut try_stack,
1126                last_exception: &mut last_exception,
1127                imports: &mut imports,
1128                global_aliases: &mut global_aliases,
1129                persistent_aliases: &mut persistent_aliases,
1130                missing_input_slots: &mut missing_input_slots,
1131                pc: &mut pc,
1132            },
1133            interp_dispatch::DispatchHooks {
1134                clear_value_residency: &mut clear_value_residency,
1135                store_var_before_overwrite: &mut store_var_before_overwrite,
1136                store_var_after_store: &mut store_var_after_store,
1137                store_local_before_local_overwrite: &mut store_local_before_local_overwrite,
1138                store_local_before_var_overwrite: &mut store_local_before_var_overwrite,
1139                store_local_after_store: &mut store_local_after_store,
1140                store_local_after_fallback_store: &mut store_local_after_fallback_store,
1141            },
1142        )
1143        .await;
1144        let dispatch_result = match dispatch_result {
1145            Ok(result) => result,
1146            Err(err) => match interp_dispatch::redirect_exception_to_catch(
1147                err,
1148                &mut try_stack,
1149                &mut vars,
1150                &mut last_exception,
1151                &mut pc,
1152                refresh_workspace_state,
1153            ) {
1154                interp_dispatch::ExceptionHandling::Caught => {
1155                    continue;
1156                }
1157                interp_dispatch::ExceptionHandling::Uncaught(err) => return Err(*err),
1158            },
1159        };
1160        if let Some(decision) = dispatch_result {
1161            match decision {
1162                interp_dispatch::DispatchHandled::Generic(DispatchDecision::ContinueLoop) => {
1163                    continue;
1164                }
1165                interp_dispatch::DispatchHandled::Generic(DispatchDecision::FallThrough) => {
1166                    pc += 1;
1167                    continue;
1168                }
1169                interp_dispatch::DispatchHandled::Generic(DispatchDecision::Return) => {
1170                    interpreter_timing.flush_host_span("return", None);
1171                    break;
1172                }
1173                interp_dispatch::DispatchHandled::ReturnValue(DispatchDecision::ContinueLoop)
1174                | interp_dispatch::DispatchHandled::Return(DispatchDecision::ContinueLoop) => {
1175                    continue;
1176                }
1177                interp_dispatch::DispatchHandled::ReturnValue(DispatchDecision::Return) => {
1178                    interpreter_timing.flush_host_span("return_value", None);
1179                    break;
1180                }
1181                interp_dispatch::DispatchHandled::Return(DispatchDecision::Return) => {
1182                    interpreter_timing.flush_host_span("return", None);
1183                    break;
1184                }
1185                interp_dispatch::DispatchHandled::ReturnValue(DispatchDecision::FallThrough)
1186                | interp_dispatch::DispatchHandled::Return(DispatchDecision::FallThrough) => {
1187                    pc += 1;
1188                    continue;
1189                }
1190            }
1191        }
1192        match bytecode.instructions[pc].clone() {
1193            Instr::EmitStackTop { .. }
1194            | Instr::EmitVar { .. }
1195            | Instr::AndAnd(_)
1196            | Instr::OrOr(_)
1197            | Instr::JumpIfFalse(_)
1198            | Instr::Jump(_)
1199            | Instr::LoadConst(_)
1200            | Instr::LoadComplex(_, _)
1201            | Instr::LoadBool(_)
1202            | Instr::LoadString(_)
1203            | Instr::LoadCharRow(_)
1204            | Instr::LoadLocal(_)
1205            | Instr::LoadVar(_)
1206            | Instr::LoadVarForIndexAssignment(_)
1207            | Instr::StoreVar(_)
1208            | Instr::StoreLocal(_)
1209            | Instr::Swap
1210            | Instr::Pop
1211            | Instr::EnterTry(_, _)
1212            | Instr::PopTry
1213            | Instr::ReturnValue
1214            | Instr::Return
1215            | Instr::EnterScope(_)
1216            | Instr::LoadMember(_)
1217            | Instr::LoadMemberOrInit(_)
1218            | Instr::LoadMemberDynamic
1219            | Instr::LoadMemberDynamicOrInit
1220            | Instr::StoreMember(_)
1221            | Instr::StoreMemberOrInit(_)
1222            | Instr::StoreMemberDynamic
1223            | Instr::StoreMemberDynamicOrInit
1224            | Instr::Index(_)
1225            | Instr::IndexSlice(_, _, _, _)
1226            | Instr::IndexSliceExpr { .. }
1227            | Instr::IndexCell { .. }
1228            | Instr::IndexCellExpand { .. }
1229            | Instr::IndexCellList { .. }
1230            | Instr::StoreIndex(_)
1231            | Instr::StoreIndexCell { .. }
1232            | Instr::StoreIndexDelete(_)
1233            | Instr::StoreIndexCellDelete { .. }
1234            | Instr::StoreSlice(_, _, _, _)
1235            | Instr::StoreSliceDelete(_, _, _, _)
1236            | Instr::StoreSliceExpr { .. }
1237            | Instr::StoreSliceExprDelete { .. }
1238            | Instr::CallMethodOrMemberIndexMulti { .. }
1239            | Instr::CallMethodOrMemberIndexExpandMultiOutput { .. }
1240            | Instr::LoadMethod(_)
1241            | Instr::CreateFunctionHandle(_)
1242            | Instr::CreateExternalFunctionHandle(_)
1243            | Instr::CreateMethodFunctionHandle(_)
1244            | Instr::CreateBoundFunctionHandle(_, _)
1245            | Instr::CreateExternalBoundFunctionHandle(_, _)
1246            | Instr::CreateClosure(_, _)
1247            | Instr::CreateSemanticClosure(_, _, _)
1248            | Instr::LoadStaticProperty(_, _)
1249            | Instr::LoadWorkspaceFirstStaticProperty { .. }
1250            | Instr::RegisterClass { .. }
1251            | Instr::CallFevalMulti(_, _)
1252            | Instr::CallFevalMultiUsingOutputSlot(_, _)
1253            | Instr::CallFevalExpandMultiOutput(_, _)
1254            | Instr::CallFevalExpandMultiOutputUsingOutputSlot(_, _)
1255            | Instr::CreateSemanticFuture(_, _, _)
1256            | Instr::CreateSemanticFutureExpandMultiOutput(_, _, _)
1257            | Instr::Spawn
1258            | Instr::Await
1259            | Instr::CallBuiltinMulti(_, _, _)
1260            | Instr::CallBuiltinMultiUsingOutputSlot(_, _, _)
1261            | Instr::CallSuperConstructorMulti { .. }
1262            | Instr::CallSuperMethodMulti { .. }
1263            | Instr::CallSemanticFunctionMulti(_, _, _)
1264            | Instr::CallSemanticFunctionMultiUsingOutputSlot(_, _, _)
1265            | Instr::CallSemanticNestedFunctionMulti { .. }
1266            | Instr::CallSemanticNestedFunctionMultiUsingOutputSlot { .. }
1267            | Instr::CallFunctionMulti { .. }
1268            | Instr::CallFunctionMultiUsingOutputSlot { .. }
1269            | Instr::CallFunctionExpandMultiOutput { .. }
1270            | Instr::CallWorkspaceFirstMulti { .. }
1271            | Instr::CallWorkspaceFirstMultiUsingOutputSlot { .. }
1272            | Instr::CallWorkspaceFirstExpandMultiOutput { .. }
1273            | Instr::CallWorkspaceFirstExpandMultiOutputUsingOutputSlot { .. }
1274            | Instr::CallSemanticFunctionExpandMultiOutput(_, _, _)
1275            | Instr::CallSemanticNestedFunctionExpandMultiOutput { .. }
1276            | Instr::CallBuiltinExpandMultiOutput(_, _, _)
1277            | Instr::CallSuperConstructorExpandMultiOutput { .. }
1278            | Instr::CallSuperMethodExpandMultiOutput { .. }
1279            | Instr::ExitScope(_)
1280            | Instr::RegisterImport { .. }
1281            | Instr::DeclareGlobal(_)
1282            | Instr::DeclareGlobalNamed(_, _)
1283            | Instr::DeclarePersistent(_)
1284            | Instr::DeclarePersistentNamed(_, _)
1285            | Instr::CreateCell2D(_, _)
1286            | Instr::CreateStructLiteral(_)
1287            | Instr::CreateObjectLiteral { .. }
1288            | Instr::Add
1289            | Instr::Sub
1290            | Instr::Mul
1291            | Instr::ElemMul
1292            | Instr::ElemDiv
1293            | Instr::ElemPow
1294            | Instr::ElemLeftDiv
1295            | Instr::Neg
1296            | Instr::UPlus
1297            | Instr::Transpose
1298            | Instr::ConjugateTranspose
1299            | Instr::Pow
1300            | Instr::RightDiv
1301            | Instr::LeftDiv
1302            | Instr::LessEqual
1303            | Instr::Less
1304            | Instr::Greater
1305            | Instr::GreaterEqual
1306            | Instr::Equal
1307            | Instr::NotEqual
1308            | Instr::LogicalNot
1309            | Instr::LogicalAnd
1310            | Instr::LogicalOr
1311            | Instr::Unpack(_)
1312            | Instr::CreateMatrix(_, _)
1313            | Instr::CreateMatrixDynamic(_)
1314            | Instr::CreateRange(_)
1315            | Instr::PackToRow(_)
1316            | Instr::PackToCol(_) => unreachable!("handled by dispatch_instruction"),
1317            Instr::StochasticEvolution => {
1318                let steps_value = stack
1319                    .pop()
1320                    .ok_or(mex("StackUnderflow", "stack underflow"))?;
1321                let scale_value = stack
1322                    .pop()
1323                    .ok_or(mex("StackUnderflow", "stack underflow"))?;
1324                let drift_value = stack
1325                    .pop()
1326                    .ok_or(mex("StackUnderflow", "stack underflow"))?;
1327                let state_value = stack
1328                    .pop()
1329                    .ok_or(mex("StackUnderflow", "stack underflow"))?;
1330                let evolved =
1331                    crate::accel::idioms::stochastic_evolution::execute_stochastic_evolution(
1332                        state_value,
1333                        drift_value,
1334                        scale_value,
1335                        steps_value,
1336                    )
1337                    .await?;
1338                stack.push(evolved);
1339            }
1340        }
1341        if debug_stack {
1342            debug!(pc, stack_len = stack.len(), "[vm] after exec");
1343        }
1344        pc += 1;
1345    }
1346    interpreter_timing.flush_host_span("loop_complete", None);
1347    #[cfg(feature = "native-accel")]
1348    {
1349        let mut live_values = Vec::with_capacity(vars.len() + context.locals.len());
1350        live_values.extend(vars.iter().cloned());
1351        live_values.extend(context.locals.iter().cloned());
1352        let live_values = Value::OutputList(live_values);
1353        for value in &stack {
1354            accel_residency::clear_value_excluding(value, &live_values);
1355        }
1356    }
1357    sync_initial_vars(initial_vars, &vars);
1358    Ok(InterpreterOutcome::Completed(vars))
1359}
1360
1361pub async fn interpret(bytecode: &Bytecode) -> Result<Vec<Value>, RuntimeError> {
1362    let mut vars = vec![Value::Num(0.0); bytecode.var_count];
1363    match interpret_with_vars(bytecode, &mut vars, Some("<main>")).await {
1364        Ok(InterpreterOutcome::Completed(values)) => Ok(values),
1365        Err(e) => Err(e),
1366    }
1367}
1368
1369pub async fn interpret_function(
1370    bytecode: &Bytecode,
1371    vars: Vec<Value>,
1372) -> Result<Vec<Value>, RuntimeError> {
1373    interpret_function_with_counts(bytecode, vars, "<anonymous>", 0, 0, HashSet::new()).await
1374}
1375
1376pub async fn interpret_function_with_counts(
1377    bytecode: &Bytecode,
1378    vars: Vec<Value>,
1379    name: &str,
1380    out_count: usize,
1381    in_count: usize,
1382    missing_input_slots: HashSet<usize>,
1383) -> Result<Vec<Value>, RuntimeError> {
1384    let mut vars = vars;
1385    CALL_COUNTS.with(|cc| {
1386        cc.borrow_mut().push((in_count, out_count));
1387    });
1388    let call_counts = CALL_COUNTS.with(|cc| cc.borrow().clone());
1389    let mut state = InterpreterState::new(bytecode.clone(), &mut vars, Some(name), call_counts);
1390    state.missing_input_slots = missing_input_slots;
1391    let res = Box::pin(run_interpreter(Box::new(state), &mut vars)).await;
1392    CALL_COUNTS.with(|cc| {
1393        cc.borrow_mut().pop();
1394    });
1395    let res = match res {
1396        Ok(InterpreterOutcome::Completed(values)) => Ok(values),
1397        Err(e) => Err(e),
1398    }?;
1399    runtime_globals::persist_declared_for_bytecode(bytecode, name, &vars);
1400    Ok(res)
1401}
1402
1403#[cfg(test)]
1404mod tests {
1405    use super::{
1406        collect_semantic_outputs, interpret_with_vars, output_value, run_interpreter_inner,
1407        value_is_empty, value_is_greater_than, value_is_greater_than_or_equal, value_is_integer,
1408        value_is_less_than, value_is_less_than_or_equal, value_is_negative, value_is_nonnegative,
1409        value_is_nonpositive, value_is_nonzero, value_is_numeric_or_logical, value_is_positive,
1410        value_is_real, value_is_scalar_or_empty, value_is_text,
1411    };
1412    use crate::bytecode::program::{Bytecode, FunctionBytecode};
1413    use crate::bytecode::Instr;
1414    use crate::interpreter::api::InterpreterState;
1415    use futures::executor::block_on;
1416    use runmat_builtins::{
1417        CellArray, Closure, HandleRef, ObjectInstance, StructValue, Tensor, Value,
1418    };
1419    use runmat_hir::FunctionId;
1420    use std::collections::{HashMap, HashSet};
1421    use std::sync::{atomic::AtomicBool, Arc};
1422    #[cfg(feature = "native-accel")]
1423    use {
1424        once_cell::sync::Lazy,
1425        runmat_accelerate::simple_provider::InProcessProvider,
1426        runmat_accelerate_api::{AccelProvider, HostTensorView, ThreadProviderGuard},
1427    };
1428
1429    #[cfg(feature = "native-accel")]
1430    static TEST_PROVIDER: Lazy<InProcessProvider> = Lazy::new(InProcessProvider::new);
1431
1432    #[cfg(feature = "native-accel")]
1433    fn upload_provider_handle(
1434        data: Vec<f64>,
1435        shape: Vec<usize>,
1436    ) -> runmat_accelerate_api::GpuTensorHandle {
1437        TEST_PROVIDER
1438            .upload(&HostTensorView {
1439                data: &data,
1440                shape: &shape,
1441            })
1442            .expect("upload should succeed")
1443    }
1444
1445    fn test_function(varargout_slot: Option<usize>) -> FunctionBytecode {
1446        FunctionBytecode {
1447            function: FunctionId(0),
1448            display_name: "f".into(),
1449            private_owner_scope: String::new(),
1450            source_id: None,
1451            instructions: vec![Instr::Return],
1452            instr_spans: Vec::new(),
1453            call_arg_spans: Vec::new(),
1454            var_count: 1,
1455            input_slots: Vec::new(),
1456            varargin_slot: None,
1457            implicit_nargin_slot: None,
1458            output_slots: Vec::new(),
1459            varargout_slot,
1460            implicit_nargout_slot: None,
1461            capture_slots: Vec::new(),
1462            var_names: HashMap::new(),
1463            initially_unassigned_slots: HashSet::new(),
1464            argument_validations: Vec::new(),
1465        }
1466    }
1467
1468    #[test]
1469    fn collect_outputs_zero_requested_does_not_consume_varargout() {
1470        let func = test_function(Some(0));
1471        let varargout = CellArray::new(vec![Value::Num(7.0)], 1, 1).expect("cell");
1472        let result_vars = vec![Value::Cell(varargout)];
1473        let outputs = collect_semantic_outputs(&func, &result_vars, 0).expect("collect");
1474        assert!(outputs.is_empty());
1475    }
1476
1477    #[test]
1478    fn collect_outputs_one_requested_reads_varargout() {
1479        let func = test_function(Some(0));
1480        let varargout = CellArray::new(vec![Value::Num(7.0)], 1, 1).expect("cell");
1481        let result_vars = vec![Value::Cell(varargout)];
1482        let outputs = collect_semantic_outputs(&func, &result_vars, 1).expect("collect");
1483        assert_eq!(outputs, vec![Value::Num(7.0)]);
1484    }
1485
1486    #[test]
1487    fn output_value_zero_requested_is_empty_output_list() {
1488        let value = output_value(vec![Value::Num(1.0)], 0);
1489        assert_eq!(value, Value::OutputList(Vec::new()));
1490    }
1491
1492    #[test]
1493    fn output_value_multi_requested_returns_output_list() {
1494        let value = output_value(vec![Value::Num(1.0), Value::Num(2.0)], 2);
1495        assert_eq!(
1496            value,
1497            Value::OutputList(vec![Value::Num(1.0), Value::Num(2.0)])
1498        );
1499    }
1500
1501    #[test]
1502    fn numeric_or_logical_validator_accepts_expected_domains() {
1503        assert!(value_is_numeric_or_logical(&Value::Num(1.0)));
1504        assert!(value_is_numeric_or_logical(&Value::Bool(true)));
1505        assert!(value_is_numeric_or_logical(&Value::Complex(1.0, 2.0)));
1506        let tensor = Tensor::new(vec![1.0, 2.0], vec![1, 2]).expect("tensor");
1507        assert!(value_is_numeric_or_logical(&Value::Tensor(tensor)));
1508        assert!(!value_is_numeric_or_logical(&Value::String(
1509            "x".to_string()
1510        )));
1511        assert!(!value_is_numeric_or_logical(&Value::CharArray(
1512            runmat_builtins::CharArray::new("x".chars().collect(), 1, 1).expect("char")
1513        )));
1514    }
1515
1516    #[test]
1517    fn text_validator_accepts_string_char_vector_and_cellstr() {
1518        assert!(value_is_text(&Value::String("x".to_string())));
1519        assert!(value_is_text(&Value::CharArray(
1520            runmat_builtins::CharArray::new("abc".chars().collect(), 1, 3).expect("char")
1521        )));
1522        assert!(value_is_text(&Value::Cell(
1523            CellArray::new(
1524                vec![
1525                    Value::CharArray(
1526                        runmat_builtins::CharArray::new("a".chars().collect(), 1, 1).expect("char"),
1527                    ),
1528                    Value::String("b".to_string()),
1529                ],
1530                1,
1531                2,
1532            )
1533            .expect("cell"),
1534        )));
1535        assert!(!value_is_text(&Value::Num(1.0)));
1536    }
1537
1538    #[test]
1539    fn nonempty_validator_rejects_empty_arrays_and_cells() {
1540        let empty_num = Tensor::new(Vec::new(), vec![0, 0]).expect("empty tensor");
1541        assert!(value_is_empty(&Value::Tensor(empty_num)));
1542        let empty_char =
1543            runmat_builtins::CharArray::new(Vec::new(), 1, 0).expect("empty char array");
1544        assert!(value_is_empty(&Value::CharArray(empty_char)));
1545        let empty_cell = CellArray::new(Vec::new(), 0, 0).expect("empty cell");
1546        assert!(value_is_empty(&Value::Cell(empty_cell)));
1547        assert!(!value_is_empty(&Value::String("".to_string())));
1548        assert!(!value_is_empty(&Value::Num(1.0)));
1549    }
1550
1551    #[test]
1552    fn scalar_or_empty_validator_accepts_scalar_or_empty_shapes() {
1553        assert!(value_is_scalar_or_empty(&Value::Num(1.0)));
1554        assert!(value_is_scalar_or_empty(&Value::Bool(true)));
1555        let empty_num = Tensor::new(Vec::new(), vec![0, 0]).expect("empty tensor");
1556        assert!(value_is_scalar_or_empty(&Value::Tensor(empty_num)));
1557        let matrix = Tensor::new(vec![1.0, 2.0], vec![1, 2]).expect("matrix");
1558        assert!(!value_is_scalar_or_empty(&Value::Tensor(matrix)));
1559    }
1560
1561    #[test]
1562    fn real_validator_rejects_imaginary_values() {
1563        assert!(value_is_real(&Value::Num(1.0)));
1564        assert!(value_is_real(&Value::Complex(1.0, 0.0)));
1565        assert!(!value_is_real(&Value::Complex(1.0, 2.0)));
1566        let complex_real = runmat_builtins::ComplexTensor::new(vec![(1.0, 0.0)], vec![1, 1])
1567            .expect("complex tensor");
1568        let complex_imag = runmat_builtins::ComplexTensor::new(vec![(1.0, 2.0)], vec![1, 1])
1569            .expect("complex tensor");
1570        assert!(value_is_real(&Value::ComplexTensor(complex_real)));
1571        assert!(!value_is_real(&Value::ComplexTensor(complex_imag)));
1572    }
1573
1574    #[test]
1575    fn integer_validator_accepts_integer_valued_numeric_inputs() {
1576        assert!(value_is_integer(&Value::Int(
1577            runmat_builtins::IntValue::I64(3)
1578        )));
1579        assert!(value_is_integer(&Value::Num(3.0)));
1580        assert!(!value_is_integer(&Value::Num(3.5)));
1581        let tensor = Tensor::new(vec![1.0, 2.0], vec![1, 2]).expect("tensor");
1582        assert!(value_is_integer(&Value::Tensor(tensor)));
1583        let non_integer = Tensor::new(vec![1.0, 2.5], vec![1, 2]).expect("tensor");
1584        assert!(!value_is_integer(&Value::Tensor(non_integer)));
1585        assert!(!value_is_integer(&Value::Bool(true)));
1586    }
1587
1588    #[test]
1589    fn positive_validator_rejects_zero_and_negative_values() {
1590        assert!(value_is_positive(&Value::Num(1.0)));
1591        assert!(!value_is_positive(&Value::Num(0.0)));
1592        assert!(!value_is_positive(&Value::Num(-1.0)));
1593        assert!(value_is_positive(&Value::Int(
1594            runmat_builtins::IntValue::I64(2)
1595        )));
1596        assert!(!value_is_positive(&Value::Int(
1597            runmat_builtins::IntValue::I64(0)
1598        )));
1599        let positive = Tensor::new(vec![1.0, 2.0], vec![1, 2]).expect("tensor");
1600        assert!(value_is_positive(&Value::Tensor(positive)));
1601        let mixed = Tensor::new(vec![1.0, 0.0], vec![1, 2]).expect("tensor");
1602        assert!(!value_is_positive(&Value::Tensor(mixed)));
1603    }
1604
1605    #[test]
1606    fn negative_validator_rejects_zero_and_positive_values() {
1607        assert!(value_is_negative(&Value::Num(-1.0)));
1608        assert!(!value_is_negative(&Value::Num(0.0)));
1609        assert!(!value_is_negative(&Value::Num(1.0)));
1610        assert!(value_is_negative(&Value::Int(
1611            runmat_builtins::IntValue::I64(-2)
1612        )));
1613        let ok = Tensor::new(vec![-1.0, -2.0], vec![1, 2]).expect("tensor");
1614        assert!(value_is_negative(&Value::Tensor(ok)));
1615        let bad = Tensor::new(vec![-1.0, 0.0], vec![1, 2]).expect("tensor");
1616        assert!(!value_is_negative(&Value::Tensor(bad)));
1617    }
1618
1619    #[test]
1620    fn nonnegative_validator_accepts_zero_and_positive_values() {
1621        assert!(value_is_nonnegative(&Value::Num(0.0)));
1622        assert!(value_is_nonnegative(&Value::Num(2.0)));
1623        assert!(!value_is_nonnegative(&Value::Num(-1.0)));
1624        assert!(value_is_nonnegative(&Value::Int(
1625            runmat_builtins::IntValue::I64(0)
1626        )));
1627        let ok = Tensor::new(vec![0.0, 1.0], vec![1, 2]).expect("tensor");
1628        assert!(value_is_nonnegative(&Value::Tensor(ok)));
1629        let bad = Tensor::new(vec![0.0, -1.0], vec![1, 2]).expect("tensor");
1630        assert!(!value_is_nonnegative(&Value::Tensor(bad)));
1631    }
1632
1633    #[test]
1634    fn nonzero_validator_rejects_zero_values() {
1635        assert!(value_is_nonzero(&Value::Num(1.0)));
1636        assert!(!value_is_nonzero(&Value::Num(0.0)));
1637        assert!(value_is_nonzero(&Value::Int(
1638            runmat_builtins::IntValue::I64(2)
1639        )));
1640        assert!(!value_is_nonzero(&Value::Int(
1641            runmat_builtins::IntValue::I64(0)
1642        )));
1643        assert!(value_is_nonzero(&Value::Complex(0.0, 1.0)));
1644        assert!(!value_is_nonzero(&Value::Complex(0.0, 0.0)));
1645        let ok = Tensor::new(vec![1.0, 2.0], vec![1, 2]).expect("tensor");
1646        assert!(value_is_nonzero(&Value::Tensor(ok)));
1647        let bad = Tensor::new(vec![1.0, 0.0], vec![1, 2]).expect("tensor");
1648        assert!(!value_is_nonzero(&Value::Tensor(bad)));
1649    }
1650
1651    #[test]
1652    fn nonpositive_validator_accepts_zero_and_negative_values() {
1653        assert!(value_is_nonpositive(&Value::Num(0.0)));
1654        assert!(value_is_nonpositive(&Value::Num(-2.0)));
1655        assert!(!value_is_nonpositive(&Value::Num(1.0)));
1656        assert!(value_is_nonpositive(&Value::Int(
1657            runmat_builtins::IntValue::I64(0)
1658        )));
1659        let ok = Tensor::new(vec![0.0, -1.0], vec![1, 2]).expect("tensor");
1660        assert!(value_is_nonpositive(&Value::Tensor(ok)));
1661        let bad = Tensor::new(vec![0.0, 1.0], vec![1, 2]).expect("tensor");
1662        assert!(!value_is_nonpositive(&Value::Tensor(bad)));
1663    }
1664
1665    #[test]
1666    fn greater_than_or_equal_validator_uses_numeric_threshold() {
1667        assert!(value_is_greater_than_or_equal(&Value::Num(2.0), 0.0));
1668        assert!(value_is_greater_than_or_equal(&Value::Num(0.0), 0.0));
1669        assert!(!value_is_greater_than_or_equal(&Value::Num(-1.0), 0.0));
1670    }
1671
1672    #[test]
1673    fn less_than_or_equal_validator_uses_numeric_threshold() {
1674        assert!(value_is_less_than_or_equal(&Value::Num(-1.0), 0.0));
1675        assert!(value_is_less_than_or_equal(&Value::Num(0.0), 0.0));
1676        assert!(!value_is_less_than_or_equal(&Value::Num(1.0), 0.0));
1677    }
1678
1679    #[test]
1680    fn greater_than_and_less_than_validators_use_numeric_threshold() {
1681        assert!(value_is_greater_than(&Value::Num(2.0), 1.0));
1682        assert!(!value_is_greater_than(&Value::Num(1.0), 1.0));
1683        assert!(value_is_less_than(&Value::Num(-2.0), -1.0));
1684        assert!(!value_is_less_than(&Value::Num(-1.0), -1.0));
1685    }
1686
1687    #[cfg(feature = "native-accel")]
1688    #[test]
1689    fn cancellation_clears_gpu_residency_for_live_values() {
1690        use runmat_accelerate::fusion_residency;
1691        use runmat_accelerate_api::GpuTensorHandle;
1692
1693        let handle = GpuTensorHandle {
1694            shape: vec![1, 1],
1695            device_id: 0,
1696            buffer_id: 777_001,
1697        };
1698        fusion_residency::mark(&handle);
1699        assert!(fusion_residency::is_resident(&handle));
1700
1701        let mut vars = vec![Value::GpuTensor(handle.clone())];
1702        let bytecode = Bytecode::with_instructions(vec![Instr::Return], vars.len());
1703        let cancelled = Arc::new(AtomicBool::new(true));
1704        let _interrupt_guard = runmat_runtime::interrupt::replace_interrupt(Some(cancelled));
1705
1706        let err = block_on(interpret_with_vars(&bytecode, &mut vars, Some("<main>")))
1707            .expect_err("cancelled execution should return error");
1708        assert_eq!(err.identifier(), Some("RunMat:ExecutionCancelled"));
1709        assert!(
1710            !fusion_residency::is_resident(&handle),
1711            "cancelled execution should clear residency marks for live GPU handles"
1712        );
1713    }
1714
1715    #[cfg(feature = "native-accel")]
1716    #[test]
1717    fn completion_clears_stack_only_gpu_residency() {
1718        use runmat_accelerate::fusion_residency;
1719        use runmat_accelerate_api::GpuTensorHandle;
1720
1721        let handle = GpuTensorHandle {
1722            shape: vec![1, 1],
1723            device_id: 0,
1724            buffer_id: 777_002,
1725        };
1726        fusion_residency::mark(&handle);
1727        assert!(fusion_residency::is_resident(&handle));
1728
1729        let bytecode = Bytecode::with_instructions(Vec::new(), 1);
1730        let mut seed_vars = vec![Value::Num(0.0)];
1731        let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
1732        state.stack.push(Value::GpuTensor(handle.clone()));
1733        state.vars = vec![Value::Num(0.0)];
1734
1735        let mut result_vars = vec![Value::Num(0.0)];
1736        let outcome = block_on(run_interpreter_inner(state, &mut result_vars))
1737            .expect("interpreter should complete");
1738        assert!(matches!(
1739            outcome,
1740            crate::interpreter::api::InterpreterOutcome::Completed(_)
1741        ));
1742        assert!(
1743            !fusion_residency::is_resident(&handle),
1744            "completion should clear residency marks for stack-only GPU handles"
1745        );
1746    }
1747
1748    #[cfg(feature = "native-accel")]
1749    #[test]
1750    fn pop_releases_stack_only_provider_handle() {
1751        use runmat_accelerate::fusion_residency;
1752
1753        let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
1754        let handle = upload_provider_handle(vec![9.0], vec![1]);
1755        assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
1756        fusion_residency::mark(&handle);
1757
1758        let bytecode = Bytecode::with_instructions(vec![Instr::Pop, Instr::Return], 1);
1759        let mut seed_vars = vec![Value::Num(0.0)];
1760        let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
1761        state.stack.push(Value::GpuTensor(handle.clone()));
1762        state.vars = vec![Value::Num(0.0)];
1763
1764        let mut result_vars = vec![Value::Num(0.0)];
1765        let _ = block_on(run_interpreter_inner(state, &mut result_vars))
1766            .expect("interpreter should complete");
1767        assert!(
1768            !fusion_residency::is_resident(&handle),
1769            "pop should clear residency for stack-only handles"
1770        );
1771        assert!(
1772            block_on(TEST_PROVIDER.download(&handle)).is_err(),
1773            "pop should release provider storage for stack-only handles"
1774        );
1775    }
1776
1777    #[cfg(feature = "native-accel")]
1778    #[test]
1779    fn pop_preserves_provider_handle_when_still_live_in_vars() {
1780        use runmat_accelerate::fusion_residency;
1781
1782        let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
1783        let handle = upload_provider_handle(vec![11.0], vec![1]);
1784        assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
1785        fusion_residency::mark(&handle);
1786
1787        let bytecode = Bytecode::with_instructions(vec![Instr::Pop, Instr::Return], 1);
1788        let mut seed_vars = vec![Value::GpuTensor(handle.clone())];
1789        let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
1790        state.stack.push(Value::GpuTensor(handle.clone()));
1791        state.vars = vec![Value::GpuTensor(handle.clone())];
1792
1793        let mut result_vars = vec![Value::GpuTensor(handle.clone())];
1794        let _ = block_on(run_interpreter_inner(state, &mut result_vars))
1795            .expect("interpreter should complete");
1796        assert!(
1797            fusion_residency::is_resident(&handle),
1798            "pop should preserve residency for handles still referenced by vars"
1799        );
1800        assert!(
1801            block_on(TEST_PROVIDER.download(&handle)).is_ok(),
1802            "pop should not release provider storage for handles still referenced by vars"
1803        );
1804        fusion_residency::clear(&handle);
1805        let _ = TEST_PROVIDER.free(&handle);
1806    }
1807
1808    #[cfg(feature = "native-accel")]
1809    #[test]
1810    fn exit_scope_releases_local_only_provider_handle() {
1811        use runmat_accelerate::fusion_residency;
1812
1813        let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
1814        let handle = upload_provider_handle(vec![15.0], vec![1]);
1815        assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
1816        fusion_residency::mark(&handle);
1817
1818        let bytecode = Bytecode::with_instructions(vec![Instr::ExitScope(1), Instr::Return], 1);
1819        let mut seed_vars = vec![Value::Num(0.0)];
1820        let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
1821        state.context.locals.push(Value::GpuTensor(handle.clone()));
1822        state.vars = vec![Value::Num(0.0)];
1823
1824        let mut result_vars = vec![Value::Num(0.0)];
1825        let _ = block_on(run_interpreter_inner(state, &mut result_vars))
1826            .expect("exit scope should complete");
1827        assert!(
1828            !fusion_residency::is_resident(&handle),
1829            "exit scope should clear residency for local-only handles"
1830        );
1831        assert!(
1832            block_on(TEST_PROVIDER.download(&handle)).is_err(),
1833            "exit scope should release provider storage for local-only handles"
1834        );
1835    }
1836
1837    #[cfg(feature = "native-accel")]
1838    #[test]
1839    fn exit_scope_preserves_provider_handle_when_still_live_in_vars() {
1840        use runmat_accelerate::fusion_residency;
1841
1842        let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
1843        let handle = upload_provider_handle(vec![17.0], vec![1]);
1844        assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
1845        fusion_residency::mark(&handle);
1846
1847        let bytecode = Bytecode::with_instructions(vec![Instr::ExitScope(1), Instr::Return], 1);
1848        let mut seed_vars = vec![Value::GpuTensor(handle.clone())];
1849        let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
1850        state.context.locals.push(Value::GpuTensor(handle.clone()));
1851        state.vars = vec![Value::GpuTensor(handle.clone())];
1852
1853        let mut result_vars = vec![Value::GpuTensor(handle.clone())];
1854        let _ = block_on(run_interpreter_inner(state, &mut result_vars))
1855            .expect("exit scope should complete");
1856        assert!(
1857            fusion_residency::is_resident(&handle),
1858            "exit scope should preserve residency for handles still referenced by vars"
1859        );
1860        assert!(
1861            block_on(TEST_PROVIDER.download(&handle)).is_ok(),
1862            "exit scope should not release provider storage for handles still referenced by vars"
1863        );
1864        fusion_residency::clear(&handle);
1865        let _ = TEST_PROVIDER.free(&handle);
1866    }
1867
1868    #[cfg(feature = "native-accel")]
1869    #[test]
1870    fn exit_scope_releases_nested_handle_object_local_provider_handle() {
1871        use runmat_accelerate::fusion_residency;
1872
1873        let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
1874        let handle = upload_provider_handle(vec![18.0], vec![1]);
1875        assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
1876        fusion_residency::mark(&handle);
1877
1878        let bytecode = Bytecode::with_instructions(vec![Instr::ExitScope(1), Instr::Return], 1);
1879        let mut seed_vars = vec![Value::Num(0.0)];
1880        let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
1881        let mut payload = StructValue::new();
1882        payload
1883            .fields
1884            .insert("nested".to_string(), Value::GpuTensor(handle.clone()));
1885        let target = runmat_gc::gc_allocate(Value::Struct(payload)).expect("gc allocate payload");
1886        state.context.locals.push(Value::HandleObject(HandleRef {
1887            class_name: "Payload".to_string(),
1888            target,
1889            valid: true,
1890        }));
1891        state.vars = vec![Value::Num(0.0)];
1892
1893        let mut result_vars = vec![Value::Num(0.0)];
1894        let _ = block_on(run_interpreter_inner(state, &mut result_vars))
1895            .expect("exit scope should complete for nested handle-object local");
1896        assert!(
1897            !fusion_residency::is_resident(&handle),
1898            "exit scope should clear residency for nested handle-object local-only handles"
1899        );
1900        assert!(
1901            block_on(TEST_PROVIDER.download(&handle)).is_err(),
1902            "exit scope should release provider storage for nested handle-object local-only handles"
1903        );
1904    }
1905
1906    #[cfg(feature = "native-accel")]
1907    #[test]
1908    fn exit_scope_preserves_nested_handle_object_provider_handle_when_still_live_in_vars() {
1909        use runmat_accelerate::fusion_residency;
1910
1911        let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
1912        let handle = upload_provider_handle(vec![20.0], vec![1]);
1913        assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
1914        fusion_residency::mark(&handle);
1915
1916        let bytecode = Bytecode::with_instructions(vec![Instr::ExitScope(1), Instr::Return], 1);
1917        let mut seed_vars = vec![Value::Num(0.0)];
1918        let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
1919        let mut payload = StructValue::new();
1920        payload
1921            .fields
1922            .insert("nested".to_string(), Value::GpuTensor(handle.clone()));
1923        let target = runmat_gc::gc_allocate(Value::Struct(payload)).expect("gc allocate payload");
1924        let local_value = Value::HandleObject(HandleRef {
1925            class_name: "Payload".to_string(),
1926            target,
1927            valid: true,
1928        });
1929        state.context.locals.push(local_value.clone());
1930        state.vars = vec![local_value.clone()];
1931
1932        let mut result_vars = vec![local_value];
1933        let _ = block_on(run_interpreter_inner(state, &mut result_vars))
1934            .expect("exit scope should complete for aliased nested handle-object local");
1935        assert!(
1936            fusion_residency::is_resident(&handle),
1937            "exit scope should preserve residency for nested handle-object handles still referenced by vars"
1938        );
1939        assert!(
1940            block_on(TEST_PROVIDER.download(&handle)).is_ok(),
1941            "exit scope should not release provider storage for nested handle-object handles still referenced by vars"
1942        );
1943        fusion_residency::clear(&handle);
1944        let _ = TEST_PROVIDER.free(&handle);
1945    }
1946
1947    #[cfg(feature = "native-accel")]
1948    #[test]
1949    fn store_var_overwrite_preserves_provider_handle_when_shared_in_other_var() {
1950        use runmat_accelerate::fusion_residency;
1951
1952        let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
1953        let handle = upload_provider_handle(vec![19.0], vec![1]);
1954        assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
1955        fusion_residency::mark(&handle);
1956
1957        let bytecode = Bytecode::with_instructions(vec![Instr::StoreVar(0), Instr::Return], 2);
1958        let mut seed_vars = vec![
1959            Value::GpuTensor(handle.clone()),
1960            Value::GpuTensor(handle.clone()),
1961        ];
1962        let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
1963        state.stack.push(Value::Num(0.0));
1964        state.vars = vec![
1965            Value::GpuTensor(handle.clone()),
1966            Value::GpuTensor(handle.clone()),
1967        ];
1968
1969        let mut result_vars = state.vars.clone();
1970        let _ = block_on(run_interpreter_inner(state, &mut result_vars))
1971            .expect("store var should complete");
1972        assert!(
1973            fusion_residency::is_resident(&handle),
1974            "store var overwrite should preserve residency for handles still live in other vars"
1975        );
1976        assert!(
1977            block_on(TEST_PROVIDER.download(&handle)).is_ok(),
1978            "store var overwrite should not release provider storage for handles still live in other vars"
1979        );
1980        fusion_residency::clear(&handle);
1981        let _ = TEST_PROVIDER.free(&handle);
1982    }
1983
1984    #[cfg(feature = "native-accel")]
1985    #[test]
1986    fn store_var_overwrite_preserves_provider_handle_when_shared_in_local() {
1987        use runmat_accelerate::fusion_residency;
1988
1989        let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
1990        let handle = upload_provider_handle(vec![20.0], vec![1]);
1991        assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
1992        fusion_residency::mark(&handle);
1993
1994        let bytecode = Bytecode::with_instructions(vec![Instr::StoreVar(0), Instr::Return], 1);
1995        let mut seed_vars = vec![Value::GpuTensor(handle.clone())];
1996        let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
1997        state.stack.push(Value::Num(0.0));
1998        state.vars = vec![Value::GpuTensor(handle.clone())];
1999        state.context.locals.push(Value::GpuTensor(handle.clone()));
2000
2001        let mut result_vars = state.vars.clone();
2002        let _ = block_on(run_interpreter_inner(state, &mut result_vars))
2003            .expect("store var should complete when alias lives in locals");
2004        assert!(
2005            fusion_residency::is_resident(&handle),
2006            "store var overwrite should preserve residency for handles still live in locals"
2007        );
2008        assert!(
2009            block_on(TEST_PROVIDER.download(&handle)).is_ok(),
2010            "store var overwrite should not release provider storage for handles still live in locals"
2011        );
2012        fusion_residency::clear(&handle);
2013        let _ = TEST_PROVIDER.free(&handle);
2014    }
2015
2016    #[cfg(feature = "native-accel")]
2017    #[test]
2018    fn store_var_overwrite_releases_nested_handle_object_provider_handle_when_unaliased() {
2019        use runmat_accelerate::fusion_residency;
2020
2021        let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
2022        let handle = upload_provider_handle(vec![22.0], vec![1]);
2023        assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
2024        fusion_residency::mark(&handle);
2025
2026        let bytecode = Bytecode::with_instructions(vec![Instr::StoreVar(0), Instr::Return], 1);
2027        let mut seed_vars = vec![Value::Num(0.0)];
2028        let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
2029        let mut payload = StructValue::new();
2030        payload
2031            .fields
2032            .insert("nested".to_string(), Value::GpuTensor(handle.clone()));
2033        let target = runmat_gc::gc_allocate(Value::Struct(payload)).expect("gc allocate payload");
2034        state.vars = vec![Value::HandleObject(HandleRef {
2035            class_name: "Payload".to_string(),
2036            target,
2037            valid: true,
2038        })];
2039        state.stack.push(Value::Num(0.0));
2040
2041        let mut result_vars = state.vars.clone();
2042        let _ = block_on(run_interpreter_inner(state, &mut result_vars))
2043            .expect("store var overwrite should complete for nested handle-object value");
2044        assert!(
2045            !fusion_residency::is_resident(&handle),
2046            "store var overwrite should clear residency for nested handle-object handles when unaliased"
2047        );
2048        assert!(
2049            block_on(TEST_PROVIDER.download(&handle)).is_err(),
2050            "store var overwrite should release provider storage for nested handle-object handles when unaliased"
2051        );
2052    }
2053
2054    #[cfg(feature = "native-accel")]
2055    #[test]
2056    fn store_var_overwrite_preserves_nested_handle_object_provider_handle_when_shared_in_other_var()
2057    {
2058        use runmat_accelerate::fusion_residency;
2059
2060        let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
2061        let handle = upload_provider_handle(vec![24.0], vec![1]);
2062        assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
2063        fusion_residency::mark(&handle);
2064
2065        let bytecode = Bytecode::with_instructions(vec![Instr::StoreVar(0), Instr::Return], 2);
2066        let mut seed_vars = vec![Value::Num(0.0), Value::Num(0.0)];
2067        let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
2068        let mut payload = StructValue::new();
2069        payload
2070            .fields
2071            .insert("nested".to_string(), Value::GpuTensor(handle.clone()));
2072        let target = runmat_gc::gc_allocate(Value::Struct(payload)).expect("gc allocate payload");
2073        let nested = Value::HandleObject(HandleRef {
2074            class_name: "Payload".to_string(),
2075            target,
2076            valid: true,
2077        });
2078        state.vars = vec![nested.clone(), nested.clone()];
2079        state.stack.push(Value::Num(0.0));
2080
2081        let mut result_vars = state.vars.clone();
2082        let _ = block_on(run_interpreter_inner(state, &mut result_vars))
2083            .expect("store var overwrite should complete for aliased nested handle-object values");
2084        assert!(
2085            fusion_residency::is_resident(&handle),
2086            "store var overwrite should preserve residency for nested handle-object handles still live in other vars"
2087        );
2088        assert!(
2089            block_on(TEST_PROVIDER.download(&handle)).is_ok(),
2090            "store var overwrite should not release provider storage for nested handle-object handles still live in other vars"
2091        );
2092        fusion_residency::clear(&handle);
2093        let _ = TEST_PROVIDER.free(&handle);
2094    }
2095
2096    #[cfg(feature = "native-accel")]
2097    #[test]
2098    fn store_var_overwrite_preserves_nested_handle_object_provider_handle_when_shared_in_local() {
2099        use runmat_accelerate::fusion_residency;
2100
2101        let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
2102        let handle = upload_provider_handle(vec![27.0], vec![1]);
2103        assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
2104        fusion_residency::mark(&handle);
2105
2106        let bytecode = Bytecode::with_instructions(vec![Instr::StoreVar(0), Instr::Return], 1);
2107        let mut seed_vars = vec![Value::Num(0.0)];
2108        let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
2109        let mut payload = StructValue::new();
2110        payload
2111            .fields
2112            .insert("nested".to_string(), Value::GpuTensor(handle.clone()));
2113        let target = runmat_gc::gc_allocate(Value::Struct(payload)).expect("gc allocate payload");
2114        let nested = Value::HandleObject(HandleRef {
2115            class_name: "Payload".to_string(),
2116            target,
2117            valid: true,
2118        });
2119        state.vars = vec![nested.clone()];
2120        state.stack.push(Value::Num(0.0));
2121        state.context.locals.push(nested);
2122
2123        let mut result_vars = state.vars.clone();
2124        let _ = block_on(run_interpreter_inner(state, &mut result_vars))
2125            .expect("store var overwrite should complete when alias lives in locals");
2126        assert!(
2127            fusion_residency::is_resident(&handle),
2128            "store var overwrite should preserve residency for nested handle-object handles still live in locals"
2129        );
2130        assert!(
2131            block_on(TEST_PROVIDER.download(&handle)).is_ok(),
2132            "store var overwrite should not release provider storage for nested handle-object handles still live in locals"
2133        );
2134        fusion_residency::clear(&handle);
2135        let _ = TEST_PROVIDER.free(&handle);
2136    }
2137
2138    #[cfg(feature = "native-accel")]
2139    #[test]
2140    fn store_local_overwrite_preserves_provider_handle_when_shared_in_var() {
2141        use runmat_accelerate::fusion_residency;
2142
2143        let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
2144        let handle = upload_provider_handle(vec![23.0], vec![1]);
2145        assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
2146        fusion_residency::mark(&handle);
2147
2148        let bytecode = Bytecode::with_instructions(vec![Instr::StoreLocal(0), Instr::Return], 1);
2149        let mut seed_vars = vec![Value::GpuTensor(handle.clone())];
2150        let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
2151        state.stack.push(Value::Num(0.0));
2152        state.vars = vec![Value::GpuTensor(handle.clone())];
2153        state
2154            .context
2155            .call_stack
2156            .push(crate::bytecode::program::CallFrame {
2157                function_name: "<local>".to_string(),
2158                return_address: 0,
2159                locals_start: 0,
2160                locals_count: 1,
2161                expected_outputs: 0,
2162            });
2163        state.context.locals.push(Value::GpuTensor(handle.clone()));
2164
2165        let mut result_vars = state.vars.clone();
2166        let _ = block_on(run_interpreter_inner(state, &mut result_vars))
2167            .expect("store local should complete");
2168        assert!(
2169            fusion_residency::is_resident(&handle),
2170            "store local overwrite should preserve residency for handles still live in vars"
2171        );
2172        assert!(
2173            block_on(TEST_PROVIDER.download(&handle)).is_ok(),
2174            "store local overwrite should not release provider storage for handles still live in vars"
2175        );
2176        fusion_residency::clear(&handle);
2177        let _ = TEST_PROVIDER.free(&handle);
2178    }
2179
2180    #[cfg(feature = "native-accel")]
2181    #[test]
2182    fn store_local_overwrite_preserves_provider_handle_when_shared_in_other_local() {
2183        use runmat_accelerate::fusion_residency;
2184
2185        let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
2186        let handle = upload_provider_handle(vec![24.0], vec![1]);
2187        assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
2188        fusion_residency::mark(&handle);
2189
2190        let bytecode = Bytecode::with_instructions(vec![Instr::StoreLocal(0), Instr::Return], 1);
2191        let mut seed_vars = vec![Value::Num(0.0)];
2192        let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
2193        state.stack.push(Value::Num(0.0));
2194        state.vars = vec![Value::Num(0.0)];
2195        state
2196            .context
2197            .call_stack
2198            .push(crate::bytecode::program::CallFrame {
2199                function_name: "<local>".to_string(),
2200                return_address: 0,
2201                locals_start: 0,
2202                locals_count: 2,
2203                expected_outputs: 0,
2204            });
2205        state.context.locals.push(Value::GpuTensor(handle.clone()));
2206        state.context.locals.push(Value::GpuTensor(handle.clone()));
2207
2208        let mut result_vars = state.vars.clone();
2209        let _ = block_on(run_interpreter_inner(state, &mut result_vars))
2210            .expect("store local should complete when alias lives in other local");
2211        assert!(
2212            fusion_residency::is_resident(&handle),
2213            "store local overwrite should preserve residency for handles still live in other locals"
2214        );
2215        assert!(
2216            block_on(TEST_PROVIDER.download(&handle)).is_ok(),
2217            "store local overwrite should not release provider storage for handles still live in other locals"
2218        );
2219        fusion_residency::clear(&handle);
2220        let _ = TEST_PROVIDER.free(&handle);
2221    }
2222
2223    #[cfg(feature = "native-accel")]
2224    #[test]
2225    fn store_local_overwrite_releases_provider_handle_when_unaliased() {
2226        use runmat_accelerate::fusion_residency;
2227
2228        let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
2229        let handle = upload_provider_handle(vec![25.0], vec![1]);
2230        assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
2231        fusion_residency::mark(&handle);
2232
2233        let bytecode = Bytecode::with_instructions(vec![Instr::StoreLocal(0), Instr::Return], 1);
2234        let mut seed_vars = vec![Value::Num(0.0)];
2235        let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
2236        state.stack.push(Value::Num(0.0));
2237        state.vars = vec![Value::Num(0.0)];
2238        state
2239            .context
2240            .call_stack
2241            .push(crate::bytecode::program::CallFrame {
2242                function_name: "<local>".to_string(),
2243                return_address: 0,
2244                locals_start: 0,
2245                locals_count: 1,
2246                expected_outputs: 0,
2247            });
2248        state.context.locals.push(Value::GpuTensor(handle.clone()));
2249
2250        let mut result_vars = state.vars.clone();
2251        let _ = block_on(run_interpreter_inner(state, &mut result_vars))
2252            .expect("store local overwrite should complete");
2253        assert!(
2254            !fusion_residency::is_resident(&handle),
2255            "store local overwrite should clear residency for unaliased local handles"
2256        );
2257        assert!(
2258            block_on(TEST_PROVIDER.download(&handle)).is_err(),
2259            "store local overwrite should release provider storage for unaliased local handles"
2260        );
2261    }
2262
2263    #[cfg(feature = "native-accel")]
2264    #[test]
2265    fn store_local_overwrite_releases_nested_handle_object_provider_handle_when_unaliased() {
2266        use runmat_accelerate::fusion_residency;
2267
2268        let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
2269        let handle = upload_provider_handle(vec![26.0], vec![1]);
2270        assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
2271        fusion_residency::mark(&handle);
2272
2273        let bytecode = Bytecode::with_instructions(vec![Instr::StoreLocal(0), Instr::Return], 1);
2274        let mut seed_vars = vec![Value::Num(0.0)];
2275        let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
2276        let mut payload = StructValue::new();
2277        payload
2278            .fields
2279            .insert("nested".to_string(), Value::GpuTensor(handle.clone()));
2280        let target = runmat_gc::gc_allocate(Value::Struct(payload)).expect("gc allocate payload");
2281        state.stack.push(Value::Num(0.0));
2282        state.vars = vec![Value::Num(0.0)];
2283        state
2284            .context
2285            .call_stack
2286            .push(crate::bytecode::program::CallFrame {
2287                function_name: "<local>".to_string(),
2288                return_address: 0,
2289                locals_start: 0,
2290                locals_count: 1,
2291                expected_outputs: 0,
2292            });
2293        state.context.locals.push(Value::HandleObject(HandleRef {
2294            class_name: "Payload".to_string(),
2295            target,
2296            valid: true,
2297        }));
2298
2299        let mut result_vars = state.vars.clone();
2300        let _ = block_on(run_interpreter_inner(state, &mut result_vars))
2301            .expect("store local overwrite should complete for nested handle-object value");
2302        assert!(
2303            !fusion_residency::is_resident(&handle),
2304            "store local overwrite should clear residency for nested handle-object handles when unaliased"
2305        );
2306        assert!(
2307            block_on(TEST_PROVIDER.download(&handle)).is_err(),
2308            "store local overwrite should release provider storage for nested handle-object handles when unaliased"
2309        );
2310    }
2311
2312    #[cfg(feature = "native-accel")]
2313    #[test]
2314    fn store_local_overwrite_preserves_nested_handle_object_provider_handle_when_shared_in_var() {
2315        use runmat_accelerate::fusion_residency;
2316
2317        let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
2318        let handle = upload_provider_handle(vec![28.0], vec![1]);
2319        assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
2320        fusion_residency::mark(&handle);
2321
2322        let bytecode = Bytecode::with_instructions(vec![Instr::StoreLocal(0), Instr::Return], 1);
2323        let mut seed_vars = vec![Value::Num(0.0)];
2324        let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
2325        let mut payload = StructValue::new();
2326        payload
2327            .fields
2328            .insert("nested".to_string(), Value::GpuTensor(handle.clone()));
2329        let target = runmat_gc::gc_allocate(Value::Struct(payload)).expect("gc allocate payload");
2330        let local_value = Value::HandleObject(HandleRef {
2331            class_name: "Payload".to_string(),
2332            target,
2333            valid: true,
2334        });
2335        state.stack.push(Value::Num(0.0));
2336        state.vars = vec![local_value.clone()];
2337        state
2338            .context
2339            .call_stack
2340            .push(crate::bytecode::program::CallFrame {
2341                function_name: "<local>".to_string(),
2342                return_address: 0,
2343                locals_start: 0,
2344                locals_count: 1,
2345                expected_outputs: 0,
2346            });
2347        state.context.locals.push(local_value);
2348
2349        let mut result_vars = state.vars.clone();
2350        let _ = block_on(run_interpreter_inner(state, &mut result_vars))
2351            .expect("store local overwrite should complete for aliased nested handle-object value");
2352        assert!(
2353            fusion_residency::is_resident(&handle),
2354            "store local overwrite should preserve residency for nested handle-object handles still live in vars"
2355        );
2356        assert!(
2357            block_on(TEST_PROVIDER.download(&handle)).is_ok(),
2358            "store local overwrite should not release provider storage for nested handle-object handles still live in vars"
2359        );
2360        fusion_residency::clear(&handle);
2361        let _ = TEST_PROVIDER.free(&handle);
2362    }
2363
2364    #[cfg(feature = "native-accel")]
2365    #[test]
2366    fn store_local_overwrite_preserves_nested_handle_object_provider_alias() {
2367        use runmat_accelerate::fusion_residency;
2368
2369        let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
2370        let handle = upload_provider_handle(vec![30.0], vec![1]);
2371        assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
2372        fusion_residency::mark(&handle);
2373
2374        let bytecode = Bytecode::with_instructions(vec![Instr::StoreLocal(0), Instr::Return], 1);
2375        let mut seed_vars = vec![Value::Num(0.0)];
2376        let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
2377        let mut payload = StructValue::new();
2378        payload
2379            .fields
2380            .insert("nested".to_string(), Value::GpuTensor(handle.clone()));
2381        let target = runmat_gc::gc_allocate(Value::Struct(payload)).expect("gc allocate payload");
2382        let nested = Value::HandleObject(HandleRef {
2383            class_name: "Payload".to_string(),
2384            target,
2385            valid: true,
2386        });
2387        state.stack.push(Value::Num(0.0));
2388        state.vars = vec![Value::Num(0.0)];
2389        state
2390            .context
2391            .call_stack
2392            .push(crate::bytecode::program::CallFrame {
2393                function_name: "<local>".to_string(),
2394                return_address: 0,
2395                locals_start: 0,
2396                locals_count: 2,
2397                expected_outputs: 0,
2398            });
2399        state.context.locals.push(nested.clone());
2400        state.context.locals.push(nested);
2401
2402        let mut result_vars = state.vars.clone();
2403        let _ = block_on(run_interpreter_inner(state, &mut result_vars))
2404            .expect("store local overwrite should complete when alias lives in other local");
2405        assert!(
2406            fusion_residency::is_resident(&handle),
2407            "store local overwrite should preserve residency for nested handle-object handles still live in other locals"
2408        );
2409        assert!(
2410            block_on(TEST_PROVIDER.download(&handle)).is_ok(),
2411            "store local overwrite should not release provider storage for nested handle-object handles still live in other locals"
2412        );
2413        fusion_residency::clear(&handle);
2414        let _ = TEST_PROVIDER.free(&handle);
2415    }
2416
2417    #[cfg(feature = "native-accel")]
2418    #[test]
2419    fn spawn_await_completion_releases_stack_only_provider_handle() {
2420        use runmat_accelerate::fusion_residency;
2421
2422        let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
2423        let handle = upload_provider_handle(vec![21.0], vec![1]);
2424        assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
2425        fusion_residency::mark(&handle);
2426
2427        let bytecode =
2428            Bytecode::with_instructions(vec![Instr::Spawn, Instr::Await, Instr::Return], 1);
2429        let mut seed_vars = vec![Value::Num(0.0)];
2430        let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
2431        state.stack.push(Value::GpuTensor(handle.clone()));
2432        state.vars = vec![Value::Num(0.0)];
2433
2434        let mut result_vars = vec![Value::Num(0.0)];
2435        let _ = block_on(run_interpreter_inner(state, &mut result_vars))
2436            .expect("spawn/await flow should complete");
2437        assert!(
2438            !fusion_residency::is_resident(&handle),
2439            "spawn/await completion should clear residency for stack-only handle"
2440        );
2441        assert!(
2442            block_on(TEST_PROVIDER.download(&handle)).is_err(),
2443            "spawn/await completion should release provider storage for stack-only handle"
2444        );
2445    }
2446
2447    #[cfg(feature = "native-accel")]
2448    #[test]
2449    fn spawn_await_completion_preserves_provider_handle_when_still_live_in_vars() {
2450        use runmat_accelerate::fusion_residency;
2451
2452        let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
2453        let handle = upload_provider_handle(vec![31.0], vec![1]);
2454        assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
2455        fusion_residency::mark(&handle);
2456
2457        let bytecode =
2458            Bytecode::with_instructions(vec![Instr::Spawn, Instr::Await, Instr::Return], 1);
2459        let mut seed_vars = vec![Value::GpuTensor(handle.clone())];
2460        let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
2461        state.stack.push(Value::GpuTensor(handle.clone()));
2462        state.vars = vec![Value::GpuTensor(handle.clone())];
2463
2464        let mut result_vars = vec![Value::GpuTensor(handle.clone())];
2465        let _ = block_on(run_interpreter_inner(state, &mut result_vars))
2466            .expect("spawn/await flow should complete");
2467        assert!(
2468            fusion_residency::is_resident(&handle),
2469            "spawn/await completion should preserve residency for live-var handle"
2470        );
2471        assert!(
2472            block_on(TEST_PROVIDER.download(&handle)).is_ok(),
2473            "spawn/await completion should not release provider storage for live-var handle"
2474        );
2475        fusion_residency::clear(&handle);
2476        let _ = TEST_PROVIDER.free(&handle);
2477    }
2478
2479    #[cfg(feature = "native-accel")]
2480    #[test]
2481    fn spawn_pop_releases_stack_only_provider_handle() {
2482        use runmat_accelerate::fusion_residency;
2483
2484        let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
2485        let handle = upload_provider_handle(vec![41.0], vec![1]);
2486        assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
2487        fusion_residency::mark(&handle);
2488
2489        let bytecode =
2490            Bytecode::with_instructions(vec![Instr::Spawn, Instr::Pop, Instr::Return], 1);
2491        let mut seed_vars = vec![Value::Num(0.0)];
2492        let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
2493        state.stack.push(Value::GpuTensor(handle.clone()));
2494        state.vars = vec![Value::Num(0.0)];
2495
2496        let mut result_vars = vec![Value::Num(0.0)];
2497        let _ = block_on(run_interpreter_inner(state, &mut result_vars))
2498            .expect("spawn/pop should complete");
2499        assert!(
2500            !fusion_residency::is_resident(&handle),
2501            "spawn/pop should clear residency for dropped spawned task payload"
2502        );
2503        assert!(
2504            block_on(TEST_PROVIDER.download(&handle)).is_err(),
2505            "spawn/pop should release provider storage for dropped spawned task payload"
2506        );
2507    }
2508
2509    #[cfg(feature = "native-accel")]
2510    #[test]
2511    fn spawn_pop_preserves_provider_handle_when_payload_still_live_in_vars() {
2512        use runmat_accelerate::fusion_residency;
2513
2514        let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
2515        let handle = upload_provider_handle(vec![51.0], vec![1]);
2516        assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
2517        fusion_residency::mark(&handle);
2518
2519        let bytecode =
2520            Bytecode::with_instructions(vec![Instr::Spawn, Instr::Pop, Instr::Return], 1);
2521        let mut seed_vars = vec![Value::GpuTensor(handle.clone())];
2522        let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
2523        state.stack.push(Value::GpuTensor(handle.clone()));
2524        state.vars = vec![Value::GpuTensor(handle.clone())];
2525
2526        let mut result_vars = vec![Value::GpuTensor(handle.clone())];
2527        let _ = block_on(run_interpreter_inner(state, &mut result_vars))
2528            .expect("spawn/pop should complete");
2529        assert!(
2530            fusion_residency::is_resident(&handle),
2531            "spawn/pop should preserve residency for spawned payload handles still referenced by vars"
2532        );
2533        assert!(
2534            block_on(TEST_PROVIDER.download(&handle)).is_ok(),
2535            "spawn/pop should not release provider storage for spawned payload handles still referenced by vars"
2536        );
2537        fusion_residency::clear(&handle);
2538        let _ = TEST_PROVIDER.free(&handle);
2539    }
2540
2541    #[cfg(feature = "native-accel")]
2542    #[test]
2543    fn spawn_pop_preserves_provider_handle_when_payload_still_live_in_locals() {
2544        use runmat_accelerate::fusion_residency;
2545
2546        let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
2547        let handle = upload_provider_handle(vec![56.0], vec![1]);
2548        assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
2549        fusion_residency::mark(&handle);
2550
2551        let bytecode =
2552            Bytecode::with_instructions(vec![Instr::Spawn, Instr::Pop, Instr::Return], 1);
2553        let mut seed_vars = vec![Value::Num(0.0)];
2554        let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
2555        state.stack.push(Value::GpuTensor(handle.clone()));
2556        state.vars = vec![Value::Num(0.0)];
2557        state.context.locals.push(Value::GpuTensor(handle.clone()));
2558
2559        let mut result_vars = vec![Value::Num(0.0)];
2560        let _ = block_on(run_interpreter_inner(state, &mut result_vars))
2561            .expect("spawn/pop should complete");
2562        assert!(
2563            fusion_residency::is_resident(&handle),
2564            "spawn/pop should preserve residency for spawned payload handles still referenced by locals"
2565        );
2566        assert!(
2567            block_on(TEST_PROVIDER.download(&handle)).is_ok(),
2568            "spawn/pop should not release provider storage for spawned payload handles still referenced by locals"
2569        );
2570        fusion_residency::clear(&handle);
2571        let _ = TEST_PROVIDER.free(&handle);
2572    }
2573
2574    #[cfg(feature = "native-accel")]
2575    #[test]
2576    fn spawn_pop_releases_nested_closure_captured_provider_handle() {
2577        use runmat_accelerate::fusion_residency;
2578
2579        let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
2580        let handle = upload_provider_handle(vec![61.0], vec![1]);
2581        assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
2582        fusion_residency::mark(&handle);
2583
2584        let bytecode =
2585            Bytecode::with_instructions(vec![Instr::Spawn, Instr::Pop, Instr::Return], 1);
2586        let mut seed_vars = vec![Value::Num(0.0)];
2587        let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
2588        state.stack.push(Value::Closure(Closure {
2589            function_name: "worker".to_string(),
2590            bound_function: None,
2591            captures: vec![Value::GpuTensor(handle.clone())],
2592        }));
2593        state.vars = vec![Value::Num(0.0)];
2594
2595        let mut result_vars = vec![Value::Num(0.0)];
2596        let _ = block_on(run_interpreter_inner(state, &mut result_vars))
2597            .expect("spawn/pop should complete for closure payload");
2598        assert!(
2599            !fusion_residency::is_resident(&handle),
2600            "spawn/pop should clear residency for nested closure-captured payload handles"
2601        );
2602        assert!(
2603            block_on(TEST_PROVIDER.download(&handle)).is_err(),
2604            "spawn/pop should release provider storage for nested closure-captured payload handles"
2605        );
2606    }
2607
2608    #[cfg(feature = "native-accel")]
2609    #[test]
2610    fn spawn_await_completion_releases_nested_output_list_provider_handle() {
2611        use runmat_accelerate::fusion_residency;
2612
2613        let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
2614        let handle = upload_provider_handle(vec![71.0], vec![1]);
2615        assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
2616        fusion_residency::mark(&handle);
2617
2618        let bytecode =
2619            Bytecode::with_instructions(vec![Instr::Spawn, Instr::Await, Instr::Return], 1);
2620        let mut seed_vars = vec![Value::Num(0.0)];
2621        let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
2622        state
2623            .stack
2624            .push(Value::OutputList(vec![Value::GpuTensor(handle.clone())]));
2625        state.vars = vec![Value::Num(0.0)];
2626
2627        let mut result_vars = vec![Value::Num(0.0)];
2628        let _ = block_on(run_interpreter_inner(state, &mut result_vars))
2629            .expect("spawn/await flow should complete for nested output payload");
2630        assert!(
2631            !fusion_residency::is_resident(&handle),
2632            "spawn/await completion should clear residency for nested output-list payload handles"
2633        );
2634        assert!(
2635            block_on(TEST_PROVIDER.download(&handle)).is_err(),
2636            "spawn/await completion should release provider storage for nested output-list payload handles"
2637        );
2638    }
2639
2640    #[cfg(feature = "native-accel")]
2641    #[test]
2642    fn spawn_await_completion_releases_nested_struct_provider_handle() {
2643        use runmat_accelerate::fusion_residency;
2644
2645        let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
2646        let handle = upload_provider_handle(vec![81.0], vec![1]);
2647        assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
2648        fusion_residency::mark(&handle);
2649
2650        let bytecode =
2651            Bytecode::with_instructions(vec![Instr::Spawn, Instr::Await, Instr::Return], 1);
2652        let mut seed_vars = vec![Value::Num(0.0)];
2653        let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
2654        let mut payload = StructValue::new();
2655        payload
2656            .fields
2657            .insert("nested".to_string(), Value::GpuTensor(handle.clone()));
2658        state.stack.push(Value::Struct(payload));
2659        state.vars = vec![Value::Num(0.0)];
2660
2661        let mut result_vars = vec![Value::Num(0.0)];
2662        let _ = block_on(run_interpreter_inner(state, &mut result_vars))
2663            .expect("spawn/await flow should complete for nested struct payload");
2664        assert!(
2665            !fusion_residency::is_resident(&handle),
2666            "spawn/await completion should clear residency for nested struct payload handles"
2667        );
2668        assert!(
2669            block_on(TEST_PROVIDER.download(&handle)).is_err(),
2670            "spawn/await completion should release provider storage for nested struct payload handles"
2671        );
2672    }
2673
2674    #[cfg(feature = "native-accel")]
2675    #[test]
2676    fn spawn_await_completion_releases_nested_object_property_provider_handle() {
2677        use runmat_accelerate::fusion_residency;
2678
2679        let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
2680        let handle = upload_provider_handle(vec![91.0], vec![1]);
2681        assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
2682        fusion_residency::mark(&handle);
2683
2684        let bytecode =
2685            Bytecode::with_instructions(vec![Instr::Spawn, Instr::Await, Instr::Return], 1);
2686        let mut seed_vars = vec![Value::Num(0.0)];
2687        let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
2688        let mut payload = ObjectInstance::new("Payload".to_string());
2689        payload
2690            .properties
2691            .insert("nested".to_string(), Value::GpuTensor(handle.clone()));
2692        state.stack.push(Value::Object(payload));
2693        state.vars = vec![Value::Num(0.0)];
2694
2695        let mut result_vars = vec![Value::Num(0.0)];
2696        let _ = block_on(run_interpreter_inner(state, &mut result_vars))
2697            .expect("spawn/await flow should complete for nested object payload");
2698        assert!(
2699            !fusion_residency::is_resident(&handle),
2700            "spawn/await completion should clear residency for nested object-property payload handles"
2701        );
2702        assert!(
2703            block_on(TEST_PROVIDER.download(&handle)).is_err(),
2704            "spawn/await completion should release provider storage for nested object-property payload handles"
2705        );
2706    }
2707
2708    #[cfg(feature = "native-accel")]
2709    #[test]
2710    fn spawn_await_completion_preserves_nested_object_property_handle_when_alias_live() {
2711        use runmat_accelerate::fusion_residency;
2712
2713        let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
2714        let handle = upload_provider_handle(vec![101.0], vec![1]);
2715        assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
2716        fusion_residency::mark(&handle);
2717
2718        let bytecode =
2719            Bytecode::with_instructions(vec![Instr::Spawn, Instr::Await, Instr::Return], 1);
2720        let mut seed_vars = vec![Value::Num(0.0)];
2721        let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
2722        let mut payload = ObjectInstance::new("Payload".to_string());
2723        payload
2724            .properties
2725            .insert("nested".to_string(), Value::GpuTensor(handle.clone()));
2726        state.stack.push(Value::Object(payload.clone()));
2727        state.vars = vec![Value::Object(payload.clone())];
2728
2729        let mut result_vars = vec![Value::Object(payload)];
2730        let _ = block_on(run_interpreter_inner(state, &mut result_vars))
2731            .expect("spawn/await flow should complete for aliased nested object payload");
2732        assert!(
2733            fusion_residency::is_resident(&handle),
2734            "spawn/await completion should preserve residency for nested object handles still referenced by vars"
2735        );
2736        assert!(
2737            block_on(TEST_PROVIDER.download(&handle)).is_ok(),
2738            "spawn/await completion should not release provider storage for nested object handles still referenced by vars"
2739        );
2740        fusion_residency::clear(&handle);
2741        let _ = TEST_PROVIDER.free(&handle);
2742    }
2743
2744    #[cfg(feature = "native-accel")]
2745    #[test]
2746    fn spawn_await_completion_releases_nested_cell_provider_handle() {
2747        use runmat_accelerate::fusion_residency;
2748
2749        let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
2750        let handle = upload_provider_handle(vec![111.0], vec![1]);
2751        assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
2752        fusion_residency::mark(&handle);
2753
2754        let bytecode =
2755            Bytecode::with_instructions(vec![Instr::Spawn, Instr::Await, Instr::Return], 1);
2756        let mut seed_vars = vec![Value::Num(0.0)];
2757        let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
2758        let payload =
2759            CellArray::new(vec![Value::GpuTensor(handle.clone())], 1, 1).expect("cell payload");
2760        state.stack.push(Value::Cell(payload));
2761        state.vars = vec![Value::Num(0.0)];
2762
2763        let mut result_vars = vec![Value::Num(0.0)];
2764        let _ = block_on(run_interpreter_inner(state, &mut result_vars))
2765            .expect("spawn/await flow should complete for nested cell payload");
2766        assert!(
2767            !fusion_residency::is_resident(&handle),
2768            "spawn/await completion should clear residency for nested cell payload handles"
2769        );
2770        assert!(
2771            block_on(TEST_PROVIDER.download(&handle)).is_err(),
2772            "spawn/await completion should release provider storage for nested cell payload handles"
2773        );
2774    }
2775
2776    #[cfg(feature = "native-accel")]
2777    #[test]
2778    fn spawn_await_completion_preserves_nested_cell_handle_when_alias_live() {
2779        use runmat_accelerate::fusion_residency;
2780
2781        let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
2782        let handle = upload_provider_handle(vec![121.0], vec![1]);
2783        assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
2784        fusion_residency::mark(&handle);
2785
2786        let bytecode =
2787            Bytecode::with_instructions(vec![Instr::Spawn, Instr::Await, Instr::Return], 1);
2788        let mut seed_vars = vec![Value::Num(0.0)];
2789        let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
2790        let payload =
2791            CellArray::new(vec![Value::GpuTensor(handle.clone())], 1, 1).expect("cell payload");
2792        state.stack.push(Value::Cell(payload.clone()));
2793        state.vars = vec![Value::Cell(payload.clone())];
2794
2795        let mut result_vars = vec![Value::Cell(payload)];
2796        let _ = block_on(run_interpreter_inner(state, &mut result_vars))
2797            .expect("spawn/await flow should complete for aliased nested cell payload");
2798        assert!(
2799            fusion_residency::is_resident(&handle),
2800            "spawn/await completion should preserve residency for nested cell handles still referenced by vars"
2801        );
2802        assert!(
2803            block_on(TEST_PROVIDER.download(&handle)).is_ok(),
2804            "spawn/await completion should not release provider storage for nested cell handles still referenced by vars"
2805        );
2806        fusion_residency::clear(&handle);
2807        let _ = TEST_PROVIDER.free(&handle);
2808    }
2809
2810    #[cfg(feature = "native-accel")]
2811    #[test]
2812    fn spawn_await_completion_releases_nested_handle_object_target_provider_handle() {
2813        use runmat_accelerate::fusion_residency;
2814
2815        let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
2816        let handle = upload_provider_handle(vec![131.0], vec![1]);
2817        assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
2818        fusion_residency::mark(&handle);
2819
2820        let bytecode =
2821            Bytecode::with_instructions(vec![Instr::Spawn, Instr::Await, Instr::Return], 1);
2822        let mut seed_vars = vec![Value::Num(0.0)];
2823        let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
2824        let mut payload = StructValue::new();
2825        payload
2826            .fields
2827            .insert("nested".to_string(), Value::GpuTensor(handle.clone()));
2828        let target = runmat_gc::gc_allocate(Value::Struct(payload)).expect("gc allocate payload");
2829        let task_payload = Value::HandleObject(HandleRef {
2830            class_name: "Payload".to_string(),
2831            target,
2832            valid: true,
2833        });
2834        state.stack.push(task_payload);
2835        state.vars = vec![Value::Num(0.0)];
2836
2837        let mut result_vars = vec![Value::Num(0.0)];
2838        let _ = block_on(run_interpreter_inner(state, &mut result_vars))
2839            .expect("spawn/await flow should complete for nested handle-object payload");
2840        assert!(
2841            !fusion_residency::is_resident(&handle),
2842            "spawn/await completion should clear residency for nested handle-object target handles"
2843        );
2844        assert!(
2845            block_on(TEST_PROVIDER.download(&handle)).is_err(),
2846            "spawn/await completion should release provider storage for nested handle-object target handles"
2847        );
2848    }
2849
2850    #[cfg(feature = "native-accel")]
2851    #[test]
2852    fn spawn_await_completion_preserves_nested_handle_object_target_handle_when_alias_live() {
2853        use runmat_accelerate::fusion_residency;
2854
2855        let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
2856        let handle = upload_provider_handle(vec![141.0], vec![1]);
2857        assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
2858        fusion_residency::mark(&handle);
2859
2860        let bytecode =
2861            Bytecode::with_instructions(vec![Instr::Spawn, Instr::Await, Instr::Return], 1);
2862        let mut seed_vars = vec![Value::Num(0.0)];
2863        let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
2864        let mut payload = StructValue::new();
2865        payload
2866            .fields
2867            .insert("nested".to_string(), Value::GpuTensor(handle.clone()));
2868        let target = runmat_gc::gc_allocate(Value::Struct(payload)).expect("gc allocate payload");
2869        let task_payload = Value::HandleObject(HandleRef {
2870            class_name: "Payload".to_string(),
2871            target,
2872            valid: true,
2873        });
2874        state.stack.push(task_payload.clone());
2875        state.vars = vec![task_payload.clone()];
2876
2877        let mut result_vars = vec![task_payload];
2878        let _ = block_on(run_interpreter_inner(state, &mut result_vars))
2879            .expect("spawn/await flow should complete for aliased nested handle-object payload");
2880        assert!(
2881            fusion_residency::is_resident(&handle),
2882            "spawn/await completion should preserve residency for nested handle-object target handles still referenced by vars"
2883        );
2884        assert!(
2885            block_on(TEST_PROVIDER.download(&handle)).is_ok(),
2886            "spawn/await completion should not release provider storage for nested handle-object target handles still referenced by vars"
2887        );
2888        fusion_residency::clear(&handle);
2889        let _ = TEST_PROVIDER.free(&handle);
2890    }
2891
2892    #[cfg(feature = "native-accel")]
2893    #[test]
2894    fn spawn_await_preserves_nested_handle_object_target_alias() {
2895        use runmat_accelerate::fusion_residency;
2896
2897        let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
2898        let handle = upload_provider_handle(vec![146.0], vec![1]);
2899        assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
2900        fusion_residency::mark(&handle);
2901
2902        let bytecode =
2903            Bytecode::with_instructions(vec![Instr::Spawn, Instr::Await, Instr::Return], 1);
2904        let mut seed_vars = vec![Value::Num(0.0)];
2905        let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
2906        let mut payload = StructValue::new();
2907        payload
2908            .fields
2909            .insert("nested".to_string(), Value::GpuTensor(handle.clone()));
2910        let target = runmat_gc::gc_allocate(Value::Struct(payload)).expect("gc allocate payload");
2911        let task_payload = Value::HandleObject(HandleRef {
2912            class_name: "Payload".to_string(),
2913            target,
2914            valid: true,
2915        });
2916        state.stack.push(task_payload.clone());
2917        state.vars = vec![Value::Num(0.0)];
2918        state.context.locals.push(task_payload.clone());
2919
2920        let mut result_vars = vec![Value::Num(0.0)];
2921        let _ = block_on(run_interpreter_inner(state, &mut result_vars))
2922            .expect("spawn/await flow should complete for aliased nested handle-object payload");
2923        assert!(
2924            fusion_residency::is_resident(&handle),
2925            "spawn/await completion should preserve residency for nested handle-object target handles still referenced by locals"
2926        );
2927        assert!(
2928            block_on(TEST_PROVIDER.download(&handle)).is_ok(),
2929            "spawn/await completion should not release provider storage for nested handle-object target handles still referenced by locals"
2930        );
2931        fusion_residency::clear(&handle);
2932        let _ = TEST_PROVIDER.free(&handle);
2933    }
2934
2935    #[cfg(feature = "native-accel")]
2936    #[test]
2937    fn spawn_pop_releases_nested_handle_object_target_provider_handle() {
2938        use runmat_accelerate::fusion_residency;
2939
2940        let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
2941        let handle = upload_provider_handle(vec![151.0], vec![1]);
2942        assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
2943        fusion_residency::mark(&handle);
2944
2945        let bytecode =
2946            Bytecode::with_instructions(vec![Instr::Spawn, Instr::Pop, Instr::Return], 1);
2947        let mut seed_vars = vec![Value::Num(0.0)];
2948        let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
2949        let mut payload = StructValue::new();
2950        payload
2951            .fields
2952            .insert("nested".to_string(), Value::GpuTensor(handle.clone()));
2953        let target = runmat_gc::gc_allocate(Value::Struct(payload)).expect("gc allocate payload");
2954        state.stack.push(Value::HandleObject(HandleRef {
2955            class_name: "Payload".to_string(),
2956            target,
2957            valid: true,
2958        }));
2959        state.vars = vec![Value::Num(0.0)];
2960
2961        let mut result_vars = vec![Value::Num(0.0)];
2962        let _ = block_on(run_interpreter_inner(state, &mut result_vars))
2963            .expect("spawn/pop flow should complete for nested handle-object payload");
2964        assert!(
2965            !fusion_residency::is_resident(&handle),
2966            "spawn/pop should clear residency for nested handle-object target handles"
2967        );
2968        assert!(
2969            block_on(TEST_PROVIDER.download(&handle)).is_err(),
2970            "spawn/pop should release provider storage for nested handle-object target handles"
2971        );
2972    }
2973
2974    #[cfg(feature = "native-accel")]
2975    #[test]
2976    fn spawn_pop_preserves_nested_handle_object_target_handle_when_alias_live() {
2977        use runmat_accelerate::fusion_residency;
2978
2979        let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
2980        let handle = upload_provider_handle(vec![161.0], vec![1]);
2981        assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
2982        fusion_residency::mark(&handle);
2983
2984        let bytecode =
2985            Bytecode::with_instructions(vec![Instr::Spawn, Instr::Pop, Instr::Return], 1);
2986        let mut seed_vars = vec![Value::Num(0.0)];
2987        let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
2988        let mut payload = StructValue::new();
2989        payload
2990            .fields
2991            .insert("nested".to_string(), Value::GpuTensor(handle.clone()));
2992        let target = runmat_gc::gc_allocate(Value::Struct(payload)).expect("gc allocate payload");
2993        let task_payload = Value::HandleObject(HandleRef {
2994            class_name: "Payload".to_string(),
2995            target,
2996            valid: true,
2997        });
2998        state.stack.push(task_payload.clone());
2999        state.vars = vec![task_payload.clone()];
3000
3001        let mut result_vars = vec![task_payload];
3002        let _ = block_on(run_interpreter_inner(state, &mut result_vars))
3003            .expect("spawn/pop flow should complete for aliased nested handle-object payload");
3004        assert!(
3005            fusion_residency::is_resident(&handle),
3006            "spawn/pop should preserve residency for nested handle-object target handles still referenced by vars"
3007        );
3008        assert!(
3009            block_on(TEST_PROVIDER.download(&handle)).is_ok(),
3010            "spawn/pop should not release provider storage for nested handle-object target handles still referenced by vars"
3011        );
3012        fusion_residency::clear(&handle);
3013        let _ = TEST_PROVIDER.free(&handle);
3014    }
3015
3016    #[cfg(feature = "native-accel")]
3017    #[test]
3018    fn spawn_pop_preserves_nested_handle_object_target_handle_when_alias_live_in_locals() {
3019        use runmat_accelerate::fusion_residency;
3020
3021        let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
3022        let handle = upload_provider_handle(vec![166.0], vec![1]);
3023        assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
3024        fusion_residency::mark(&handle);
3025
3026        let bytecode =
3027            Bytecode::with_instructions(vec![Instr::Spawn, Instr::Pop, Instr::Return], 1);
3028        let mut seed_vars = vec![Value::Num(0.0)];
3029        let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
3030        let mut payload = StructValue::new();
3031        payload
3032            .fields
3033            .insert("nested".to_string(), Value::GpuTensor(handle.clone()));
3034        let target = runmat_gc::gc_allocate(Value::Struct(payload)).expect("gc allocate payload");
3035        let task_payload = Value::HandleObject(HandleRef {
3036            class_name: "Payload".to_string(),
3037            target,
3038            valid: true,
3039        });
3040        state.stack.push(task_payload.clone());
3041        state.vars = vec![Value::Num(0.0)];
3042        state.context.locals.push(task_payload);
3043
3044        let mut result_vars = vec![Value::Num(0.0)];
3045        let _ = block_on(run_interpreter_inner(state, &mut result_vars))
3046            .expect("spawn/pop flow should complete for aliased nested handle-object payload");
3047        assert!(
3048            fusion_residency::is_resident(&handle),
3049            "spawn/pop should preserve residency for nested handle-object target handles still referenced by locals"
3050        );
3051        assert!(
3052            block_on(TEST_PROVIDER.download(&handle)).is_ok(),
3053            "spawn/pop should not release provider storage for nested handle-object target handles still referenced by locals"
3054        );
3055        fusion_residency::clear(&handle);
3056        let _ = TEST_PROVIDER.free(&handle);
3057    }
3058
3059    #[test]
3060    fn await_passes_through_non_spawn_value_operand() {
3061        let bytecode =
3062            Bytecode::with_instructions(vec![Instr::Await, Instr::StoreVar(0), Instr::Return], 1);
3063        let mut seed_vars = vec![Value::Num(0.0)];
3064        let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
3065        state.stack.push(Value::Num(7.0));
3066        state.vars = vec![Value::Num(0.0)];
3067
3068        let mut result_vars = vec![Value::Num(0.0)];
3069        let _ = block_on(run_interpreter_inner(state, &mut result_vars))
3070            .expect("await should pass through non-task operand");
3071        assert_eq!(result_vars[0], Value::Num(7.0));
3072    }
3073
3074    #[test]
3075    fn await_succeeds_after_spawn_handle_self_reassignment() {
3076        let bytecode = Bytecode::with_instructions(
3077            vec![
3078                Instr::Spawn,
3079                Instr::StoreVar(0),
3080                Instr::LoadVar(0),
3081                Instr::StoreVar(0),
3082                Instr::LoadVar(0),
3083                Instr::Await,
3084                Instr::StoreVar(0),
3085                Instr::Return,
3086            ],
3087            1,
3088        );
3089        let mut seed_vars = vec![Value::Num(0.0)];
3090        let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
3091        state.stack.push(Value::Num(9.0));
3092        state.vars = vec![Value::Num(0.0)];
3093
3094        let mut result_vars = vec![Value::Num(0.0)];
3095        let _ = block_on(run_interpreter_inner(state, &mut result_vars))
3096            .expect("await should still succeed after self-reassignment of spawn handle");
3097        assert_eq!(result_vars[0], Value::Num(9.0));
3098    }
3099
3100    #[test]
3101    fn await_succeeds_after_overwriting_one_spawn_handle_alias() {
3102        let bytecode = Bytecode::with_instructions(
3103            vec![
3104                Instr::Spawn,
3105                Instr::StoreVar(0),
3106                Instr::LoadVar(0),
3107                Instr::StoreVar(1),
3108                Instr::LoadConst(0.0),
3109                Instr::StoreVar(0),
3110                Instr::LoadVar(1),
3111                Instr::Await,
3112                Instr::StoreVar(0),
3113                Instr::Return,
3114            ],
3115            2,
3116        );
3117        let mut seed_vars = vec![Value::Num(0.0), Value::Num(0.0)];
3118        let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
3119        state.stack.push(Value::Num(9.0));
3120        state.vars = vec![Value::Num(0.0), Value::Num(0.0)];
3121
3122        let mut result_vars = vec![Value::Num(0.0), Value::Num(0.0)];
3123        let _ = block_on(run_interpreter_inner(state, &mut result_vars))
3124            .expect("await should succeed when another alias still carries the spawn task handle");
3125        assert_eq!(result_vars[0], Value::Num(9.0));
3126    }
3127
3128    #[test]
3129    fn await_succeeds_after_overwriting_one_local_spawn_handle_alias() {
3130        let bytecode = Bytecode::with_instructions(
3131            vec![
3132                Instr::Spawn,
3133                Instr::StoreLocal(0),
3134                Instr::LoadLocal(0),
3135                Instr::StoreLocal(1),
3136                Instr::LoadConst(0.0),
3137                Instr::StoreLocal(0),
3138                Instr::LoadLocal(1),
3139                Instr::Await,
3140                Instr::StoreVar(0),
3141                Instr::Return,
3142            ],
3143            1,
3144        );
3145        let mut seed_vars = vec![Value::Num(0.0)];
3146        let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
3147        state.stack.push(Value::Num(9.0));
3148        state.vars = vec![Value::Num(0.0)];
3149        state
3150            .context
3151            .call_stack
3152            .push(crate::bytecode::program::CallFrame {
3153                function_name: "<local>".to_string(),
3154                return_address: 0,
3155                locals_start: 0,
3156                locals_count: 2,
3157                expected_outputs: 0,
3158            });
3159        state.context.locals = vec![Value::Num(0.0), Value::Num(0.0)];
3160
3161        let mut result_vars = vec![Value::Num(0.0)];
3162        let _ = block_on(run_interpreter_inner(state, &mut result_vars)).expect(
3163            "await should succeed when another local alias still carries the spawn task handle",
3164        );
3165        assert_eq!(result_vars[0], Value::Num(9.0));
3166    }
3167
3168    #[test]
3169    fn await_succeeds_after_overwriting_var_alias_when_local_spawn_handle_alias_live() {
3170        let bytecode = Bytecode::with_instructions(
3171            vec![
3172                Instr::Spawn,
3173                Instr::StoreLocal(0),
3174                Instr::LoadLocal(0),
3175                Instr::StoreVar(0),
3176                Instr::LoadConst(0.0),
3177                Instr::StoreVar(0),
3178                Instr::LoadLocal(0),
3179                Instr::Await,
3180                Instr::StoreVar(0),
3181                Instr::Return,
3182            ],
3183            1,
3184        );
3185        let mut seed_vars = vec![Value::Num(0.0)];
3186        let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
3187        state.stack.push(Value::Num(9.0));
3188        state.vars = vec![Value::Num(0.0)];
3189        state
3190            .context
3191            .call_stack
3192            .push(crate::bytecode::program::CallFrame {
3193                function_name: "<local>".to_string(),
3194                return_address: 0,
3195                locals_start: 0,
3196                locals_count: 1,
3197                expected_outputs: 0,
3198            });
3199        state.context.locals = vec![Value::Num(0.0)];
3200
3201        let mut result_vars = vec![Value::Num(0.0)];
3202        let _ = block_on(run_interpreter_inner(state, &mut result_vars)).expect(
3203            "await should succeed when var alias is overwritten but local alias still carries the spawn task handle",
3204        );
3205        assert_eq!(result_vars[0], Value::Num(9.0));
3206    }
3207
3208    #[test]
3209    fn await_succeeds_after_scope_exit_when_var_alias_keeps_spawn_task_id_live() {
3210        let mut task = runmat_builtins::StructValue::new();
3211        task.fields.insert(
3212            "__runmat_spawn_kind".to_string(),
3213            Value::String("task".to_string()),
3214        );
3215        task.fields.insert(
3216            "__runmat_spawn_id".to_string(),
3217            Value::Int(runmat_builtins::IntValue::U64(23)),
3218        );
3219        task.fields
3220            .insert("__runmat_spawn_payload".to_string(), Value::Num(4.0));
3221        let task_value = Value::Struct(task);
3222
3223        let bytecode = Bytecode::with_instructions(
3224            vec![
3225                Instr::ExitScope(1),
3226                Instr::LoadVar(0),
3227                Instr::Await,
3228                Instr::Return,
3229            ],
3230            1,
3231        );
3232        let mut seed_vars = vec![task_value.clone()];
3233        let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
3234        state.context.locals.push(task_value);
3235        state.context.spawned_task_ids.insert(23);
3236        state.vars = seed_vars.clone();
3237
3238        let mut result_vars = seed_vars.clone();
3239        let _ = block_on(run_interpreter_inner(state, &mut result_vars))
3240            .expect("await should succeed when var alias keeps the spawn task id live");
3241        assert!(
3242            matches!(result_vars[0], Value::Struct(_)),
3243            "await in this sequence does not overwrite var0"
3244        );
3245    }
3246}