Skip to main content

runmat_vm/interpreter/
runner.rs

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