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::CreateClosure(_, _)
1246 | Instr::CreateSemanticClosure(_, _, _)
1247 | Instr::LoadStaticProperty(_, _)
1248 | Instr::LoadWorkspaceFirstStaticProperty { .. }
1249 | Instr::RegisterClass { .. }
1250 | Instr::CallFevalMulti(_, _)
1251 | Instr::CallFevalMultiUsingOutputSlot(_, _)
1252 | Instr::CallFevalExpandMultiOutput(_, _)
1253 | Instr::CallFevalExpandMultiOutputUsingOutputSlot(_, _)
1254 | Instr::CreateSemanticFuture(_, _, _)
1255 | Instr::CreateSemanticFutureExpandMultiOutput(_, _, _)
1256 | Instr::Spawn
1257 | Instr::Await
1258 | Instr::CallBuiltinMulti(_, _, _)
1259 | Instr::CallBuiltinMultiUsingOutputSlot(_, _, _)
1260 | Instr::CallSuperConstructorMulti { .. }
1261 | Instr::CallSuperMethodMulti { .. }
1262 | Instr::CallSemanticFunctionMulti(_, _, _)
1263 | Instr::CallSemanticFunctionMultiUsingOutputSlot(_, _, _)
1264 | Instr::CallSemanticNestedFunctionMulti { .. }
1265 | Instr::CallSemanticNestedFunctionMultiUsingOutputSlot { .. }
1266 | Instr::CallFunctionMulti { .. }
1267 | Instr::CallFunctionMultiUsingOutputSlot { .. }
1268 | Instr::CallFunctionExpandMultiOutput { .. }
1269 | Instr::CallWorkspaceFirstMulti { .. }
1270 | Instr::CallWorkspaceFirstMultiUsingOutputSlot { .. }
1271 | Instr::CallWorkspaceFirstExpandMultiOutput { .. }
1272 | Instr::CallWorkspaceFirstExpandMultiOutputUsingOutputSlot { .. }
1273 | Instr::CallSemanticFunctionExpandMultiOutput(_, _, _)
1274 | Instr::CallSemanticNestedFunctionExpandMultiOutput { .. }
1275 | Instr::CallBuiltinExpandMultiOutput(_, _, _)
1276 | Instr::CallSuperConstructorExpandMultiOutput { .. }
1277 | Instr::CallSuperMethodExpandMultiOutput { .. }
1278 | Instr::ExitScope(_)
1279 | Instr::RegisterImport { .. }
1280 | Instr::DeclareGlobal(_)
1281 | Instr::DeclareGlobalNamed(_, _)
1282 | Instr::DeclarePersistent(_)
1283 | Instr::DeclarePersistentNamed(_, _)
1284 | Instr::CreateCell2D(_, _)
1285 | Instr::CreateStructLiteral(_)
1286 | Instr::CreateObjectLiteral { .. }
1287 | Instr::Add
1288 | Instr::Sub
1289 | Instr::Mul
1290 | Instr::ElemMul
1291 | Instr::ElemDiv
1292 | Instr::ElemPow
1293 | Instr::ElemLeftDiv
1294 | Instr::Neg
1295 | Instr::UPlus
1296 | Instr::Transpose
1297 | Instr::ConjugateTranspose
1298 | Instr::Pow
1299 | Instr::RightDiv
1300 | Instr::LeftDiv
1301 | Instr::LessEqual
1302 | Instr::Less
1303 | Instr::Greater
1304 | Instr::GreaterEqual
1305 | Instr::Equal
1306 | Instr::NotEqual
1307 | Instr::LogicalNot
1308 | Instr::LogicalAnd
1309 | Instr::LogicalOr
1310 | Instr::Unpack(_)
1311 | Instr::CreateMatrix(_, _)
1312 | Instr::CreateMatrixDynamic(_)
1313 | Instr::CreateRange(_)
1314 | Instr::PackToRow(_)
1315 | Instr::PackToCol(_) => unreachable!("handled by dispatch_instruction"),
1316 Instr::StochasticEvolution => {
1317 let steps_value = stack
1318 .pop()
1319 .ok_or(mex("StackUnderflow", "stack underflow"))?;
1320 let scale_value = stack
1321 .pop()
1322 .ok_or(mex("StackUnderflow", "stack underflow"))?;
1323 let drift_value = stack
1324 .pop()
1325 .ok_or(mex("StackUnderflow", "stack underflow"))?;
1326 let state_value = stack
1327 .pop()
1328 .ok_or(mex("StackUnderflow", "stack underflow"))?;
1329 let evolved =
1330 crate::accel::idioms::stochastic_evolution::execute_stochastic_evolution(
1331 state_value,
1332 drift_value,
1333 scale_value,
1334 steps_value,
1335 )
1336 .await?;
1337 stack.push(evolved);
1338 }
1339 }
1340 if debug_stack {
1341 debug!(pc, stack_len = stack.len(), "[vm] after exec");
1342 }
1343 pc += 1;
1344 }
1345 interpreter_timing.flush_host_span("loop_complete", None);
1346 #[cfg(feature = "native-accel")]
1347 {
1348 let mut live_values = Vec::with_capacity(vars.len() + context.locals.len());
1349 live_values.extend(vars.iter().cloned());
1350 live_values.extend(context.locals.iter().cloned());
1351 let live_values = Value::OutputList(live_values);
1352 for value in &stack {
1353 accel_residency::clear_value_excluding(value, &live_values);
1354 }
1355 }
1356 sync_initial_vars(initial_vars, &vars);
1357 Ok(InterpreterOutcome::Completed(vars))
1358}
1359
1360pub async fn interpret(bytecode: &Bytecode) -> Result<Vec<Value>, RuntimeError> {
1361 let mut vars = vec![Value::Num(0.0); bytecode.var_count];
1362 match interpret_with_vars(bytecode, &mut vars, Some("<main>")).await {
1363 Ok(InterpreterOutcome::Completed(values)) => Ok(values),
1364 Err(e) => Err(e),
1365 }
1366}
1367
1368pub async fn interpret_function(
1369 bytecode: &Bytecode,
1370 vars: Vec<Value>,
1371) -> Result<Vec<Value>, RuntimeError> {
1372 interpret_function_with_counts(bytecode, vars, "<anonymous>", 0, 0, HashSet::new()).await
1373}
1374
1375pub async fn interpret_function_with_counts(
1376 bytecode: &Bytecode,
1377 vars: Vec<Value>,
1378 name: &str,
1379 out_count: usize,
1380 in_count: usize,
1381 missing_input_slots: HashSet<usize>,
1382) -> Result<Vec<Value>, RuntimeError> {
1383 let mut vars = vars;
1384 CALL_COUNTS.with(|cc| {
1385 cc.borrow_mut().push((in_count, out_count));
1386 });
1387 let call_counts = CALL_COUNTS.with(|cc| cc.borrow().clone());
1388 let mut state = InterpreterState::new(bytecode.clone(), &mut vars, Some(name), call_counts);
1389 state.missing_input_slots = missing_input_slots;
1390 let res = Box::pin(run_interpreter(Box::new(state), &mut vars)).await;
1391 CALL_COUNTS.with(|cc| {
1392 cc.borrow_mut().pop();
1393 });
1394 let res = match res {
1395 Ok(InterpreterOutcome::Completed(values)) => Ok(values),
1396 Err(e) => Err(e),
1397 }?;
1398 runtime_globals::persist_declared_for_bytecode(bytecode, name, &vars);
1399 Ok(res)
1400}
1401
1402#[cfg(test)]
1403mod tests {
1404 use super::{
1405 collect_semantic_outputs, interpret_with_vars, output_value, run_interpreter_inner,
1406 value_is_empty, value_is_greater_than, value_is_greater_than_or_equal, value_is_integer,
1407 value_is_less_than, value_is_less_than_or_equal, value_is_negative, value_is_nonnegative,
1408 value_is_nonpositive, value_is_nonzero, value_is_numeric_or_logical, value_is_positive,
1409 value_is_real, value_is_scalar_or_empty, value_is_text,
1410 };
1411 use crate::bytecode::program::{Bytecode, FunctionBytecode};
1412 use crate::bytecode::Instr;
1413 use crate::interpreter::api::InterpreterState;
1414 use futures::executor::block_on;
1415 use runmat_builtins::{
1416 CellArray, Closure, HandleRef, ObjectInstance, StructValue, Tensor, Value,
1417 };
1418 use runmat_hir::FunctionId;
1419 use std::collections::{HashMap, HashSet};
1420 use std::sync::{atomic::AtomicBool, Arc};
1421 #[cfg(feature = "native-accel")]
1422 use {
1423 once_cell::sync::Lazy,
1424 runmat_accelerate::simple_provider::InProcessProvider,
1425 runmat_accelerate_api::{AccelProvider, HostTensorView, ThreadProviderGuard},
1426 };
1427
1428 #[cfg(feature = "native-accel")]
1429 static TEST_PROVIDER: Lazy<InProcessProvider> = Lazy::new(InProcessProvider::new);
1430
1431 #[cfg(feature = "native-accel")]
1432 fn upload_provider_handle(
1433 data: Vec<f64>,
1434 shape: Vec<usize>,
1435 ) -> runmat_accelerate_api::GpuTensorHandle {
1436 TEST_PROVIDER
1437 .upload(&HostTensorView {
1438 data: &data,
1439 shape: &shape,
1440 })
1441 .expect("upload should succeed")
1442 }
1443
1444 fn test_function(varargout_slot: Option<usize>) -> FunctionBytecode {
1445 FunctionBytecode {
1446 function: FunctionId(0),
1447 display_name: "f".into(),
1448 private_owner_scope: String::new(),
1449 source_id: None,
1450 instructions: vec![Instr::Return],
1451 instr_spans: Vec::new(),
1452 call_arg_spans: Vec::new(),
1453 var_count: 1,
1454 input_slots: Vec::new(),
1455 varargin_slot: None,
1456 implicit_nargin_slot: None,
1457 output_slots: Vec::new(),
1458 varargout_slot,
1459 implicit_nargout_slot: None,
1460 capture_slots: Vec::new(),
1461 var_names: HashMap::new(),
1462 initially_unassigned_slots: HashSet::new(),
1463 argument_validations: Vec::new(),
1464 }
1465 }
1466
1467 #[test]
1468 fn collect_outputs_zero_requested_does_not_consume_varargout() {
1469 let func = test_function(Some(0));
1470 let varargout = CellArray::new(vec![Value::Num(7.0)], 1, 1).expect("cell");
1471 let result_vars = vec![Value::Cell(varargout)];
1472 let outputs = collect_semantic_outputs(&func, &result_vars, 0).expect("collect");
1473 assert!(outputs.is_empty());
1474 }
1475
1476 #[test]
1477 fn collect_outputs_one_requested_reads_varargout() {
1478 let func = test_function(Some(0));
1479 let varargout = CellArray::new(vec![Value::Num(7.0)], 1, 1).expect("cell");
1480 let result_vars = vec![Value::Cell(varargout)];
1481 let outputs = collect_semantic_outputs(&func, &result_vars, 1).expect("collect");
1482 assert_eq!(outputs, vec![Value::Num(7.0)]);
1483 }
1484
1485 #[test]
1486 fn output_value_zero_requested_is_empty_output_list() {
1487 let value = output_value(vec![Value::Num(1.0)], 0);
1488 assert_eq!(value, Value::OutputList(Vec::new()));
1489 }
1490
1491 #[test]
1492 fn output_value_multi_requested_returns_output_list() {
1493 let value = output_value(vec![Value::Num(1.0), Value::Num(2.0)], 2);
1494 assert_eq!(
1495 value,
1496 Value::OutputList(vec![Value::Num(1.0), Value::Num(2.0)])
1497 );
1498 }
1499
1500 #[test]
1501 fn numeric_or_logical_validator_accepts_expected_domains() {
1502 assert!(value_is_numeric_or_logical(&Value::Num(1.0)));
1503 assert!(value_is_numeric_or_logical(&Value::Bool(true)));
1504 assert!(value_is_numeric_or_logical(&Value::Complex(1.0, 2.0)));
1505 let tensor = Tensor::new(vec![1.0, 2.0], vec![1, 2]).expect("tensor");
1506 assert!(value_is_numeric_or_logical(&Value::Tensor(tensor)));
1507 assert!(!value_is_numeric_or_logical(&Value::String(
1508 "x".to_string()
1509 )));
1510 assert!(!value_is_numeric_or_logical(&Value::CharArray(
1511 runmat_builtins::CharArray::new("x".chars().collect(), 1, 1).expect("char")
1512 )));
1513 }
1514
1515 #[test]
1516 fn text_validator_accepts_string_char_vector_and_cellstr() {
1517 assert!(value_is_text(&Value::String("x".to_string())));
1518 assert!(value_is_text(&Value::CharArray(
1519 runmat_builtins::CharArray::new("abc".chars().collect(), 1, 3).expect("char")
1520 )));
1521 assert!(value_is_text(&Value::Cell(
1522 CellArray::new(
1523 vec![
1524 Value::CharArray(
1525 runmat_builtins::CharArray::new("a".chars().collect(), 1, 1).expect("char"),
1526 ),
1527 Value::String("b".to_string()),
1528 ],
1529 1,
1530 2,
1531 )
1532 .expect("cell"),
1533 )));
1534 assert!(!value_is_text(&Value::Num(1.0)));
1535 }
1536
1537 #[test]
1538 fn nonempty_validator_rejects_empty_arrays_and_cells() {
1539 let empty_num = Tensor::new(Vec::new(), vec![0, 0]).expect("empty tensor");
1540 assert!(value_is_empty(&Value::Tensor(empty_num)));
1541 let empty_char =
1542 runmat_builtins::CharArray::new(Vec::new(), 1, 0).expect("empty char array");
1543 assert!(value_is_empty(&Value::CharArray(empty_char)));
1544 let empty_cell = CellArray::new(Vec::new(), 0, 0).expect("empty cell");
1545 assert!(value_is_empty(&Value::Cell(empty_cell)));
1546 assert!(!value_is_empty(&Value::String("".to_string())));
1547 assert!(!value_is_empty(&Value::Num(1.0)));
1548 }
1549
1550 #[test]
1551 fn scalar_or_empty_validator_accepts_scalar_or_empty_shapes() {
1552 assert!(value_is_scalar_or_empty(&Value::Num(1.0)));
1553 assert!(value_is_scalar_or_empty(&Value::Bool(true)));
1554 let empty_num = Tensor::new(Vec::new(), vec![0, 0]).expect("empty tensor");
1555 assert!(value_is_scalar_or_empty(&Value::Tensor(empty_num)));
1556 let matrix = Tensor::new(vec![1.0, 2.0], vec![1, 2]).expect("matrix");
1557 assert!(!value_is_scalar_or_empty(&Value::Tensor(matrix)));
1558 }
1559
1560 #[test]
1561 fn real_validator_rejects_imaginary_values() {
1562 assert!(value_is_real(&Value::Num(1.0)));
1563 assert!(value_is_real(&Value::Complex(1.0, 0.0)));
1564 assert!(!value_is_real(&Value::Complex(1.0, 2.0)));
1565 let complex_real = runmat_builtins::ComplexTensor::new(vec![(1.0, 0.0)], vec![1, 1])
1566 .expect("complex tensor");
1567 let complex_imag = runmat_builtins::ComplexTensor::new(vec![(1.0, 2.0)], vec![1, 1])
1568 .expect("complex tensor");
1569 assert!(value_is_real(&Value::ComplexTensor(complex_real)));
1570 assert!(!value_is_real(&Value::ComplexTensor(complex_imag)));
1571 }
1572
1573 #[test]
1574 fn integer_validator_accepts_integer_valued_numeric_inputs() {
1575 assert!(value_is_integer(&Value::Int(
1576 runmat_builtins::IntValue::I64(3)
1577 )));
1578 assert!(value_is_integer(&Value::Num(3.0)));
1579 assert!(!value_is_integer(&Value::Num(3.5)));
1580 let tensor = Tensor::new(vec![1.0, 2.0], vec![1, 2]).expect("tensor");
1581 assert!(value_is_integer(&Value::Tensor(tensor)));
1582 let non_integer = Tensor::new(vec![1.0, 2.5], vec![1, 2]).expect("tensor");
1583 assert!(!value_is_integer(&Value::Tensor(non_integer)));
1584 assert!(!value_is_integer(&Value::Bool(true)));
1585 }
1586
1587 #[test]
1588 fn positive_validator_rejects_zero_and_negative_values() {
1589 assert!(value_is_positive(&Value::Num(1.0)));
1590 assert!(!value_is_positive(&Value::Num(0.0)));
1591 assert!(!value_is_positive(&Value::Num(-1.0)));
1592 assert!(value_is_positive(&Value::Int(
1593 runmat_builtins::IntValue::I64(2)
1594 )));
1595 assert!(!value_is_positive(&Value::Int(
1596 runmat_builtins::IntValue::I64(0)
1597 )));
1598 let positive = Tensor::new(vec![1.0, 2.0], vec![1, 2]).expect("tensor");
1599 assert!(value_is_positive(&Value::Tensor(positive)));
1600 let mixed = Tensor::new(vec![1.0, 0.0], vec![1, 2]).expect("tensor");
1601 assert!(!value_is_positive(&Value::Tensor(mixed)));
1602 }
1603
1604 #[test]
1605 fn negative_validator_rejects_zero_and_positive_values() {
1606 assert!(value_is_negative(&Value::Num(-1.0)));
1607 assert!(!value_is_negative(&Value::Num(0.0)));
1608 assert!(!value_is_negative(&Value::Num(1.0)));
1609 assert!(value_is_negative(&Value::Int(
1610 runmat_builtins::IntValue::I64(-2)
1611 )));
1612 let ok = Tensor::new(vec![-1.0, -2.0], vec![1, 2]).expect("tensor");
1613 assert!(value_is_negative(&Value::Tensor(ok)));
1614 let bad = Tensor::new(vec![-1.0, 0.0], vec![1, 2]).expect("tensor");
1615 assert!(!value_is_negative(&Value::Tensor(bad)));
1616 }
1617
1618 #[test]
1619 fn nonnegative_validator_accepts_zero_and_positive_values() {
1620 assert!(value_is_nonnegative(&Value::Num(0.0)));
1621 assert!(value_is_nonnegative(&Value::Num(2.0)));
1622 assert!(!value_is_nonnegative(&Value::Num(-1.0)));
1623 assert!(value_is_nonnegative(&Value::Int(
1624 runmat_builtins::IntValue::I64(0)
1625 )));
1626 let ok = Tensor::new(vec![0.0, 1.0], vec![1, 2]).expect("tensor");
1627 assert!(value_is_nonnegative(&Value::Tensor(ok)));
1628 let bad = Tensor::new(vec![0.0, -1.0], vec![1, 2]).expect("tensor");
1629 assert!(!value_is_nonnegative(&Value::Tensor(bad)));
1630 }
1631
1632 #[test]
1633 fn nonzero_validator_rejects_zero_values() {
1634 assert!(value_is_nonzero(&Value::Num(1.0)));
1635 assert!(!value_is_nonzero(&Value::Num(0.0)));
1636 assert!(value_is_nonzero(&Value::Int(
1637 runmat_builtins::IntValue::I64(2)
1638 )));
1639 assert!(!value_is_nonzero(&Value::Int(
1640 runmat_builtins::IntValue::I64(0)
1641 )));
1642 assert!(value_is_nonzero(&Value::Complex(0.0, 1.0)));
1643 assert!(!value_is_nonzero(&Value::Complex(0.0, 0.0)));
1644 let ok = Tensor::new(vec![1.0, 2.0], vec![1, 2]).expect("tensor");
1645 assert!(value_is_nonzero(&Value::Tensor(ok)));
1646 let bad = Tensor::new(vec![1.0, 0.0], vec![1, 2]).expect("tensor");
1647 assert!(!value_is_nonzero(&Value::Tensor(bad)));
1648 }
1649
1650 #[test]
1651 fn nonpositive_validator_accepts_zero_and_negative_values() {
1652 assert!(value_is_nonpositive(&Value::Num(0.0)));
1653 assert!(value_is_nonpositive(&Value::Num(-2.0)));
1654 assert!(!value_is_nonpositive(&Value::Num(1.0)));
1655 assert!(value_is_nonpositive(&Value::Int(
1656 runmat_builtins::IntValue::I64(0)
1657 )));
1658 let ok = Tensor::new(vec![0.0, -1.0], vec![1, 2]).expect("tensor");
1659 assert!(value_is_nonpositive(&Value::Tensor(ok)));
1660 let bad = Tensor::new(vec![0.0, 1.0], vec![1, 2]).expect("tensor");
1661 assert!(!value_is_nonpositive(&Value::Tensor(bad)));
1662 }
1663
1664 #[test]
1665 fn greater_than_or_equal_validator_uses_numeric_threshold() {
1666 assert!(value_is_greater_than_or_equal(&Value::Num(2.0), 0.0));
1667 assert!(value_is_greater_than_or_equal(&Value::Num(0.0), 0.0));
1668 assert!(!value_is_greater_than_or_equal(&Value::Num(-1.0), 0.0));
1669 }
1670
1671 #[test]
1672 fn less_than_or_equal_validator_uses_numeric_threshold() {
1673 assert!(value_is_less_than_or_equal(&Value::Num(-1.0), 0.0));
1674 assert!(value_is_less_than_or_equal(&Value::Num(0.0), 0.0));
1675 assert!(!value_is_less_than_or_equal(&Value::Num(1.0), 0.0));
1676 }
1677
1678 #[test]
1679 fn greater_than_and_less_than_validators_use_numeric_threshold() {
1680 assert!(value_is_greater_than(&Value::Num(2.0), 1.0));
1681 assert!(!value_is_greater_than(&Value::Num(1.0), 1.0));
1682 assert!(value_is_less_than(&Value::Num(-2.0), -1.0));
1683 assert!(!value_is_less_than(&Value::Num(-1.0), -1.0));
1684 }
1685
1686 #[cfg(feature = "native-accel")]
1687 #[test]
1688 fn cancellation_clears_gpu_residency_for_live_values() {
1689 use runmat_accelerate::fusion_residency;
1690 use runmat_accelerate_api::GpuTensorHandle;
1691
1692 let handle = GpuTensorHandle {
1693 shape: vec![1, 1],
1694 device_id: 0,
1695 buffer_id: 777_001,
1696 };
1697 fusion_residency::mark(&handle);
1698 assert!(fusion_residency::is_resident(&handle));
1699
1700 let mut vars = vec![Value::GpuTensor(handle.clone())];
1701 let bytecode = Bytecode::with_instructions(vec![Instr::Return], vars.len());
1702 let cancelled = Arc::new(AtomicBool::new(true));
1703 let _interrupt_guard = runmat_runtime::interrupt::replace_interrupt(Some(cancelled));
1704
1705 let err = block_on(interpret_with_vars(&bytecode, &mut vars, Some("<main>")))
1706 .expect_err("cancelled execution should return error");
1707 assert_eq!(err.identifier(), Some("RunMat:ExecutionCancelled"));
1708 assert!(
1709 !fusion_residency::is_resident(&handle),
1710 "cancelled execution should clear residency marks for live GPU handles"
1711 );
1712 }
1713
1714 #[cfg(feature = "native-accel")]
1715 #[test]
1716 fn completion_clears_stack_only_gpu_residency() {
1717 use runmat_accelerate::fusion_residency;
1718 use runmat_accelerate_api::GpuTensorHandle;
1719
1720 let handle = GpuTensorHandle {
1721 shape: vec![1, 1],
1722 device_id: 0,
1723 buffer_id: 777_002,
1724 };
1725 fusion_residency::mark(&handle);
1726 assert!(fusion_residency::is_resident(&handle));
1727
1728 let bytecode = Bytecode::with_instructions(Vec::new(), 1);
1729 let mut seed_vars = vec![Value::Num(0.0)];
1730 let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
1731 state.stack.push(Value::GpuTensor(handle.clone()));
1732 state.vars = vec![Value::Num(0.0)];
1733
1734 let mut result_vars = vec![Value::Num(0.0)];
1735 let outcome = block_on(run_interpreter_inner(state, &mut result_vars))
1736 .expect("interpreter should complete");
1737 assert!(matches!(
1738 outcome,
1739 crate::interpreter::api::InterpreterOutcome::Completed(_)
1740 ));
1741 assert!(
1742 !fusion_residency::is_resident(&handle),
1743 "completion should clear residency marks for stack-only GPU handles"
1744 );
1745 }
1746
1747 #[cfg(feature = "native-accel")]
1748 #[test]
1749 fn pop_releases_stack_only_provider_handle() {
1750 use runmat_accelerate::fusion_residency;
1751
1752 let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
1753 let handle = upload_provider_handle(vec![9.0], vec![1]);
1754 assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
1755 fusion_residency::mark(&handle);
1756
1757 let bytecode = Bytecode::with_instructions(vec![Instr::Pop, Instr::Return], 1);
1758 let mut seed_vars = vec![Value::Num(0.0)];
1759 let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
1760 state.stack.push(Value::GpuTensor(handle.clone()));
1761 state.vars = vec![Value::Num(0.0)];
1762
1763 let mut result_vars = vec![Value::Num(0.0)];
1764 let _ = block_on(run_interpreter_inner(state, &mut result_vars))
1765 .expect("interpreter should complete");
1766 assert!(
1767 !fusion_residency::is_resident(&handle),
1768 "pop should clear residency for stack-only handles"
1769 );
1770 assert!(
1771 block_on(TEST_PROVIDER.download(&handle)).is_err(),
1772 "pop should release provider storage for stack-only handles"
1773 );
1774 }
1775
1776 #[cfg(feature = "native-accel")]
1777 #[test]
1778 fn pop_preserves_provider_handle_when_still_live_in_vars() {
1779 use runmat_accelerate::fusion_residency;
1780
1781 let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
1782 let handle = upload_provider_handle(vec![11.0], vec![1]);
1783 assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
1784 fusion_residency::mark(&handle);
1785
1786 let bytecode = Bytecode::with_instructions(vec![Instr::Pop, Instr::Return], 1);
1787 let mut seed_vars = vec![Value::GpuTensor(handle.clone())];
1788 let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
1789 state.stack.push(Value::GpuTensor(handle.clone()));
1790 state.vars = vec![Value::GpuTensor(handle.clone())];
1791
1792 let mut result_vars = vec![Value::GpuTensor(handle.clone())];
1793 let _ = block_on(run_interpreter_inner(state, &mut result_vars))
1794 .expect("interpreter should complete");
1795 assert!(
1796 fusion_residency::is_resident(&handle),
1797 "pop should preserve residency for handles still referenced by vars"
1798 );
1799 assert!(
1800 block_on(TEST_PROVIDER.download(&handle)).is_ok(),
1801 "pop should not release provider storage for handles still referenced by vars"
1802 );
1803 fusion_residency::clear(&handle);
1804 let _ = TEST_PROVIDER.free(&handle);
1805 }
1806
1807 #[cfg(feature = "native-accel")]
1808 #[test]
1809 fn exit_scope_releases_local_only_provider_handle() {
1810 use runmat_accelerate::fusion_residency;
1811
1812 let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
1813 let handle = upload_provider_handle(vec![15.0], vec![1]);
1814 assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
1815 fusion_residency::mark(&handle);
1816
1817 let bytecode = Bytecode::with_instructions(vec![Instr::ExitScope(1), Instr::Return], 1);
1818 let mut seed_vars = vec![Value::Num(0.0)];
1819 let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
1820 state.context.locals.push(Value::GpuTensor(handle.clone()));
1821 state.vars = vec![Value::Num(0.0)];
1822
1823 let mut result_vars = vec![Value::Num(0.0)];
1824 let _ = block_on(run_interpreter_inner(state, &mut result_vars))
1825 .expect("exit scope should complete");
1826 assert!(
1827 !fusion_residency::is_resident(&handle),
1828 "exit scope should clear residency for local-only handles"
1829 );
1830 assert!(
1831 block_on(TEST_PROVIDER.download(&handle)).is_err(),
1832 "exit scope should release provider storage for local-only handles"
1833 );
1834 }
1835
1836 #[cfg(feature = "native-accel")]
1837 #[test]
1838 fn exit_scope_preserves_provider_handle_when_still_live_in_vars() {
1839 use runmat_accelerate::fusion_residency;
1840
1841 let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
1842 let handle = upload_provider_handle(vec![17.0], vec![1]);
1843 assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
1844 fusion_residency::mark(&handle);
1845
1846 let bytecode = Bytecode::with_instructions(vec![Instr::ExitScope(1), Instr::Return], 1);
1847 let mut seed_vars = vec![Value::GpuTensor(handle.clone())];
1848 let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
1849 state.context.locals.push(Value::GpuTensor(handle.clone()));
1850 state.vars = vec![Value::GpuTensor(handle.clone())];
1851
1852 let mut result_vars = vec![Value::GpuTensor(handle.clone())];
1853 let _ = block_on(run_interpreter_inner(state, &mut result_vars))
1854 .expect("exit scope should complete");
1855 assert!(
1856 fusion_residency::is_resident(&handle),
1857 "exit scope should preserve residency for handles still referenced by vars"
1858 );
1859 assert!(
1860 block_on(TEST_PROVIDER.download(&handle)).is_ok(),
1861 "exit scope should not release provider storage for handles still referenced by vars"
1862 );
1863 fusion_residency::clear(&handle);
1864 let _ = TEST_PROVIDER.free(&handle);
1865 }
1866
1867 #[cfg(feature = "native-accel")]
1868 #[test]
1869 fn exit_scope_releases_nested_handle_object_local_provider_handle() {
1870 use runmat_accelerate::fusion_residency;
1871
1872 let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
1873 let handle = upload_provider_handle(vec![18.0], vec![1]);
1874 assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
1875 fusion_residency::mark(&handle);
1876
1877 let bytecode = Bytecode::with_instructions(vec![Instr::ExitScope(1), Instr::Return], 1);
1878 let mut seed_vars = vec![Value::Num(0.0)];
1879 let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
1880 let mut payload = StructValue::new();
1881 payload
1882 .fields
1883 .insert("nested".to_string(), Value::GpuTensor(handle.clone()));
1884 let target = runmat_gc::gc_allocate(Value::Struct(payload)).expect("gc allocate payload");
1885 state.context.locals.push(Value::HandleObject(HandleRef {
1886 class_name: "Payload".to_string(),
1887 target,
1888 valid: true,
1889 }));
1890 state.vars = vec![Value::Num(0.0)];
1891
1892 let mut result_vars = vec![Value::Num(0.0)];
1893 let _ = block_on(run_interpreter_inner(state, &mut result_vars))
1894 .expect("exit scope should complete for nested handle-object local");
1895 assert!(
1896 !fusion_residency::is_resident(&handle),
1897 "exit scope should clear residency for nested handle-object local-only handles"
1898 );
1899 assert!(
1900 block_on(TEST_PROVIDER.download(&handle)).is_err(),
1901 "exit scope should release provider storage for nested handle-object local-only handles"
1902 );
1903 }
1904
1905 #[cfg(feature = "native-accel")]
1906 #[test]
1907 fn exit_scope_preserves_nested_handle_object_provider_handle_when_still_live_in_vars() {
1908 use runmat_accelerate::fusion_residency;
1909
1910 let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
1911 let handle = upload_provider_handle(vec![20.0], vec![1]);
1912 assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
1913 fusion_residency::mark(&handle);
1914
1915 let bytecode = Bytecode::with_instructions(vec![Instr::ExitScope(1), Instr::Return], 1);
1916 let mut seed_vars = vec![Value::Num(0.0)];
1917 let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
1918 let mut payload = StructValue::new();
1919 payload
1920 .fields
1921 .insert("nested".to_string(), Value::GpuTensor(handle.clone()));
1922 let target = runmat_gc::gc_allocate(Value::Struct(payload)).expect("gc allocate payload");
1923 let local_value = Value::HandleObject(HandleRef {
1924 class_name: "Payload".to_string(),
1925 target,
1926 valid: true,
1927 });
1928 state.context.locals.push(local_value.clone());
1929 state.vars = vec![local_value.clone()];
1930
1931 let mut result_vars = vec![local_value];
1932 let _ = block_on(run_interpreter_inner(state, &mut result_vars))
1933 .expect("exit scope should complete for aliased nested handle-object local");
1934 assert!(
1935 fusion_residency::is_resident(&handle),
1936 "exit scope should preserve residency for nested handle-object handles still referenced by vars"
1937 );
1938 assert!(
1939 block_on(TEST_PROVIDER.download(&handle)).is_ok(),
1940 "exit scope should not release provider storage for nested handle-object handles still referenced by vars"
1941 );
1942 fusion_residency::clear(&handle);
1943 let _ = TEST_PROVIDER.free(&handle);
1944 }
1945
1946 #[cfg(feature = "native-accel")]
1947 #[test]
1948 fn store_var_overwrite_preserves_provider_handle_when_shared_in_other_var() {
1949 use runmat_accelerate::fusion_residency;
1950
1951 let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
1952 let handle = upload_provider_handle(vec![19.0], vec![1]);
1953 assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
1954 fusion_residency::mark(&handle);
1955
1956 let bytecode = Bytecode::with_instructions(vec![Instr::StoreVar(0), Instr::Return], 2);
1957 let mut seed_vars = vec![
1958 Value::GpuTensor(handle.clone()),
1959 Value::GpuTensor(handle.clone()),
1960 ];
1961 let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
1962 state.stack.push(Value::Num(0.0));
1963 state.vars = vec![
1964 Value::GpuTensor(handle.clone()),
1965 Value::GpuTensor(handle.clone()),
1966 ];
1967
1968 let mut result_vars = state.vars.clone();
1969 let _ = block_on(run_interpreter_inner(state, &mut result_vars))
1970 .expect("store var should complete");
1971 assert!(
1972 fusion_residency::is_resident(&handle),
1973 "store var overwrite should preserve residency for handles still live in other vars"
1974 );
1975 assert!(
1976 block_on(TEST_PROVIDER.download(&handle)).is_ok(),
1977 "store var overwrite should not release provider storage for handles still live in other vars"
1978 );
1979 fusion_residency::clear(&handle);
1980 let _ = TEST_PROVIDER.free(&handle);
1981 }
1982
1983 #[cfg(feature = "native-accel")]
1984 #[test]
1985 fn store_var_overwrite_preserves_provider_handle_when_shared_in_local() {
1986 use runmat_accelerate::fusion_residency;
1987
1988 let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
1989 let handle = upload_provider_handle(vec![20.0], vec![1]);
1990 assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
1991 fusion_residency::mark(&handle);
1992
1993 let bytecode = Bytecode::with_instructions(vec![Instr::StoreVar(0), Instr::Return], 1);
1994 let mut seed_vars = vec![Value::GpuTensor(handle.clone())];
1995 let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
1996 state.stack.push(Value::Num(0.0));
1997 state.vars = vec![Value::GpuTensor(handle.clone())];
1998 state.context.locals.push(Value::GpuTensor(handle.clone()));
1999
2000 let mut result_vars = state.vars.clone();
2001 let _ = block_on(run_interpreter_inner(state, &mut result_vars))
2002 .expect("store var should complete when alias lives in locals");
2003 assert!(
2004 fusion_residency::is_resident(&handle),
2005 "store var overwrite should preserve residency for handles still live in locals"
2006 );
2007 assert!(
2008 block_on(TEST_PROVIDER.download(&handle)).is_ok(),
2009 "store var overwrite should not release provider storage for handles still live in locals"
2010 );
2011 fusion_residency::clear(&handle);
2012 let _ = TEST_PROVIDER.free(&handle);
2013 }
2014
2015 #[cfg(feature = "native-accel")]
2016 #[test]
2017 fn store_var_overwrite_releases_nested_handle_object_provider_handle_when_unaliased() {
2018 use runmat_accelerate::fusion_residency;
2019
2020 let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
2021 let handle = upload_provider_handle(vec![22.0], vec![1]);
2022 assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
2023 fusion_residency::mark(&handle);
2024
2025 let bytecode = Bytecode::with_instructions(vec![Instr::StoreVar(0), Instr::Return], 1);
2026 let mut seed_vars = vec![Value::Num(0.0)];
2027 let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
2028 let mut payload = StructValue::new();
2029 payload
2030 .fields
2031 .insert("nested".to_string(), Value::GpuTensor(handle.clone()));
2032 let target = runmat_gc::gc_allocate(Value::Struct(payload)).expect("gc allocate payload");
2033 state.vars = vec![Value::HandleObject(HandleRef {
2034 class_name: "Payload".to_string(),
2035 target,
2036 valid: true,
2037 })];
2038 state.stack.push(Value::Num(0.0));
2039
2040 let mut result_vars = state.vars.clone();
2041 let _ = block_on(run_interpreter_inner(state, &mut result_vars))
2042 .expect("store var overwrite should complete for nested handle-object value");
2043 assert!(
2044 !fusion_residency::is_resident(&handle),
2045 "store var overwrite should clear residency for nested handle-object handles when unaliased"
2046 );
2047 assert!(
2048 block_on(TEST_PROVIDER.download(&handle)).is_err(),
2049 "store var overwrite should release provider storage for nested handle-object handles when unaliased"
2050 );
2051 }
2052
2053 #[cfg(feature = "native-accel")]
2054 #[test]
2055 fn store_var_overwrite_preserves_nested_handle_object_provider_handle_when_shared_in_other_var()
2056 {
2057 use runmat_accelerate::fusion_residency;
2058
2059 let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
2060 let handle = upload_provider_handle(vec![24.0], vec![1]);
2061 assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
2062 fusion_residency::mark(&handle);
2063
2064 let bytecode = Bytecode::with_instructions(vec![Instr::StoreVar(0), Instr::Return], 2);
2065 let mut seed_vars = vec![Value::Num(0.0), Value::Num(0.0)];
2066 let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
2067 let mut payload = StructValue::new();
2068 payload
2069 .fields
2070 .insert("nested".to_string(), Value::GpuTensor(handle.clone()));
2071 let target = runmat_gc::gc_allocate(Value::Struct(payload)).expect("gc allocate payload");
2072 let nested = Value::HandleObject(HandleRef {
2073 class_name: "Payload".to_string(),
2074 target,
2075 valid: true,
2076 });
2077 state.vars = vec![nested.clone(), nested.clone()];
2078 state.stack.push(Value::Num(0.0));
2079
2080 let mut result_vars = state.vars.clone();
2081 let _ = block_on(run_interpreter_inner(state, &mut result_vars))
2082 .expect("store var overwrite should complete for aliased nested handle-object values");
2083 assert!(
2084 fusion_residency::is_resident(&handle),
2085 "store var overwrite should preserve residency for nested handle-object handles still live in other vars"
2086 );
2087 assert!(
2088 block_on(TEST_PROVIDER.download(&handle)).is_ok(),
2089 "store var overwrite should not release provider storage for nested handle-object handles still live in other vars"
2090 );
2091 fusion_residency::clear(&handle);
2092 let _ = TEST_PROVIDER.free(&handle);
2093 }
2094
2095 #[cfg(feature = "native-accel")]
2096 #[test]
2097 fn store_var_overwrite_preserves_nested_handle_object_provider_handle_when_shared_in_local() {
2098 use runmat_accelerate::fusion_residency;
2099
2100 let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
2101 let handle = upload_provider_handle(vec![27.0], vec![1]);
2102 assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
2103 fusion_residency::mark(&handle);
2104
2105 let bytecode = Bytecode::with_instructions(vec![Instr::StoreVar(0), Instr::Return], 1);
2106 let mut seed_vars = vec![Value::Num(0.0)];
2107 let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
2108 let mut payload = StructValue::new();
2109 payload
2110 .fields
2111 .insert("nested".to_string(), Value::GpuTensor(handle.clone()));
2112 let target = runmat_gc::gc_allocate(Value::Struct(payload)).expect("gc allocate payload");
2113 let nested = Value::HandleObject(HandleRef {
2114 class_name: "Payload".to_string(),
2115 target,
2116 valid: true,
2117 });
2118 state.vars = vec![nested.clone()];
2119 state.stack.push(Value::Num(0.0));
2120 state.context.locals.push(nested);
2121
2122 let mut result_vars = state.vars.clone();
2123 let _ = block_on(run_interpreter_inner(state, &mut result_vars))
2124 .expect("store var overwrite should complete when alias lives in locals");
2125 assert!(
2126 fusion_residency::is_resident(&handle),
2127 "store var overwrite should preserve residency for nested handle-object handles still live in locals"
2128 );
2129 assert!(
2130 block_on(TEST_PROVIDER.download(&handle)).is_ok(),
2131 "store var overwrite should not release provider storage for nested handle-object handles still live in locals"
2132 );
2133 fusion_residency::clear(&handle);
2134 let _ = TEST_PROVIDER.free(&handle);
2135 }
2136
2137 #[cfg(feature = "native-accel")]
2138 #[test]
2139 fn store_local_overwrite_preserves_provider_handle_when_shared_in_var() {
2140 use runmat_accelerate::fusion_residency;
2141
2142 let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
2143 let handle = upload_provider_handle(vec![23.0], vec![1]);
2144 assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
2145 fusion_residency::mark(&handle);
2146
2147 let bytecode = Bytecode::with_instructions(vec![Instr::StoreLocal(0), Instr::Return], 1);
2148 let mut seed_vars = vec![Value::GpuTensor(handle.clone())];
2149 let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
2150 state.stack.push(Value::Num(0.0));
2151 state.vars = vec![Value::GpuTensor(handle.clone())];
2152 state
2153 .context
2154 .call_stack
2155 .push(crate::bytecode::program::CallFrame {
2156 function_name: "<local>".to_string(),
2157 return_address: 0,
2158 locals_start: 0,
2159 locals_count: 1,
2160 expected_outputs: 0,
2161 });
2162 state.context.locals.push(Value::GpuTensor(handle.clone()));
2163
2164 let mut result_vars = state.vars.clone();
2165 let _ = block_on(run_interpreter_inner(state, &mut result_vars))
2166 .expect("store local should complete");
2167 assert!(
2168 fusion_residency::is_resident(&handle),
2169 "store local overwrite should preserve residency for handles still live in vars"
2170 );
2171 assert!(
2172 block_on(TEST_PROVIDER.download(&handle)).is_ok(),
2173 "store local overwrite should not release provider storage for handles still live in vars"
2174 );
2175 fusion_residency::clear(&handle);
2176 let _ = TEST_PROVIDER.free(&handle);
2177 }
2178
2179 #[cfg(feature = "native-accel")]
2180 #[test]
2181 fn store_local_overwrite_preserves_provider_handle_when_shared_in_other_local() {
2182 use runmat_accelerate::fusion_residency;
2183
2184 let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
2185 let handle = upload_provider_handle(vec![24.0], vec![1]);
2186 assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
2187 fusion_residency::mark(&handle);
2188
2189 let bytecode = Bytecode::with_instructions(vec![Instr::StoreLocal(0), Instr::Return], 1);
2190 let mut seed_vars = vec![Value::Num(0.0)];
2191 let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
2192 state.stack.push(Value::Num(0.0));
2193 state.vars = vec![Value::Num(0.0)];
2194 state
2195 .context
2196 .call_stack
2197 .push(crate::bytecode::program::CallFrame {
2198 function_name: "<local>".to_string(),
2199 return_address: 0,
2200 locals_start: 0,
2201 locals_count: 2,
2202 expected_outputs: 0,
2203 });
2204 state.context.locals.push(Value::GpuTensor(handle.clone()));
2205 state.context.locals.push(Value::GpuTensor(handle.clone()));
2206
2207 let mut result_vars = state.vars.clone();
2208 let _ = block_on(run_interpreter_inner(state, &mut result_vars))
2209 .expect("store local should complete when alias lives in other local");
2210 assert!(
2211 fusion_residency::is_resident(&handle),
2212 "store local overwrite should preserve residency for handles still live in other locals"
2213 );
2214 assert!(
2215 block_on(TEST_PROVIDER.download(&handle)).is_ok(),
2216 "store local overwrite should not release provider storage for handles still live in other locals"
2217 );
2218 fusion_residency::clear(&handle);
2219 let _ = TEST_PROVIDER.free(&handle);
2220 }
2221
2222 #[cfg(feature = "native-accel")]
2223 #[test]
2224 fn store_local_overwrite_releases_provider_handle_when_unaliased() {
2225 use runmat_accelerate::fusion_residency;
2226
2227 let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
2228 let handle = upload_provider_handle(vec![25.0], vec![1]);
2229 assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
2230 fusion_residency::mark(&handle);
2231
2232 let bytecode = Bytecode::with_instructions(vec![Instr::StoreLocal(0), Instr::Return], 1);
2233 let mut seed_vars = vec![Value::Num(0.0)];
2234 let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
2235 state.stack.push(Value::Num(0.0));
2236 state.vars = vec![Value::Num(0.0)];
2237 state
2238 .context
2239 .call_stack
2240 .push(crate::bytecode::program::CallFrame {
2241 function_name: "<local>".to_string(),
2242 return_address: 0,
2243 locals_start: 0,
2244 locals_count: 1,
2245 expected_outputs: 0,
2246 });
2247 state.context.locals.push(Value::GpuTensor(handle.clone()));
2248
2249 let mut result_vars = state.vars.clone();
2250 let _ = block_on(run_interpreter_inner(state, &mut result_vars))
2251 .expect("store local overwrite should complete");
2252 assert!(
2253 !fusion_residency::is_resident(&handle),
2254 "store local overwrite should clear residency for unaliased local handles"
2255 );
2256 assert!(
2257 block_on(TEST_PROVIDER.download(&handle)).is_err(),
2258 "store local overwrite should release provider storage for unaliased local handles"
2259 );
2260 }
2261
2262 #[cfg(feature = "native-accel")]
2263 #[test]
2264 fn store_local_overwrite_releases_nested_handle_object_provider_handle_when_unaliased() {
2265 use runmat_accelerate::fusion_residency;
2266
2267 let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
2268 let handle = upload_provider_handle(vec![26.0], vec![1]);
2269 assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
2270 fusion_residency::mark(&handle);
2271
2272 let bytecode = Bytecode::with_instructions(vec![Instr::StoreLocal(0), Instr::Return], 1);
2273 let mut seed_vars = vec![Value::Num(0.0)];
2274 let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
2275 let mut payload = StructValue::new();
2276 payload
2277 .fields
2278 .insert("nested".to_string(), Value::GpuTensor(handle.clone()));
2279 let target = runmat_gc::gc_allocate(Value::Struct(payload)).expect("gc allocate payload");
2280 state.stack.push(Value::Num(0.0));
2281 state.vars = vec![Value::Num(0.0)];
2282 state
2283 .context
2284 .call_stack
2285 .push(crate::bytecode::program::CallFrame {
2286 function_name: "<local>".to_string(),
2287 return_address: 0,
2288 locals_start: 0,
2289 locals_count: 1,
2290 expected_outputs: 0,
2291 });
2292 state.context.locals.push(Value::HandleObject(HandleRef {
2293 class_name: "Payload".to_string(),
2294 target,
2295 valid: true,
2296 }));
2297
2298 let mut result_vars = state.vars.clone();
2299 let _ = block_on(run_interpreter_inner(state, &mut result_vars))
2300 .expect("store local overwrite should complete for nested handle-object value");
2301 assert!(
2302 !fusion_residency::is_resident(&handle),
2303 "store local overwrite should clear residency for nested handle-object handles when unaliased"
2304 );
2305 assert!(
2306 block_on(TEST_PROVIDER.download(&handle)).is_err(),
2307 "store local overwrite should release provider storage for nested handle-object handles when unaliased"
2308 );
2309 }
2310
2311 #[cfg(feature = "native-accel")]
2312 #[test]
2313 fn store_local_overwrite_preserves_nested_handle_object_provider_handle_when_shared_in_var() {
2314 use runmat_accelerate::fusion_residency;
2315
2316 let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
2317 let handle = upload_provider_handle(vec![28.0], vec![1]);
2318 assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
2319 fusion_residency::mark(&handle);
2320
2321 let bytecode = Bytecode::with_instructions(vec![Instr::StoreLocal(0), Instr::Return], 1);
2322 let mut seed_vars = vec![Value::Num(0.0)];
2323 let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
2324 let mut payload = StructValue::new();
2325 payload
2326 .fields
2327 .insert("nested".to_string(), Value::GpuTensor(handle.clone()));
2328 let target = runmat_gc::gc_allocate(Value::Struct(payload)).expect("gc allocate payload");
2329 let local_value = Value::HandleObject(HandleRef {
2330 class_name: "Payload".to_string(),
2331 target,
2332 valid: true,
2333 });
2334 state.stack.push(Value::Num(0.0));
2335 state.vars = vec![local_value.clone()];
2336 state
2337 .context
2338 .call_stack
2339 .push(crate::bytecode::program::CallFrame {
2340 function_name: "<local>".to_string(),
2341 return_address: 0,
2342 locals_start: 0,
2343 locals_count: 1,
2344 expected_outputs: 0,
2345 });
2346 state.context.locals.push(local_value);
2347
2348 let mut result_vars = state.vars.clone();
2349 let _ = block_on(run_interpreter_inner(state, &mut result_vars))
2350 .expect("store local overwrite should complete for aliased nested handle-object value");
2351 assert!(
2352 fusion_residency::is_resident(&handle),
2353 "store local overwrite should preserve residency for nested handle-object handles still live in vars"
2354 );
2355 assert!(
2356 block_on(TEST_PROVIDER.download(&handle)).is_ok(),
2357 "store local overwrite should not release provider storage for nested handle-object handles still live in vars"
2358 );
2359 fusion_residency::clear(&handle);
2360 let _ = TEST_PROVIDER.free(&handle);
2361 }
2362
2363 #[cfg(feature = "native-accel")]
2364 #[test]
2365 fn store_local_overwrite_preserves_nested_handle_object_provider_alias() {
2366 use runmat_accelerate::fusion_residency;
2367
2368 let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
2369 let handle = upload_provider_handle(vec![30.0], vec![1]);
2370 assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
2371 fusion_residency::mark(&handle);
2372
2373 let bytecode = Bytecode::with_instructions(vec![Instr::StoreLocal(0), Instr::Return], 1);
2374 let mut seed_vars = vec![Value::Num(0.0)];
2375 let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
2376 let mut payload = StructValue::new();
2377 payload
2378 .fields
2379 .insert("nested".to_string(), Value::GpuTensor(handle.clone()));
2380 let target = runmat_gc::gc_allocate(Value::Struct(payload)).expect("gc allocate payload");
2381 let nested = Value::HandleObject(HandleRef {
2382 class_name: "Payload".to_string(),
2383 target,
2384 valid: true,
2385 });
2386 state.stack.push(Value::Num(0.0));
2387 state.vars = vec![Value::Num(0.0)];
2388 state
2389 .context
2390 .call_stack
2391 .push(crate::bytecode::program::CallFrame {
2392 function_name: "<local>".to_string(),
2393 return_address: 0,
2394 locals_start: 0,
2395 locals_count: 2,
2396 expected_outputs: 0,
2397 });
2398 state.context.locals.push(nested.clone());
2399 state.context.locals.push(nested);
2400
2401 let mut result_vars = state.vars.clone();
2402 let _ = block_on(run_interpreter_inner(state, &mut result_vars))
2403 .expect("store local overwrite should complete when alias lives in other local");
2404 assert!(
2405 fusion_residency::is_resident(&handle),
2406 "store local overwrite should preserve residency for nested handle-object handles still live in other locals"
2407 );
2408 assert!(
2409 block_on(TEST_PROVIDER.download(&handle)).is_ok(),
2410 "store local overwrite should not release provider storage for nested handle-object handles still live in other locals"
2411 );
2412 fusion_residency::clear(&handle);
2413 let _ = TEST_PROVIDER.free(&handle);
2414 }
2415
2416 #[cfg(feature = "native-accel")]
2417 #[test]
2418 fn spawn_await_completion_releases_stack_only_provider_handle() {
2419 use runmat_accelerate::fusion_residency;
2420
2421 let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
2422 let handle = upload_provider_handle(vec![21.0], vec![1]);
2423 assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
2424 fusion_residency::mark(&handle);
2425
2426 let bytecode =
2427 Bytecode::with_instructions(vec![Instr::Spawn, Instr::Await, Instr::Return], 1);
2428 let mut seed_vars = vec![Value::Num(0.0)];
2429 let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
2430 state.stack.push(Value::GpuTensor(handle.clone()));
2431 state.vars = vec![Value::Num(0.0)];
2432
2433 let mut result_vars = vec![Value::Num(0.0)];
2434 let _ = block_on(run_interpreter_inner(state, &mut result_vars))
2435 .expect("spawn/await flow should complete");
2436 assert!(
2437 !fusion_residency::is_resident(&handle),
2438 "spawn/await completion should clear residency for stack-only handle"
2439 );
2440 assert!(
2441 block_on(TEST_PROVIDER.download(&handle)).is_err(),
2442 "spawn/await completion should release provider storage for stack-only handle"
2443 );
2444 }
2445
2446 #[cfg(feature = "native-accel")]
2447 #[test]
2448 fn spawn_await_completion_preserves_provider_handle_when_still_live_in_vars() {
2449 use runmat_accelerate::fusion_residency;
2450
2451 let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
2452 let handle = upload_provider_handle(vec![31.0], vec![1]);
2453 assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
2454 fusion_residency::mark(&handle);
2455
2456 let bytecode =
2457 Bytecode::with_instructions(vec![Instr::Spawn, Instr::Await, Instr::Return], 1);
2458 let mut seed_vars = vec![Value::GpuTensor(handle.clone())];
2459 let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
2460 state.stack.push(Value::GpuTensor(handle.clone()));
2461 state.vars = vec![Value::GpuTensor(handle.clone())];
2462
2463 let mut result_vars = vec![Value::GpuTensor(handle.clone())];
2464 let _ = block_on(run_interpreter_inner(state, &mut result_vars))
2465 .expect("spawn/await flow should complete");
2466 assert!(
2467 fusion_residency::is_resident(&handle),
2468 "spawn/await completion should preserve residency for live-var handle"
2469 );
2470 assert!(
2471 block_on(TEST_PROVIDER.download(&handle)).is_ok(),
2472 "spawn/await completion should not release provider storage for live-var handle"
2473 );
2474 fusion_residency::clear(&handle);
2475 let _ = TEST_PROVIDER.free(&handle);
2476 }
2477
2478 #[cfg(feature = "native-accel")]
2479 #[test]
2480 fn spawn_pop_releases_stack_only_provider_handle() {
2481 use runmat_accelerate::fusion_residency;
2482
2483 let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
2484 let handle = upload_provider_handle(vec![41.0], vec![1]);
2485 assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
2486 fusion_residency::mark(&handle);
2487
2488 let bytecode =
2489 Bytecode::with_instructions(vec![Instr::Spawn, Instr::Pop, Instr::Return], 1);
2490 let mut seed_vars = vec![Value::Num(0.0)];
2491 let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
2492 state.stack.push(Value::GpuTensor(handle.clone()));
2493 state.vars = vec![Value::Num(0.0)];
2494
2495 let mut result_vars = vec![Value::Num(0.0)];
2496 let _ = block_on(run_interpreter_inner(state, &mut result_vars))
2497 .expect("spawn/pop should complete");
2498 assert!(
2499 !fusion_residency::is_resident(&handle),
2500 "spawn/pop should clear residency for dropped spawned task payload"
2501 );
2502 assert!(
2503 block_on(TEST_PROVIDER.download(&handle)).is_err(),
2504 "spawn/pop should release provider storage for dropped spawned task payload"
2505 );
2506 }
2507
2508 #[cfg(feature = "native-accel")]
2509 #[test]
2510 fn spawn_pop_preserves_provider_handle_when_payload_still_live_in_vars() {
2511 use runmat_accelerate::fusion_residency;
2512
2513 let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
2514 let handle = upload_provider_handle(vec![51.0], vec![1]);
2515 assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
2516 fusion_residency::mark(&handle);
2517
2518 let bytecode =
2519 Bytecode::with_instructions(vec![Instr::Spawn, Instr::Pop, Instr::Return], 1);
2520 let mut seed_vars = vec![Value::GpuTensor(handle.clone())];
2521 let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
2522 state.stack.push(Value::GpuTensor(handle.clone()));
2523 state.vars = vec![Value::GpuTensor(handle.clone())];
2524
2525 let mut result_vars = vec![Value::GpuTensor(handle.clone())];
2526 let _ = block_on(run_interpreter_inner(state, &mut result_vars))
2527 .expect("spawn/pop should complete");
2528 assert!(
2529 fusion_residency::is_resident(&handle),
2530 "spawn/pop should preserve residency for spawned payload handles still referenced by vars"
2531 );
2532 assert!(
2533 block_on(TEST_PROVIDER.download(&handle)).is_ok(),
2534 "spawn/pop should not release provider storage for spawned payload handles still referenced by vars"
2535 );
2536 fusion_residency::clear(&handle);
2537 let _ = TEST_PROVIDER.free(&handle);
2538 }
2539
2540 #[cfg(feature = "native-accel")]
2541 #[test]
2542 fn spawn_pop_preserves_provider_handle_when_payload_still_live_in_locals() {
2543 use runmat_accelerate::fusion_residency;
2544
2545 let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
2546 let handle = upload_provider_handle(vec![56.0], vec![1]);
2547 assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
2548 fusion_residency::mark(&handle);
2549
2550 let bytecode =
2551 Bytecode::with_instructions(vec![Instr::Spawn, Instr::Pop, Instr::Return], 1);
2552 let mut seed_vars = vec![Value::Num(0.0)];
2553 let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
2554 state.stack.push(Value::GpuTensor(handle.clone()));
2555 state.vars = vec![Value::Num(0.0)];
2556 state.context.locals.push(Value::GpuTensor(handle.clone()));
2557
2558 let mut result_vars = vec![Value::Num(0.0)];
2559 let _ = block_on(run_interpreter_inner(state, &mut result_vars))
2560 .expect("spawn/pop should complete");
2561 assert!(
2562 fusion_residency::is_resident(&handle),
2563 "spawn/pop should preserve residency for spawned payload handles still referenced by locals"
2564 );
2565 assert!(
2566 block_on(TEST_PROVIDER.download(&handle)).is_ok(),
2567 "spawn/pop should not release provider storage for spawned payload handles still referenced by locals"
2568 );
2569 fusion_residency::clear(&handle);
2570 let _ = TEST_PROVIDER.free(&handle);
2571 }
2572
2573 #[cfg(feature = "native-accel")]
2574 #[test]
2575 fn spawn_pop_releases_nested_closure_captured_provider_handle() {
2576 use runmat_accelerate::fusion_residency;
2577
2578 let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
2579 let handle = upload_provider_handle(vec![61.0], vec![1]);
2580 assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
2581 fusion_residency::mark(&handle);
2582
2583 let bytecode =
2584 Bytecode::with_instructions(vec![Instr::Spawn, Instr::Pop, Instr::Return], 1);
2585 let mut seed_vars = vec![Value::Num(0.0)];
2586 let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
2587 state.stack.push(Value::Closure(Closure {
2588 function_name: "worker".to_string(),
2589 bound_function: None,
2590 captures: vec![Value::GpuTensor(handle.clone())],
2591 }));
2592 state.vars = vec![Value::Num(0.0)];
2593
2594 let mut result_vars = vec![Value::Num(0.0)];
2595 let _ = block_on(run_interpreter_inner(state, &mut result_vars))
2596 .expect("spawn/pop should complete for closure payload");
2597 assert!(
2598 !fusion_residency::is_resident(&handle),
2599 "spawn/pop should clear residency for nested closure-captured payload handles"
2600 );
2601 assert!(
2602 block_on(TEST_PROVIDER.download(&handle)).is_err(),
2603 "spawn/pop should release provider storage for nested closure-captured payload handles"
2604 );
2605 }
2606
2607 #[cfg(feature = "native-accel")]
2608 #[test]
2609 fn spawn_await_completion_releases_nested_output_list_provider_handle() {
2610 use runmat_accelerate::fusion_residency;
2611
2612 let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
2613 let handle = upload_provider_handle(vec![71.0], vec![1]);
2614 assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
2615 fusion_residency::mark(&handle);
2616
2617 let bytecode =
2618 Bytecode::with_instructions(vec![Instr::Spawn, Instr::Await, Instr::Return], 1);
2619 let mut seed_vars = vec![Value::Num(0.0)];
2620 let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
2621 state
2622 .stack
2623 .push(Value::OutputList(vec![Value::GpuTensor(handle.clone())]));
2624 state.vars = vec![Value::Num(0.0)];
2625
2626 let mut result_vars = vec![Value::Num(0.0)];
2627 let _ = block_on(run_interpreter_inner(state, &mut result_vars))
2628 .expect("spawn/await flow should complete for nested output payload");
2629 assert!(
2630 !fusion_residency::is_resident(&handle),
2631 "spawn/await completion should clear residency for nested output-list payload handles"
2632 );
2633 assert!(
2634 block_on(TEST_PROVIDER.download(&handle)).is_err(),
2635 "spawn/await completion should release provider storage for nested output-list payload handles"
2636 );
2637 }
2638
2639 #[cfg(feature = "native-accel")]
2640 #[test]
2641 fn spawn_await_completion_releases_nested_struct_provider_handle() {
2642 use runmat_accelerate::fusion_residency;
2643
2644 let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
2645 let handle = upload_provider_handle(vec![81.0], vec![1]);
2646 assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
2647 fusion_residency::mark(&handle);
2648
2649 let bytecode =
2650 Bytecode::with_instructions(vec![Instr::Spawn, Instr::Await, Instr::Return], 1);
2651 let mut seed_vars = vec![Value::Num(0.0)];
2652 let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
2653 let mut payload = StructValue::new();
2654 payload
2655 .fields
2656 .insert("nested".to_string(), Value::GpuTensor(handle.clone()));
2657 state.stack.push(Value::Struct(payload));
2658 state.vars = vec![Value::Num(0.0)];
2659
2660 let mut result_vars = vec![Value::Num(0.0)];
2661 let _ = block_on(run_interpreter_inner(state, &mut result_vars))
2662 .expect("spawn/await flow should complete for nested struct payload");
2663 assert!(
2664 !fusion_residency::is_resident(&handle),
2665 "spawn/await completion should clear residency for nested struct payload handles"
2666 );
2667 assert!(
2668 block_on(TEST_PROVIDER.download(&handle)).is_err(),
2669 "spawn/await completion should release provider storage for nested struct payload handles"
2670 );
2671 }
2672
2673 #[cfg(feature = "native-accel")]
2674 #[test]
2675 fn spawn_await_completion_releases_nested_object_property_provider_handle() {
2676 use runmat_accelerate::fusion_residency;
2677
2678 let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
2679 let handle = upload_provider_handle(vec![91.0], vec![1]);
2680 assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
2681 fusion_residency::mark(&handle);
2682
2683 let bytecode =
2684 Bytecode::with_instructions(vec![Instr::Spawn, Instr::Await, Instr::Return], 1);
2685 let mut seed_vars = vec![Value::Num(0.0)];
2686 let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
2687 let mut payload = ObjectInstance::new("Payload".to_string());
2688 payload
2689 .properties
2690 .insert("nested".to_string(), Value::GpuTensor(handle.clone()));
2691 state.stack.push(Value::Object(payload));
2692 state.vars = vec![Value::Num(0.0)];
2693
2694 let mut result_vars = vec![Value::Num(0.0)];
2695 let _ = block_on(run_interpreter_inner(state, &mut result_vars))
2696 .expect("spawn/await flow should complete for nested object payload");
2697 assert!(
2698 !fusion_residency::is_resident(&handle),
2699 "spawn/await completion should clear residency for nested object-property payload handles"
2700 );
2701 assert!(
2702 block_on(TEST_PROVIDER.download(&handle)).is_err(),
2703 "spawn/await completion should release provider storage for nested object-property payload handles"
2704 );
2705 }
2706
2707 #[cfg(feature = "native-accel")]
2708 #[test]
2709 fn spawn_await_completion_preserves_nested_object_property_handle_when_alias_live() {
2710 use runmat_accelerate::fusion_residency;
2711
2712 let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
2713 let handle = upload_provider_handle(vec![101.0], vec![1]);
2714 assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
2715 fusion_residency::mark(&handle);
2716
2717 let bytecode =
2718 Bytecode::with_instructions(vec![Instr::Spawn, Instr::Await, Instr::Return], 1);
2719 let mut seed_vars = vec![Value::Num(0.0)];
2720 let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
2721 let mut payload = ObjectInstance::new("Payload".to_string());
2722 payload
2723 .properties
2724 .insert("nested".to_string(), Value::GpuTensor(handle.clone()));
2725 state.stack.push(Value::Object(payload.clone()));
2726 state.vars = vec![Value::Object(payload.clone())];
2727
2728 let mut result_vars = vec![Value::Object(payload)];
2729 let _ = block_on(run_interpreter_inner(state, &mut result_vars))
2730 .expect("spawn/await flow should complete for aliased nested object payload");
2731 assert!(
2732 fusion_residency::is_resident(&handle),
2733 "spawn/await completion should preserve residency for nested object handles still referenced by vars"
2734 );
2735 assert!(
2736 block_on(TEST_PROVIDER.download(&handle)).is_ok(),
2737 "spawn/await completion should not release provider storage for nested object handles still referenced by vars"
2738 );
2739 fusion_residency::clear(&handle);
2740 let _ = TEST_PROVIDER.free(&handle);
2741 }
2742
2743 #[cfg(feature = "native-accel")]
2744 #[test]
2745 fn spawn_await_completion_releases_nested_cell_provider_handle() {
2746 use runmat_accelerate::fusion_residency;
2747
2748 let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
2749 let handle = upload_provider_handle(vec![111.0], vec![1]);
2750 assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
2751 fusion_residency::mark(&handle);
2752
2753 let bytecode =
2754 Bytecode::with_instructions(vec![Instr::Spawn, Instr::Await, Instr::Return], 1);
2755 let mut seed_vars = vec![Value::Num(0.0)];
2756 let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
2757 let payload =
2758 CellArray::new(vec![Value::GpuTensor(handle.clone())], 1, 1).expect("cell payload");
2759 state.stack.push(Value::Cell(payload));
2760 state.vars = vec![Value::Num(0.0)];
2761
2762 let mut result_vars = vec![Value::Num(0.0)];
2763 let _ = block_on(run_interpreter_inner(state, &mut result_vars))
2764 .expect("spawn/await flow should complete for nested cell payload");
2765 assert!(
2766 !fusion_residency::is_resident(&handle),
2767 "spawn/await completion should clear residency for nested cell payload handles"
2768 );
2769 assert!(
2770 block_on(TEST_PROVIDER.download(&handle)).is_err(),
2771 "spawn/await completion should release provider storage for nested cell payload handles"
2772 );
2773 }
2774
2775 #[cfg(feature = "native-accel")]
2776 #[test]
2777 fn spawn_await_completion_preserves_nested_cell_handle_when_alias_live() {
2778 use runmat_accelerate::fusion_residency;
2779
2780 let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
2781 let handle = upload_provider_handle(vec![121.0], vec![1]);
2782 assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
2783 fusion_residency::mark(&handle);
2784
2785 let bytecode =
2786 Bytecode::with_instructions(vec![Instr::Spawn, Instr::Await, Instr::Return], 1);
2787 let mut seed_vars = vec![Value::Num(0.0)];
2788 let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
2789 let payload =
2790 CellArray::new(vec![Value::GpuTensor(handle.clone())], 1, 1).expect("cell payload");
2791 state.stack.push(Value::Cell(payload.clone()));
2792 state.vars = vec![Value::Cell(payload.clone())];
2793
2794 let mut result_vars = vec![Value::Cell(payload)];
2795 let _ = block_on(run_interpreter_inner(state, &mut result_vars))
2796 .expect("spawn/await flow should complete for aliased nested cell payload");
2797 assert!(
2798 fusion_residency::is_resident(&handle),
2799 "spawn/await completion should preserve residency for nested cell handles still referenced by vars"
2800 );
2801 assert!(
2802 block_on(TEST_PROVIDER.download(&handle)).is_ok(),
2803 "spawn/await completion should not release provider storage for nested cell handles still referenced by vars"
2804 );
2805 fusion_residency::clear(&handle);
2806 let _ = TEST_PROVIDER.free(&handle);
2807 }
2808
2809 #[cfg(feature = "native-accel")]
2810 #[test]
2811 fn spawn_await_completion_releases_nested_handle_object_target_provider_handle() {
2812 use runmat_accelerate::fusion_residency;
2813
2814 let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
2815 let handle = upload_provider_handle(vec![131.0], vec![1]);
2816 assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
2817 fusion_residency::mark(&handle);
2818
2819 let bytecode =
2820 Bytecode::with_instructions(vec![Instr::Spawn, Instr::Await, Instr::Return], 1);
2821 let mut seed_vars = vec![Value::Num(0.0)];
2822 let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
2823 let mut payload = StructValue::new();
2824 payload
2825 .fields
2826 .insert("nested".to_string(), Value::GpuTensor(handle.clone()));
2827 let target = runmat_gc::gc_allocate(Value::Struct(payload)).expect("gc allocate payload");
2828 let task_payload = Value::HandleObject(HandleRef {
2829 class_name: "Payload".to_string(),
2830 target,
2831 valid: true,
2832 });
2833 state.stack.push(task_payload);
2834 state.vars = vec![Value::Num(0.0)];
2835
2836 let mut result_vars = vec![Value::Num(0.0)];
2837 let _ = block_on(run_interpreter_inner(state, &mut result_vars))
2838 .expect("spawn/await flow should complete for nested handle-object payload");
2839 assert!(
2840 !fusion_residency::is_resident(&handle),
2841 "spawn/await completion should clear residency for nested handle-object target handles"
2842 );
2843 assert!(
2844 block_on(TEST_PROVIDER.download(&handle)).is_err(),
2845 "spawn/await completion should release provider storage for nested handle-object target handles"
2846 );
2847 }
2848
2849 #[cfg(feature = "native-accel")]
2850 #[test]
2851 fn spawn_await_completion_preserves_nested_handle_object_target_handle_when_alias_live() {
2852 use runmat_accelerate::fusion_residency;
2853
2854 let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
2855 let handle = upload_provider_handle(vec![141.0], vec![1]);
2856 assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
2857 fusion_residency::mark(&handle);
2858
2859 let bytecode =
2860 Bytecode::with_instructions(vec![Instr::Spawn, Instr::Await, Instr::Return], 1);
2861 let mut seed_vars = vec![Value::Num(0.0)];
2862 let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
2863 let mut payload = StructValue::new();
2864 payload
2865 .fields
2866 .insert("nested".to_string(), Value::GpuTensor(handle.clone()));
2867 let target = runmat_gc::gc_allocate(Value::Struct(payload)).expect("gc allocate payload");
2868 let task_payload = Value::HandleObject(HandleRef {
2869 class_name: "Payload".to_string(),
2870 target,
2871 valid: true,
2872 });
2873 state.stack.push(task_payload.clone());
2874 state.vars = vec![task_payload.clone()];
2875
2876 let mut result_vars = vec![task_payload];
2877 let _ = block_on(run_interpreter_inner(state, &mut result_vars))
2878 .expect("spawn/await flow should complete for aliased nested handle-object payload");
2879 assert!(
2880 fusion_residency::is_resident(&handle),
2881 "spawn/await completion should preserve residency for nested handle-object target handles still referenced by vars"
2882 );
2883 assert!(
2884 block_on(TEST_PROVIDER.download(&handle)).is_ok(),
2885 "spawn/await completion should not release provider storage for nested handle-object target handles still referenced by vars"
2886 );
2887 fusion_residency::clear(&handle);
2888 let _ = TEST_PROVIDER.free(&handle);
2889 }
2890
2891 #[cfg(feature = "native-accel")]
2892 #[test]
2893 fn spawn_await_preserves_nested_handle_object_target_alias() {
2894 use runmat_accelerate::fusion_residency;
2895
2896 let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
2897 let handle = upload_provider_handle(vec![146.0], vec![1]);
2898 assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
2899 fusion_residency::mark(&handle);
2900
2901 let bytecode =
2902 Bytecode::with_instructions(vec![Instr::Spawn, Instr::Await, Instr::Return], 1);
2903 let mut seed_vars = vec![Value::Num(0.0)];
2904 let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
2905 let mut payload = StructValue::new();
2906 payload
2907 .fields
2908 .insert("nested".to_string(), Value::GpuTensor(handle.clone()));
2909 let target = runmat_gc::gc_allocate(Value::Struct(payload)).expect("gc allocate payload");
2910 let task_payload = Value::HandleObject(HandleRef {
2911 class_name: "Payload".to_string(),
2912 target,
2913 valid: true,
2914 });
2915 state.stack.push(task_payload.clone());
2916 state.vars = vec![Value::Num(0.0)];
2917 state.context.locals.push(task_payload.clone());
2918
2919 let mut result_vars = vec![Value::Num(0.0)];
2920 let _ = block_on(run_interpreter_inner(state, &mut result_vars))
2921 .expect("spawn/await flow should complete for aliased nested handle-object payload");
2922 assert!(
2923 fusion_residency::is_resident(&handle),
2924 "spawn/await completion should preserve residency for nested handle-object target handles still referenced by locals"
2925 );
2926 assert!(
2927 block_on(TEST_PROVIDER.download(&handle)).is_ok(),
2928 "spawn/await completion should not release provider storage for nested handle-object target handles still referenced by locals"
2929 );
2930 fusion_residency::clear(&handle);
2931 let _ = TEST_PROVIDER.free(&handle);
2932 }
2933
2934 #[cfg(feature = "native-accel")]
2935 #[test]
2936 fn spawn_pop_releases_nested_handle_object_target_provider_handle() {
2937 use runmat_accelerate::fusion_residency;
2938
2939 let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
2940 let handle = upload_provider_handle(vec![151.0], vec![1]);
2941 assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
2942 fusion_residency::mark(&handle);
2943
2944 let bytecode =
2945 Bytecode::with_instructions(vec![Instr::Spawn, Instr::Pop, Instr::Return], 1);
2946 let mut seed_vars = vec![Value::Num(0.0)];
2947 let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
2948 let mut payload = StructValue::new();
2949 payload
2950 .fields
2951 .insert("nested".to_string(), Value::GpuTensor(handle.clone()));
2952 let target = runmat_gc::gc_allocate(Value::Struct(payload)).expect("gc allocate payload");
2953 state.stack.push(Value::HandleObject(HandleRef {
2954 class_name: "Payload".to_string(),
2955 target,
2956 valid: true,
2957 }));
2958 state.vars = vec![Value::Num(0.0)];
2959
2960 let mut result_vars = vec![Value::Num(0.0)];
2961 let _ = block_on(run_interpreter_inner(state, &mut result_vars))
2962 .expect("spawn/pop flow should complete for nested handle-object payload");
2963 assert!(
2964 !fusion_residency::is_resident(&handle),
2965 "spawn/pop should clear residency for nested handle-object target handles"
2966 );
2967 assert!(
2968 block_on(TEST_PROVIDER.download(&handle)).is_err(),
2969 "spawn/pop should release provider storage for nested handle-object target handles"
2970 );
2971 }
2972
2973 #[cfg(feature = "native-accel")]
2974 #[test]
2975 fn spawn_pop_preserves_nested_handle_object_target_handle_when_alias_live() {
2976 use runmat_accelerate::fusion_residency;
2977
2978 let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
2979 let handle = upload_provider_handle(vec![161.0], vec![1]);
2980 assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
2981 fusion_residency::mark(&handle);
2982
2983 let bytecode =
2984 Bytecode::with_instructions(vec![Instr::Spawn, Instr::Pop, Instr::Return], 1);
2985 let mut seed_vars = vec![Value::Num(0.0)];
2986 let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
2987 let mut payload = StructValue::new();
2988 payload
2989 .fields
2990 .insert("nested".to_string(), Value::GpuTensor(handle.clone()));
2991 let target = runmat_gc::gc_allocate(Value::Struct(payload)).expect("gc allocate payload");
2992 let task_payload = Value::HandleObject(HandleRef {
2993 class_name: "Payload".to_string(),
2994 target,
2995 valid: true,
2996 });
2997 state.stack.push(task_payload.clone());
2998 state.vars = vec![task_payload.clone()];
2999
3000 let mut result_vars = vec![task_payload];
3001 let _ = block_on(run_interpreter_inner(state, &mut result_vars))
3002 .expect("spawn/pop flow should complete for aliased nested handle-object payload");
3003 assert!(
3004 fusion_residency::is_resident(&handle),
3005 "spawn/pop should preserve residency for nested handle-object target handles still referenced by vars"
3006 );
3007 assert!(
3008 block_on(TEST_PROVIDER.download(&handle)).is_ok(),
3009 "spawn/pop should not release provider storage for nested handle-object target handles still referenced by vars"
3010 );
3011 fusion_residency::clear(&handle);
3012 let _ = TEST_PROVIDER.free(&handle);
3013 }
3014
3015 #[cfg(feature = "native-accel")]
3016 #[test]
3017 fn spawn_pop_preserves_nested_handle_object_target_handle_when_alias_live_in_locals() {
3018 use runmat_accelerate::fusion_residency;
3019
3020 let _provider_guard = ThreadProviderGuard::set(Some(&*TEST_PROVIDER));
3021 let handle = upload_provider_handle(vec![166.0], vec![1]);
3022 assert!(block_on(TEST_PROVIDER.download(&handle)).is_ok());
3023 fusion_residency::mark(&handle);
3024
3025 let bytecode =
3026 Bytecode::with_instructions(vec![Instr::Spawn, Instr::Pop, Instr::Return], 1);
3027 let mut seed_vars = vec![Value::Num(0.0)];
3028 let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
3029 let mut payload = StructValue::new();
3030 payload
3031 .fields
3032 .insert("nested".to_string(), Value::GpuTensor(handle.clone()));
3033 let target = runmat_gc::gc_allocate(Value::Struct(payload)).expect("gc allocate payload");
3034 let task_payload = Value::HandleObject(HandleRef {
3035 class_name: "Payload".to_string(),
3036 target,
3037 valid: true,
3038 });
3039 state.stack.push(task_payload.clone());
3040 state.vars = vec![Value::Num(0.0)];
3041 state.context.locals.push(task_payload);
3042
3043 let mut result_vars = vec![Value::Num(0.0)];
3044 let _ = block_on(run_interpreter_inner(state, &mut result_vars))
3045 .expect("spawn/pop flow should complete for aliased nested handle-object payload");
3046 assert!(
3047 fusion_residency::is_resident(&handle),
3048 "spawn/pop should preserve residency for nested handle-object target handles still referenced by locals"
3049 );
3050 assert!(
3051 block_on(TEST_PROVIDER.download(&handle)).is_ok(),
3052 "spawn/pop should not release provider storage for nested handle-object target handles still referenced by locals"
3053 );
3054 fusion_residency::clear(&handle);
3055 let _ = TEST_PROVIDER.free(&handle);
3056 }
3057
3058 #[test]
3059 fn await_passes_through_non_spawn_value_operand() {
3060 let bytecode =
3061 Bytecode::with_instructions(vec![Instr::Await, Instr::StoreVar(0), Instr::Return], 1);
3062 let mut seed_vars = vec![Value::Num(0.0)];
3063 let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
3064 state.stack.push(Value::Num(7.0));
3065 state.vars = vec![Value::Num(0.0)];
3066
3067 let mut result_vars = vec![Value::Num(0.0)];
3068 let _ = block_on(run_interpreter_inner(state, &mut result_vars))
3069 .expect("await should pass through non-task operand");
3070 assert_eq!(result_vars[0], Value::Num(7.0));
3071 }
3072
3073 #[test]
3074 fn await_succeeds_after_spawn_handle_self_reassignment() {
3075 let bytecode = Bytecode::with_instructions(
3076 vec![
3077 Instr::Spawn,
3078 Instr::StoreVar(0),
3079 Instr::LoadVar(0),
3080 Instr::StoreVar(0),
3081 Instr::LoadVar(0),
3082 Instr::Await,
3083 Instr::StoreVar(0),
3084 Instr::Return,
3085 ],
3086 1,
3087 );
3088 let mut seed_vars = vec![Value::Num(0.0)];
3089 let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
3090 state.stack.push(Value::Num(9.0));
3091 state.vars = vec![Value::Num(0.0)];
3092
3093 let mut result_vars = vec![Value::Num(0.0)];
3094 let _ = block_on(run_interpreter_inner(state, &mut result_vars))
3095 .expect("await should still succeed after self-reassignment of spawn handle");
3096 assert_eq!(result_vars[0], Value::Num(9.0));
3097 }
3098
3099 #[test]
3100 fn await_succeeds_after_overwriting_one_spawn_handle_alias() {
3101 let bytecode = Bytecode::with_instructions(
3102 vec![
3103 Instr::Spawn,
3104 Instr::StoreVar(0),
3105 Instr::LoadVar(0),
3106 Instr::StoreVar(1),
3107 Instr::LoadConst(0.0),
3108 Instr::StoreVar(0),
3109 Instr::LoadVar(1),
3110 Instr::Await,
3111 Instr::StoreVar(0),
3112 Instr::Return,
3113 ],
3114 2,
3115 );
3116 let mut seed_vars = vec![Value::Num(0.0), Value::Num(0.0)];
3117 let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
3118 state.stack.push(Value::Num(9.0));
3119 state.vars = vec![Value::Num(0.0), Value::Num(0.0)];
3120
3121 let mut result_vars = vec![Value::Num(0.0), Value::Num(0.0)];
3122 let _ = block_on(run_interpreter_inner(state, &mut result_vars))
3123 .expect("await should succeed when another alias still carries the spawn task handle");
3124 assert_eq!(result_vars[0], Value::Num(9.0));
3125 }
3126
3127 #[test]
3128 fn await_succeeds_after_overwriting_one_local_spawn_handle_alias() {
3129 let bytecode = Bytecode::with_instructions(
3130 vec![
3131 Instr::Spawn,
3132 Instr::StoreLocal(0),
3133 Instr::LoadLocal(0),
3134 Instr::StoreLocal(1),
3135 Instr::LoadConst(0.0),
3136 Instr::StoreLocal(0),
3137 Instr::LoadLocal(1),
3138 Instr::Await,
3139 Instr::StoreVar(0),
3140 Instr::Return,
3141 ],
3142 1,
3143 );
3144 let mut seed_vars = vec![Value::Num(0.0)];
3145 let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
3146 state.stack.push(Value::Num(9.0));
3147 state.vars = vec![Value::Num(0.0)];
3148 state
3149 .context
3150 .call_stack
3151 .push(crate::bytecode::program::CallFrame {
3152 function_name: "<local>".to_string(),
3153 return_address: 0,
3154 locals_start: 0,
3155 locals_count: 2,
3156 expected_outputs: 0,
3157 });
3158 state.context.locals = vec![Value::Num(0.0), Value::Num(0.0)];
3159
3160 let mut result_vars = vec![Value::Num(0.0)];
3161 let _ = block_on(run_interpreter_inner(state, &mut result_vars)).expect(
3162 "await should succeed when another local alias still carries the spawn task handle",
3163 );
3164 assert_eq!(result_vars[0], Value::Num(9.0));
3165 }
3166
3167 #[test]
3168 fn await_succeeds_after_overwriting_var_alias_when_local_spawn_handle_alias_live() {
3169 let bytecode = Bytecode::with_instructions(
3170 vec![
3171 Instr::Spawn,
3172 Instr::StoreLocal(0),
3173 Instr::LoadLocal(0),
3174 Instr::StoreVar(0),
3175 Instr::LoadConst(0.0),
3176 Instr::StoreVar(0),
3177 Instr::LoadLocal(0),
3178 Instr::Await,
3179 Instr::StoreVar(0),
3180 Instr::Return,
3181 ],
3182 1,
3183 );
3184 let mut seed_vars = vec![Value::Num(0.0)];
3185 let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
3186 state.stack.push(Value::Num(9.0));
3187 state.vars = vec![Value::Num(0.0)];
3188 state
3189 .context
3190 .call_stack
3191 .push(crate::bytecode::program::CallFrame {
3192 function_name: "<local>".to_string(),
3193 return_address: 0,
3194 locals_start: 0,
3195 locals_count: 1,
3196 expected_outputs: 0,
3197 });
3198 state.context.locals = vec![Value::Num(0.0)];
3199
3200 let mut result_vars = vec![Value::Num(0.0)];
3201 let _ = block_on(run_interpreter_inner(state, &mut result_vars)).expect(
3202 "await should succeed when var alias is overwritten but local alias still carries the spawn task handle",
3203 );
3204 assert_eq!(result_vars[0], Value::Num(9.0));
3205 }
3206
3207 #[test]
3208 fn await_succeeds_after_scope_exit_when_var_alias_keeps_spawn_task_id_live() {
3209 let mut task = runmat_builtins::StructValue::new();
3210 task.fields.insert(
3211 "__runmat_spawn_kind".to_string(),
3212 Value::String("task".to_string()),
3213 );
3214 task.fields.insert(
3215 "__runmat_spawn_id".to_string(),
3216 Value::Int(runmat_builtins::IntValue::U64(23)),
3217 );
3218 task.fields
3219 .insert("__runmat_spawn_payload".to_string(), Value::Num(4.0));
3220 let task_value = Value::Struct(task);
3221
3222 let bytecode = Bytecode::with_instructions(
3223 vec![
3224 Instr::ExitScope(1),
3225 Instr::LoadVar(0),
3226 Instr::Await,
3227 Instr::Return,
3228 ],
3229 1,
3230 );
3231 let mut seed_vars = vec![task_value.clone()];
3232 let mut state = InterpreterState::new(bytecode, &mut seed_vars, Some("<main>"), Vec::new());
3233 state.context.locals.push(task_value);
3234 state.context.spawned_task_ids.insert(23);
3235 state.vars = seed_vars.clone();
3236
3237 let mut result_vars = seed_vars.clone();
3238 let _ = block_on(run_interpreter_inner(state, &mut result_vars))
3239 .expect("await should succeed when var alias keeps the spawn task id live");
3240 assert!(
3241 matches!(result_vars[0], Value::Struct(_)),
3242 "await in this sequence does not overwrite var0"
3243 );
3244 }
3245}