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