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