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