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