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