Skip to main content

tenferro_runtime/runtime/
execution.rs

1//! Runtime-owned compiled-graph execution boundary.
2//!
3//! This module owns the private execution bridge used by
4//! `Runtime::run_compiled*`.
5
6use std::collections::{HashMap, HashSet};
7use std::error::Error as StdError;
8use std::fmt;
9use std::panic::{catch_unwind, AssertUnwindSafe};
10use std::sync::{Arc, Condvar, Mutex, MutexGuard};
11use std::thread::{self, ThreadId};
12
13use smallvec::SmallVec;
14use tenferro_ops::shape_extent::ShapeExtent;
15use tenferro_tensor::{Tensor, TensorBackend, TensorRead, TensorValue};
16
17use crate::error::ErrorPhase;
18use crate::exec::{
19    DispatchMode, ExecInstruction, ExecProgram, ExecSlot, ExtensionExecutionDispatch,
20};
21use crate::extension_cache::{ExtensionCacheSelector, ExtensionCacheStore};
22use crate::graph::CompiledGraph;
23use crate::runtime::schedule::{
24    EventDependency, ExecutionLocation, ScheduledGraph, ScheduledNode, ScheduledNodeKind,
25    ScheduledTransfer, UnsupportedScheduledNodeError,
26};
27use crate::runtime::{
28    CacheOwnerError, CacheStats, EventDomainError, EventDomainOperation, EventDomainRun,
29    EventToken, InputSignature, PrepareError, PrepareOptions, PreparedOperationPlan, Runtime,
30    RuntimeCacheOwner, SubmissionError, TransferError, TransferProviderContractError,
31    TransferRequest,
32};
33use crate::{Error, Result};
34
35type RuntimeInputRefs<'a> = SmallVec<[&'a Tensor; 8]>;
36type RuntimeInputReads<'a> = SmallVec<[TensorRead<'a>; 8]>;
37type RuntimeInputShapes<'a> = SmallVec<[&'a [usize]; 8]>;
38type RuntimeShapeScratch = SmallVec<[usize; 8]>;
39
40#[derive(Clone, Copy, Debug)]
41pub(super) enum RuntimeOutputMode {
42    Tensor,
43    Value,
44}
45
46/// Runtime-prepared compiled graph execution handle.
47///
48/// This handle keeps the prepared execution staging and its direct provider
49/// witnesses inside the immutable schedule. It is tied to the runtime epoch
50/// that created it and becomes stale after runtime reconfiguration.
51#[derive(Clone)]
52pub struct PreparedCompiledGraph {
53    runtime_id: super::RuntimeId,
54    epoch: super::RuntimeEpoch,
55    program: CompiledGraph,
56    prepared: Arc<super::preparation::PreparedProgram>,
57}
58
59/// Asynchronous runtime execution handle returned by [`Runtime::submit`].
60pub struct ExecutionHandle {
61    submission: Arc<InFlightSubmission>,
62}
63
64impl ExecutionHandle {
65    /// Wait for submitted work to finish and return its tensor outputs.
66    ///
67    /// # Errors
68    ///
69    /// Returns the submitted runtime execution [`Error`], including
70    /// [`ErrorKind::RuntimeState`](tenferro_tensor::ErrorKind::RuntimeState)
71    /// when the handle was already consumed or the worker panicked.
72    pub fn wait(self) -> Result<Vec<Tensor>> {
73        self.submission.wait()
74    }
75}
76
77impl fmt::Debug for ExecutionHandle {
78    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
79        formatter
80            .debug_struct("ExecutionHandle")
81            .field("pending", &self.submission.is_pending())
82            .finish_non_exhaustive()
83    }
84}
85
86pub(super) struct InFlightSubmission {
87    work: Mutex<Option<InFlightWork>>,
88    completion: Mutex<Option<Result<Vec<Tensor>>>>,
89    completed: Condvar,
90}
91
92struct AdmittedExecution {
93    prepared: PreparedCompiledGraph,
94    inputs: Vec<Tensor>,
95}
96
97enum InFlightWork {
98    Admitted(AdmittedExecution),
99    #[cfg(test)]
100    Test(Box<dyn FnOnce() -> Result<Vec<Tensor>> + Send>),
101}
102
103impl InFlightSubmission {
104    fn new(prepared: PreparedCompiledGraph, inputs: Vec<Tensor>) -> Self {
105        Self {
106            work: Mutex::new(Some(InFlightWork::Admitted(AdmittedExecution {
107                prepared,
108                inputs,
109            }))),
110            completion: Mutex::new(None),
111            completed: Condvar::new(),
112        }
113    }
114
115    #[cfg(test)]
116    pub(super) fn for_test(work: impl FnOnce() -> Result<Vec<Tensor>> + Send + 'static) -> Self {
117        Self {
118            work: Mutex::new(Some(InFlightWork::Test(Box::new(work)))),
119            completion: Mutex::new(None),
120            completed: Condvar::new(),
121        }
122    }
123
124    pub(super) fn run(&self) {
125        let work = match self.work.lock() {
126            Ok(mut work) => work.take(),
127            Err(poisoned) => poisoned.into_inner().take(),
128        };
129        let result = catch_unwind(AssertUnwindSafe(|| match work {
130            Some(InFlightWork::Admitted(admitted)) => {
131                let input_refs = admitted.inputs.iter().collect::<Vec<_>>();
132                execute_admitted(&admitted.prepared, &input_refs)
133            }
134            #[cfg(test)]
135            Some(InFlightWork::Test(work)) => work(),
136            None => Err(Error::runtime_state(
137                "Runtime::submit",
138                ErrorPhase::Execution,
139                "in-flight submission work was already consumed",
140            )),
141        }))
142        .unwrap_or_else(|payload| {
143            Err(Error::runtime_state(
144                "ExecutionHandle::wait",
145                ErrorPhase::Execution,
146                panic_payload_message(payload),
147            ))
148        });
149        match self.completion.lock() {
150            Ok(mut completion) => {
151                *completion = Some(result);
152            }
153            Err(poisoned) => {
154                *poisoned.into_inner() = Some(Err(Error::runtime_state(
155                    "ExecutionHandle::wait",
156                    ErrorPhase::Execution,
157                    "in-flight completion lock poisoned",
158                )));
159            }
160        }
161        self.completed.notify_all();
162    }
163
164    fn wait(&self) -> Result<Vec<Tensor>> {
165        let mut completion = self.completion.lock().map_err(|_| {
166            Error::runtime_state(
167                "ExecutionHandle::wait",
168                ErrorPhase::Execution,
169                "in-flight completion lock poisoned",
170            )
171        })?;
172        loop {
173            if let Some(result) = completion.take() {
174                return result;
175            }
176            completion = self.completed.wait(completion).map_err(|_| {
177                Error::runtime_state(
178                    "ExecutionHandle::wait",
179                    ErrorPhase::Execution,
180                    "in-flight completion lock poisoned while waiting",
181                )
182            })?;
183        }
184    }
185
186    fn is_pending(&self) -> bool {
187        self.completion
188            .lock()
189            .map_or(true, |completion| completion.is_none())
190    }
191}
192
193pub(super) trait SubmissionSpawner {
194    fn spawn(&self, submission: Arc<InFlightSubmission>) -> std::io::Result<()>;
195}
196
197pub(super) fn spawn_in_flight(
198    submission: Arc<InFlightSubmission>,
199    spawner: &dyn SubmissionSpawner,
200) -> Result<ExecutionHandle> {
201    spawner.spawn(Arc::clone(&submission)).map_err(|source| {
202        Error::runtime_state_source(
203            "Runtime::submit",
204            ErrorPhase::Execution,
205            SubmissionError::WorkerSpawn { source },
206        )
207    })?;
208    Ok(ExecutionHandle { submission })
209}
210
211pub(super) struct OsThreadSpawner;
212
213impl SubmissionSpawner for OsThreadSpawner {
214    fn spawn(&self, submission: Arc<InFlightSubmission>) -> std::io::Result<()> {
215        thread::Builder::new()
216            .name("tenferro-runtime-submit".to_string())
217            .spawn(move || submission.run())
218            .map(drop)
219    }
220}
221
222impl fmt::Debug for PreparedCompiledGraph {
223    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
224        formatter
225            .debug_struct("PreparedCompiledGraph")
226            .field("runtime_id", &self.runtime_id)
227            .field("epoch", &self.epoch)
228            .field("program", &self.program)
229            .finish_non_exhaustive()
230    }
231}
232
233fn panic_payload_message(payload: Box<dyn std::any::Any + Send + 'static>) -> String {
234    if let Some(message) = payload.downcast_ref::<&str>() {
235        format!("submitted execution panicked: {message}")
236    } else if let Some(message) = payload.downcast_ref::<String>() {
237        format!("submitted execution panicked: {message}")
238    } else {
239        "submitted execution panicked".to_string()
240    }
241}
242
243#[allow(
244    dead_code,
245    reason = "Phase 5 runtime execution task adds erased dispatch methods"
246)]
247pub(super) trait ErasedTensorBackendExecutor: fmt::Debug + Send + Sync {
248    fn backend_type_name(&self) -> &'static str;
249    fn extension_cache_stats(&self) -> std::result::Result<CacheStats, CacheOwnerError>;
250    fn clear_extension_caches(&self) -> std::result::Result<(), CacheOwnerError>;
251    fn execute(
252        &self,
253        program: &ExecProgram,
254        operations: &[PreparedOperationPlan],
255        inputs: Vec<Tensor>,
256    ) -> Result<Vec<Tensor>>;
257    fn execute_tensor_refs(
258        &self,
259        program: &ExecProgram,
260        operations: &[PreparedOperationPlan],
261        inputs: &[&Tensor],
262    ) -> Result<Vec<Tensor>>;
263    fn execute_values(
264        &self,
265        program: &ExecProgram,
266        operations: &[PreparedOperationPlan],
267        inputs: Vec<Tensor>,
268    ) -> Result<Vec<TensorValue>>;
269    fn execute_value_refs(
270        &self,
271        program: &ExecProgram,
272        operations: &[PreparedOperationPlan],
273        inputs: &[&Tensor],
274    ) -> Result<Vec<TensorValue>>;
275    fn execute_slot_instruction<'input>(
276        &self,
277        instruction_index: usize,
278        instruction: &ExecInstruction,
279        operations: &[PreparedOperationPlan],
280        slots: &mut [Option<ExecSlot<'input>>],
281        output_mode: RuntimeOutputMode,
282        terminal_slots: &[bool],
283    ) -> Result<()>;
284    fn materialize_slot<'input>(&self, slot: ExecSlot<'input>) -> Result<Tensor>;
285    fn materialize_slot_value<'input>(&self, slot: ExecSlot<'input>) -> Result<TensorValue>;
286}
287
288pub(super) fn erased_tensor_backend_executor<B>(backend: B) -> Arc<dyn ErasedTensorBackendExecutor>
289where
290    B: TensorBackend + Send + Sync + 'static,
291{
292    Arc::new(TensorBackendExecutor::<B>::new(backend))
293}
294
295pub(super) fn extension_cache_owner(
296    executor: Arc<dyn ErasedTensorBackendExecutor>,
297) -> Arc<dyn RuntimeCacheOwner> {
298    Arc::new(TensorBackendExtensionCacheOwner { executor })
299}
300
301#[derive(Debug)]
302struct TensorBackendExtensionCacheOwner {
303    executor: Arc<dyn ErasedTensorBackendExecutor>,
304}
305
306impl RuntimeCacheOwner for TensorBackendExtensionCacheOwner {
307    fn cache_stats(&self) -> std::result::Result<CacheStats, CacheOwnerError> {
308        self.executor.extension_cache_stats()
309    }
310
311    fn clear_caches(&self) -> std::result::Result<(), CacheOwnerError> {
312        self.executor.clear_extension_caches()
313    }
314}
315
316#[allow(
317    dead_code,
318    reason = "Phase 5 runtime execution task consumes backend execution state"
319)]
320struct TensorBackendExecutorState<B: TensorBackend + 'static> {
321    backend: B,
322    backend_cache: B::RuntimeCache,
323    extension_caches: ExtensionCacheStore,
324    slot_workspace: Vec<Option<ExecSlot<'static>>>,
325    borrowed_slot_workspace_capacity: usize,
326}
327
328struct TensorBackendExecutor<B: TensorBackend + 'static> {
329    state: Mutex<TensorBackendExecutorSlot<B>>,
330    available: Condvar,
331}
332
333struct TensorBackendExecutorSlot<B: TensorBackend + 'static> {
334    state: Option<TensorBackendExecutorState<B>>,
335    active_thread: Option<ThreadId>,
336    execution_poisoned: bool,
337}
338
339impl<B> TensorBackendExecutor<B>
340where
341    B: TensorBackend + Send + Sync + 'static,
342{
343    fn new(backend: B) -> Self {
344        Self {
345            state: Mutex::new(TensorBackendExecutorSlot {
346                state: Some(TensorBackendExecutorState {
347                    backend,
348                    backend_cache: B::RuntimeCache::default(),
349                    extension_caches: ExtensionCacheStore::new(),
350                    slot_workspace: Vec::new(),
351                    borrowed_slot_workspace_capacity: 0,
352                }),
353                active_thread: None,
354                execution_poisoned: false,
355            }),
356            available: Condvar::new(),
357        }
358    }
359
360    fn lease_state(&self, caller: &'static str) -> Result<TensorBackendExecutorLease<'_, B>> {
361        let current = thread::current().id();
362        let mut slot = self.lock_slot(caller)?;
363        loop {
364            if slot.execution_poisoned {
365                return Err(Error::runtime_state(
366                    caller,
367                    ErrorPhase::Execution,
368                    "tensor backend executor state poisoned by panic during prior execution",
369                ));
370            }
371            if let Some(state) = slot.state.take() {
372                slot.active_thread = Some(current);
373                return Ok(TensorBackendExecutorLease {
374                    executor: self,
375                    state: Some(state),
376                });
377            }
378            if slot.active_thread == Some(current) {
379                return Err(Error::runtime_state(
380                    caller,
381                    ErrorPhase::Execution,
382                    "reentrant tensor backend executor call would deadlock",
383                ));
384            }
385            slot = self.available.wait(slot).map_err(|_| {
386                Error::runtime_state(
387                    caller,
388                    ErrorPhase::Execution,
389                    "tensor backend executor state lock poisoned while waiting",
390                )
391            })?;
392        }
393    }
394
395    fn lock_slot(
396        &self,
397        caller: &'static str,
398    ) -> Result<MutexGuard<'_, TensorBackendExecutorSlot<B>>> {
399        self.state.lock().map_err(|_| {
400            Error::runtime_state(
401                caller,
402                ErrorPhase::Execution,
403                "tensor backend executor state lock poisoned",
404            )
405        })
406    }
407}
408
409struct TensorBackendExecutorLease<'a, B: TensorBackend + 'static> {
410    executor: &'a TensorBackendExecutor<B>,
411    state: Option<TensorBackendExecutorState<B>>,
412}
413
414impl<B: TensorBackend + 'static> TensorBackendExecutorLease<'_, B> {
415    fn state_mut(&mut self) -> &mut TensorBackendExecutorState<B> {
416        self.state
417            .as_mut()
418            .expect("executor state lease always owns state before drop")
419    }
420}
421
422impl<B: TensorBackend + 'static> Drop for TensorBackendExecutorLease<'_, B> {
423    fn drop(&mut self) {
424        let Some(state) = self.state.take() else {
425            return;
426        };
427        let panicking = thread::panicking();
428        let mut wake_all_waiters = panicking;
429        match self.executor.state.lock() {
430            Ok(mut slot) => {
431                slot.state = Some(state);
432                slot.active_thread = None;
433                slot.execution_poisoned |= panicking;
434            }
435            Err(poisoned_lock) => {
436                let mut slot = poisoned_lock.into_inner();
437                slot.state = Some(state);
438                slot.active_thread = None;
439                slot.execution_poisoned = true;
440                wake_all_waiters = true;
441            }
442        }
443        if wake_all_waiters {
444            self.executor.available.notify_all();
445        } else {
446            self.executor.available.notify_one();
447        }
448    }
449}
450
451impl<B: TensorBackend + 'static> fmt::Debug for TensorBackendExecutor<B> {
452    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
453        formatter
454            .debug_struct("TensorBackendExecutor")
455            .field("backend_type", &std::any::type_name::<B>())
456            .field("state_lock_poisoned", &self.state.is_poisoned())
457            .finish_non_exhaustive()
458    }
459}
460
461impl<B> ErasedTensorBackendExecutor for TensorBackendExecutor<B>
462where
463    B: TensorBackend + Send + Sync + 'static,
464{
465    fn backend_type_name(&self) -> &'static str {
466        std::any::type_name::<B>()
467    }
468
469    fn extension_cache_stats(&self) -> std::result::Result<CacheStats, CacheOwnerError> {
470        let lease = self
471            .lease_state("Runtime::extension_cache_stats")
472            .map_err(cache_owner_error)?;
473        let state = lease
474            .state
475            .as_ref()
476            .expect("executor state lease always owns state before drop");
477        Ok(cache_stats_from_tensor_stats(
478            state.extension_caches.stats(ExtensionCacheSelector::All),
479        ))
480    }
481
482    fn clear_extension_caches(&self) -> std::result::Result<(), CacheOwnerError> {
483        let mut lease = self
484            .lease_state("Runtime::clear_extension_caches")
485            .map_err(cache_owner_error)?;
486        lease.state_mut().extension_caches.clear();
487        Ok(())
488    }
489
490    fn execute(
491        &self,
492        program: &ExecProgram,
493        operations: &[PreparedOperationPlan],
494        inputs: Vec<Tensor>,
495    ) -> Result<Vec<Tensor>> {
496        validate_exec_input_count(program, inputs.len())?;
497        let mut lease = self.lease_state("Runtime::run_compiled")?;
498        let TensorBackendExecutorState {
499            backend,
500            backend_cache,
501            extension_caches,
502            slot_workspace,
503            borrowed_slot_workspace_capacity: _,
504        } = lease.state_mut();
505        let mut extension_dispatch = ExtensionExecutionDispatch {
506            operations,
507            caches: extension_caches,
508        };
509        crate::segment::eval_exec_segmented_with_cache_and_workspace(
510            backend,
511            program,
512            inputs,
513            slot_workspace,
514            backend_cache,
515            Some(&mut extension_dispatch),
516        )
517    }
518
519    fn execute_tensor_refs(
520        &self,
521        program: &ExecProgram,
522        operations: &[PreparedOperationPlan],
523        inputs: &[&Tensor],
524    ) -> Result<Vec<Tensor>> {
525        validate_exec_input_count(program, inputs.len())?;
526        let inputs = inputs
527            .iter()
528            .map(|tensor| ExecSlot::Read(TensorRead::from_tensor(tensor)))
529            .collect();
530        let mut lease = self.lease_state("Runtime::run_compiled")?;
531        let TensorBackendExecutorState {
532            backend,
533            backend_cache,
534            extension_caches,
535            borrowed_slot_workspace_capacity,
536            ..
537        } = lease.state_mut();
538        let mut extension_dispatch = ExtensionExecutionDispatch {
539            operations,
540            caches: extension_caches,
541        };
542        let mut slot_workspace = Vec::with_capacity(*borrowed_slot_workspace_capacity);
543        let result = crate::segment::eval_exec_segmented_slots_with_cache_and_workspace(
544            backend,
545            program,
546            inputs,
547            &mut slot_workspace,
548            backend_cache,
549            Some(&mut extension_dispatch),
550        );
551        *borrowed_slot_workspace_capacity = slot_workspace.capacity();
552        result
553    }
554
555    fn execute_values(
556        &self,
557        program: &ExecProgram,
558        operations: &[PreparedOperationPlan],
559        inputs: Vec<Tensor>,
560    ) -> Result<Vec<TensorValue>> {
561        validate_exec_input_count(program, inputs.len())?;
562        let inputs = inputs.into_iter().map(ExecSlot::Owned).collect();
563        let mut lease = self.lease_state("Runtime::run_compiled_values")?;
564        let TensorBackendExecutorState {
565            backend,
566            backend_cache,
567            extension_caches,
568            slot_workspace,
569            borrowed_slot_workspace_capacity: _,
570        } = lease.state_mut();
571        let mut extension_dispatch = ExtensionExecutionDispatch {
572            operations,
573            caches: extension_caches,
574        };
575        crate::segment::eval_exec_segmented_slot_values_with_cache_and_workspace(
576            backend,
577            program,
578            inputs,
579            slot_workspace,
580            backend_cache,
581            Some(&mut extension_dispatch),
582        )
583    }
584
585    fn execute_value_refs(
586        &self,
587        program: &ExecProgram,
588        operations: &[PreparedOperationPlan],
589        inputs: &[&Tensor],
590    ) -> Result<Vec<TensorValue>> {
591        validate_exec_input_count(program, inputs.len())?;
592        let inputs = inputs
593            .iter()
594            .map(|tensor| ExecSlot::Read(TensorRead::from_tensor(tensor)))
595            .collect();
596        let mut lease = self.lease_state("Runtime::run_compiled_values")?;
597        let TensorBackendExecutorState {
598            backend,
599            backend_cache,
600            extension_caches,
601            borrowed_slot_workspace_capacity,
602            ..
603        } = lease.state_mut();
604        let mut extension_dispatch = ExtensionExecutionDispatch {
605            operations,
606            caches: extension_caches,
607        };
608        let mut slot_workspace = Vec::with_capacity(*borrowed_slot_workspace_capacity);
609        let result = crate::segment::eval_exec_segmented_slot_values_with_cache_and_workspace(
610            backend,
611            program,
612            inputs,
613            &mut slot_workspace,
614            backend_cache,
615            Some(&mut extension_dispatch),
616        );
617        *borrowed_slot_workspace_capacity = slot_workspace.capacity();
618        result
619    }
620
621    fn execute_slot_instruction<'input>(
622        &self,
623        instruction_index: usize,
624        instruction: &ExecInstruction,
625        operations: &[PreparedOperationPlan],
626        slots: &mut [Option<ExecSlot<'input>>],
627        output_mode: RuntimeOutputMode,
628        terminal_slots: &[bool],
629    ) -> Result<()> {
630        let mut lease = self.lease_state("Runtime::run_compiled scheduled instruction")?;
631        let TensorBackendExecutorState {
632            backend,
633            backend_cache,
634            extension_caches,
635            ..
636        } = lease.state_mut();
637        let mut extension_dispatch = ExtensionExecutionDispatch {
638            operations,
639            caches: extension_caches,
640        };
641
642        if matches!(output_mode, RuntimeOutputMode::Value)
643            && backend.with_backend_session(|exec| {
644                crate::exec::try_execute_terminal_value_instruction(
645                    exec,
646                    slots,
647                    instruction,
648                    terminal_slots,
649                )
650            })?
651        {
652            // Already handled as a metadata-only TensorValue.
653        } else if crate::exec::is_host_instruction(instruction) {
654            crate::exec::execute_host_instruction(backend, slots, instruction)?;
655        } else if crate::exec::is_ffi_instruction(instruction) {
656            crate::exec::execute_ffi_instruction_cached(
657                backend,
658                backend_cache,
659                slots,
660                instruction,
661                DispatchMode::Unsegmented,
662                Some(instruction_index),
663                Some(&mut extension_dispatch),
664            )?;
665        } else {
666            let result = backend.with_backend_session(|exec| {
667                crate::exec::execute_backend_op(exec, slots, instruction)
668            })?;
669            slots[instruction.output_slots[0]] = Some(ExecSlot::Owned(result));
670        }
671        crate::exec::reclaim_last_use_inputs_backend(slots, instruction, backend);
672        Ok(())
673    }
674
675    fn materialize_slot<'input>(&self, slot: ExecSlot<'input>) -> Result<Tensor> {
676        let mut lease = self.lease_state("Runtime::run_compiled collect outputs")?;
677        let backend = &mut lease.state_mut().backend;
678        backend.with_backend_session(|exec| slot.into_tensor(exec))
679    }
680
681    fn materialize_slot_value<'input>(&self, slot: ExecSlot<'input>) -> Result<TensorValue> {
682        let mut lease = self.lease_state("Runtime::run_compiled_values collect outputs")?;
683        let backend = &mut lease.state_mut().backend;
684        backend.with_backend_session(|exec| slot.into_value(exec))
685    }
686}
687
688fn cache_owner_error(error: Error) -> CacheOwnerError {
689    CacheOwnerError::new(Arc::new(error))
690}
691
692fn cache_stats_from_tensor_stats(stats: tenferro_tensor::CacheStats) -> CacheStats {
693    CacheStats {
694        entries: stats.entries,
695        retained_bytes: stats.retained_bytes,
696        hits: stats.hits,
697        misses: stats.misses,
698        evictions: stats.evictions,
699        clears: stats.clears,
700    }
701}
702
703pub(super) fn run_compiled(
704    runtime: &Runtime,
705    program: &CompiledGraph,
706    inputs: &[&Tensor],
707) -> Result<Vec<Tensor>> {
708    let inputs = resolve_input_refs(program, inputs)?;
709    let signature = input_signature(&inputs)?;
710    let prepared = prepare(runtime, program, &signature)?;
711    validate_prepared_epoch(runtime, prepared.root().epoch(), "Runtime::run_compiled")?;
712    execute_scheduled_tensor_refs(
713        prepared.root().staging(),
714        prepared.root().schedule(),
715        prepared.operations(),
716        &inputs,
717    )
718}
719
720pub(super) fn prepare_compiled(
721    runtime: &Runtime,
722    program: &CompiledGraph,
723    inputs: &[&Tensor],
724) -> Result<PreparedCompiledGraph> {
725    let inputs = resolve_input_refs(program, inputs)?;
726    let signature = input_signature(&inputs)?;
727    let prepared = prepare(runtime, program, &signature)?;
728    validate_prepared_epoch(
729        runtime,
730        prepared.root().epoch(),
731        "Runtime::prepare_compiled",
732    )?;
733    Ok(PreparedCompiledGraph {
734        runtime_id: runtime.id(),
735        epoch: prepared.root().epoch(),
736        program: program.clone(),
737        prepared,
738    })
739}
740
741pub(super) fn submit(
742    runtime: &Runtime,
743    program: &CompiledGraph,
744    inputs: &[&Tensor],
745) -> Result<ExecutionHandle> {
746    submit_with_spawner(runtime, program, inputs, &OsThreadSpawner)
747}
748
749pub(super) fn submit_with_spawner(
750    runtime: &Runtime,
751    program: &CompiledGraph,
752    inputs: &[&Tensor],
753    spawner: &dyn SubmissionSpawner,
754) -> Result<ExecutionHandle> {
755    let inputs = resolve_input_refs(program, inputs)?
756        .into_iter()
757        .cloned()
758        .collect::<Vec<_>>();
759    let input_refs = inputs.iter().collect::<Vec<_>>();
760    let prepared = prepare_compiled(runtime, program, &input_refs)?;
761    let submission = Arc::new(InFlightSubmission::new(prepared, inputs));
762    spawn_in_flight(submission, spawner)
763}
764
765pub(super) fn run_prepared(
766    runtime: &Runtime,
767    prepared: &PreparedCompiledGraph,
768    inputs: &[&Tensor],
769) -> Result<Vec<Tensor>> {
770    validate_prepared_runtime(runtime, prepared, "Runtime::run_prepared")?;
771    let inputs = resolve_input_refs(&prepared.program, inputs)?;
772    execute_scheduled_tensor_refs(
773        prepared.prepared.root().staging(),
774        prepared.prepared.root().schedule(),
775        prepared.prepared.operations(),
776        &inputs,
777    )
778}
779
780fn execute_admitted(prepared: &PreparedCompiledGraph, inputs: &[&Tensor]) -> Result<Vec<Tensor>> {
781    let inputs = resolve_input_refs(&prepared.program, inputs)?;
782    execute_scheduled_tensor_refs(
783        prepared.prepared.root().staging(),
784        prepared.prepared.root().schedule(),
785        prepared.prepared.operations(),
786        &inputs,
787    )
788}
789
790pub(super) fn run_compiled_values(
791    runtime: &Runtime,
792    program: &CompiledGraph,
793    inputs: &[&Tensor],
794) -> Result<Vec<TensorValue>> {
795    let inputs = resolve_input_refs(program, inputs)?;
796    let signature = input_signature(&inputs)?;
797    let prepared = prepare(runtime, program, &signature)?;
798    validate_prepared_epoch(
799        runtime,
800        prepared.root().epoch(),
801        "Runtime::run_compiled_values",
802    )?;
803    execute_scheduled_value_refs(
804        prepared.root().staging(),
805        prepared.root().schedule(),
806        prepared.operations(),
807        &inputs,
808    )
809}
810
811fn validate_prepared_runtime(
812    runtime: &Runtime,
813    prepared: &PreparedCompiledGraph,
814    caller: &'static str,
815) -> Result<()> {
816    if runtime.id() != prepared.runtime_id {
817        return Err(Error::runtime_state(
818            caller,
819            ErrorPhase::Execution,
820            "prepared compiled graph belongs to a different runtime",
821        ));
822    }
823    let epoch = runtime
824        .epoch()
825        .map_err(|source| Error::runtime_state_source(caller, ErrorPhase::Execution, source))?;
826    if epoch != prepared.epoch {
827        return Err(Error::runtime_state(
828            caller,
829            ErrorPhase::Execution,
830            format!(
831                "prepared epoch {:?} does not match current epoch {:?}",
832                prepared.epoch, epoch
833            ),
834        ));
835    }
836    Ok(())
837}
838
839fn prepare(
840    runtime: &Runtime,
841    program: &CompiledGraph,
842    signature: &InputSignature,
843) -> Result<Arc<super::preparation::PreparedProgram>> {
844    runtime
845        .prepare_compiled_for(program, signature, &PrepareOptions::new())
846        .map_err(prepare_error)
847}
848
849fn validate_prepared_epoch(
850    runtime: &Runtime,
851    prepared_epoch: super::RuntimeEpoch,
852    caller: &'static str,
853) -> Result<()> {
854    let epoch = runtime
855        .epoch()
856        .map_err(|source| Error::runtime_state_source(caller, ErrorPhase::Execution, source))?;
857    if epoch != prepared_epoch {
858        return Err(Error::runtime_state(
859            caller,
860            ErrorPhase::Execution,
861            format!(
862                "prepared epoch {:?} does not match current epoch {:?}",
863                prepared_epoch, epoch
864            ),
865        ));
866    }
867    Ok(())
868}
869
870fn execute_scheduled_tensor_refs(
871    program: &ExecProgram,
872    schedule: &ScheduledGraph,
873    operations: &[PreparedOperationPlan],
874    inputs: &[&Tensor],
875) -> Result<Vec<Tensor>> {
876    validate_exec_input_count(program, inputs.len())?;
877    crate::exec::validate_exec_program(program, "scheduled tensor executor")?;
878    let inputs = inputs
879        .iter()
880        .map(|tensor| ExecSlot::Read(TensorRead::from_tensor(tensor)))
881        .collect();
882    execute_scheduled_slots(
883        program,
884        schedule,
885        operations,
886        inputs,
887        RuntimeOutputMode::Tensor,
888    )
889    .and_then(|mut slots| {
890        collect_tensor_outputs_with(program, &mut slots, |location, slot| {
891            location.witness().executor().materialize_slot(slot)
892        })
893    })
894}
895
896fn execute_scheduled_value_refs(
897    program: &ExecProgram,
898    schedule: &ScheduledGraph,
899    operations: &[PreparedOperationPlan],
900    inputs: &[&Tensor],
901) -> Result<Vec<TensorValue>> {
902    validate_exec_input_count(program, inputs.len())?;
903    crate::exec::validate_exec_program(program, "scheduled value executor")?;
904    let inputs = inputs
905        .iter()
906        .map(|tensor| ExecSlot::Read(TensorRead::from_tensor(tensor)))
907        .collect();
908    execute_scheduled_slots(
909        program,
910        schedule,
911        operations,
912        inputs,
913        RuntimeOutputMode::Value,
914    )
915    .and_then(|mut slots| {
916        collect_value_outputs_with(program, &mut slots, |location, slot| {
917            location.witness().executor().materialize_slot_value(slot)
918        })
919    })
920}
921
922fn execute_scheduled_slots<'input>(
923    program: &ExecProgram,
924    schedule: &ScheduledGraph,
925    operations: &[PreparedOperationPlan],
926    inputs: Vec<ExecSlot<'input>>,
927    output_mode: RuntimeOutputMode,
928) -> Result<Vec<Option<LocatedExecSlot<'input>>>> {
929    let terminal_slots = if matches!(output_mode, RuntimeOutputMode::Value) {
930        crate::exec::terminal_output_slots(program)
931    } else {
932        Vec::new()
933    };
934    let mut staged = Vec::new();
935    let mut located = (0..program.n_slots)
936        .map(|_| Vec::new())
937        .collect::<Vec<Vec<LocatedExecSlot<'input>>>>();
938    // INVARIANT: the immutable schedule was validated during preparation;
939    // runs are declared after the value stores so unwinding drops
940    // and drains native domain work before any tensor storage is released.
941    // Driver preflight completes before input ingress or any operation launch.
942    let mut event_domains = ScheduledEventDomains::new(schedule)?;
943    let result = (|| {
944        crate::exec::initialize_exec_slots_in(program, inputs, &mut staged)?;
945        if schedule.input_locations().len() != program.input_slots.len() {
946            return Err(Error::runtime_state(
947                "Runtime::run_compiled",
948                ErrorPhase::Execution,
949                format!(
950                    "prepared schedule has {} input locations for {} inputs",
951                    schedule.input_locations().len(),
952                    program.input_slots.len()
953                ),
954            ));
955        }
956        for (&slot, location) in program.input_slots.iter().zip(schedule.input_locations()) {
957            let value = staged
958                .get_mut(slot)
959                .and_then(Option::take)
960                .ok_or(tenferro_tensor::Error::MissingValue { slot })?;
961            validate_runtime_input_ingress(location, &value.as_read(), slot)?;
962            located[slot].push(LocatedExecSlot {
963                location: location.clone(),
964                value,
965            });
966        }
967        for (node_index, node) in schedule.nodes().iter().enumerate() {
968            match node {
969                ScheduledNode::Operation(operation_node) => {
970                    let mut launch = || {
971                        let instruction_index = operation_node.instruction_index();
972                        let instruction =
973                            program.instructions.get(instruction_index).ok_or_else(|| {
974                                Error::runtime_state(
975                                    "Runtime::run_compiled",
976                                    ErrorPhase::Execution,
977                                    format!(
978                                        "scheduled operation references instruction \
979                                         {instruction_index}, but the execution program has {} \
980                                         instructions",
981                                        program.instructions.len()
982                                    ),
983                                )
984                            })?;
985                        let operation = instruction_execution(schedule, instruction)?;
986                        if operation.location() != operation_node.location() {
987                            return Err(Error::runtime_state(
988                                "Runtime::run_compiled",
989                                ErrorPhase::Execution,
990                                format!(
991                                    "scheduled instruction {instruction_index} location does not \
992                                     match its prepared executor"
993                                ),
994                            ));
995                        }
996                        stage_instruction_inputs(
997                            instruction,
998                            operation.location(),
999                            &mut located,
1000                            &mut staged,
1001                        )?;
1002                        operation.executor().execute_slot_instruction(
1003                            instruction_index,
1004                            instruction,
1005                            operations,
1006                            &mut staged,
1007                            output_mode,
1008                            &terminal_slots,
1009                        )?;
1010                        validate_instruction_outputs(
1011                            instruction_index,
1012                            instruction,
1013                            operation.location(),
1014                            &staged,
1015                        )?;
1016                        retain_instruction_results(
1017                            instruction,
1018                            operation.location(),
1019                            &mut located,
1020                            &mut staged,
1021                        )
1022                    };
1023                    event_domains.enqueue(node_index, node, &mut launch)?;
1024                }
1025                ScheduledNode::Transfer(transfer) => {
1026                    let mut launch = || execute_scheduled_transfer(transfer, &mut located);
1027                    event_domains.enqueue(node_index, node, &mut launch)?;
1028                }
1029                ScheduledNode::Collective(_) => {
1030                    return Err(Error::runtime_state_source(
1031                        "Runtime::run_compiled",
1032                        ErrorPhase::Execution,
1033                        UnsupportedScheduledNodeError {
1034                            node_index,
1035                            node_kind: ScheduledNodeKind::Collective,
1036                        },
1037                    ));
1038                }
1039                ScheduledNode::Barrier(_) => {
1040                    let mut launch = || Ok(());
1041                    event_domains.enqueue(node_index, node, &mut launch)?;
1042                }
1043            }
1044        }
1045        Ok(())
1046    })();
1047    let drain = event_domains.drain();
1048    match (result, drain) {
1049        (Ok(()), Ok(())) => collect_located_outputs(program, &mut located),
1050        (Err(error), Ok(())) | (Ok(()), Err(error)) => {
1051            staged.clear();
1052            located.clear();
1053            Err(error)
1054        }
1055        (Err(primary), Err(cleanup)) => {
1056            staged.clear();
1057            located.clear();
1058            Err(scheduled_execution_cleanup_error(primary, cleanup))
1059        }
1060    }
1061}
1062
1063#[derive(Debug)]
1064pub(crate) struct ScheduledEventDomains {
1065    runs: Vec<RuntimeOwnedEventDomainRun>,
1066    completions: HashMap<EventDependency, Arc<dyn EventToken>>,
1067}
1068
1069#[derive(Debug, thiserror::Error)]
1070#[error(
1071    "scheduled node {node_index} depends on completion {dependency:?}, but no completion token was recorded"
1072)]
1073pub(crate) struct MissingScheduledDependencyCompletionError {
1074    pub(crate) dependency: EventDependency,
1075    pub(crate) node_index: usize,
1076}
1077
1078#[derive(Debug, thiserror::Error)]
1079#[error("scheduled transfer destination {destination:?} already contains value slot {value_slot}")]
1080pub(crate) struct DuplicateTransferDestinationError {
1081    pub(crate) value_slot: usize,
1082    pub(crate) destination: ExecutionLocation,
1083}
1084
1085impl ScheduledEventDomains {
1086    pub(crate) fn new(schedule: &ScheduledGraph) -> Result<Self> {
1087        schedule.preflight().map_err(|source| {
1088            Error::runtime_state_source("Runtime::run_compiled", ErrorPhase::Execution, source)
1089        })?;
1090        let mut drivers = Vec::new();
1091        let mut seen_domains = HashSet::new();
1092        for node in schedule.nodes() {
1093            let domain = node.completion().domain();
1094            if !seen_domains.insert(domain) {
1095                continue;
1096            }
1097            let witness = node.event_domain_witness();
1098            debug_assert_eq!(witness.event_domain_id(), domain);
1099            drivers.push((domain, witness.event_domain_driver().clone()));
1100        }
1101        let mut runs = Vec::with_capacity(drivers.len());
1102        for (domain, driver) in drivers {
1103            let run = RuntimeOwnedEventDomainRun::new(domain, driver.begin_run(domain)?);
1104            let actual = run.domain(EventDomainOperation::BeginRun)?;
1105            if actual != domain {
1106                return Err(event_domain_error(EventDomainError::RunDomainMismatch {
1107                    operation: EventDomainOperation::BeginRun,
1108                    node_index: None,
1109                    expected: domain,
1110                    actual,
1111                }));
1112            }
1113            runs.push(run);
1114        }
1115        Ok(Self {
1116            runs,
1117            completions: HashMap::new(),
1118        })
1119    }
1120
1121    #[cfg(test)]
1122    pub(crate) fn for_test(
1123        drivers: Vec<(super::EventDomainId, Arc<dyn super::EventDomainDriver>)>,
1124    ) -> Result<Self> {
1125        let mut runs = Vec::with_capacity(drivers.len());
1126        for (domain, driver) in drivers {
1127            let run = RuntimeOwnedEventDomainRun::new(domain, driver.begin_run(domain)?);
1128            let actual = run.domain(EventDomainOperation::BeginRun)?;
1129            if actual != domain {
1130                return Err(event_domain_error(EventDomainError::RunDomainMismatch {
1131                    operation: EventDomainOperation::BeginRun,
1132                    node_index: None,
1133                    expected: domain,
1134                    actual,
1135                }));
1136            }
1137            runs.push(run);
1138        }
1139        Ok(Self {
1140            runs,
1141            completions: HashMap::new(),
1142        })
1143    }
1144
1145    pub(crate) fn enqueue(
1146        &mut self,
1147        node_index: usize,
1148        node: &ScheduledNode,
1149        launch: &mut dyn FnMut() -> Result<()>,
1150    ) -> Result<()> {
1151        let completion = node.completion();
1152        let destination = completion.domain();
1153        let run_index = self.run_index(completion.domain())?;
1154        let actual_preflight_domain = self.runs[run_index].domain(EventDomainOperation::Enqueue)?;
1155        if actual_preflight_domain != destination {
1156            return Err(event_domain_error(EventDomainError::RunDomainMismatch {
1157                operation: EventDomainOperation::Enqueue,
1158                node_index: Some(node_index),
1159                expected: destination,
1160                actual: actual_preflight_domain,
1161            }));
1162        }
1163        let dependencies = self.classify_dependencies(node_index, node, destination)?;
1164        let actual_run_domain = self.runs[run_index].domain(EventDomainOperation::Enqueue)?;
1165        if actual_run_domain != destination {
1166            return Err(event_domain_error(EventDomainError::RunDomainMismatch {
1167                operation: EventDomainOperation::Enqueue,
1168                node_index: Some(node_index),
1169                expected: destination,
1170                actual: actual_run_domain,
1171            }));
1172        }
1173        let completion_event = self.runs[run_index].enqueue(&dependencies, launch)?;
1174        let actual = completion_event.origin();
1175        if actual != completion.domain() {
1176            return Err(event_domain_error(
1177                EventDomainError::CompletionTokenDomainMismatch {
1178                    operation: EventDomainOperation::ValidateCompletion,
1179                    node_index: Some(node_index),
1180                    expected: completion.domain(),
1181                    actual,
1182                },
1183            ));
1184        }
1185        self.completions.insert(
1186            EventDependency::from_completion(completion),
1187            completion_event,
1188        );
1189        Ok(())
1190    }
1191
1192    fn classify_dependencies(
1193        &self,
1194        node_index: usize,
1195        node: &ScheduledNode,
1196        destination: super::EventDomainId,
1197    ) -> Result<SmallVec<[Arc<dyn EventToken>; 4]>> {
1198        let mut admitted = SmallVec::with_capacity(node.dependencies().len());
1199        for dependency in node.dependencies() {
1200            let dependency_completion =
1201                self.completions.get(dependency).cloned().ok_or_else(|| {
1202                    Error::runtime_state_source(
1203                        "Runtime::run_compiled",
1204                        ErrorPhase::Execution,
1205                        MissingScheduledDependencyCompletionError {
1206                            dependency: *dependency,
1207                            node_index,
1208                        },
1209                    )
1210                })?;
1211            let actual = dependency_completion.origin();
1212            if actual != dependency.domain() {
1213                return Err(event_domain_error(
1214                    EventDomainError::DependencyDomainMismatch {
1215                        operation: match node {
1216                            ScheduledNode::Transfer(_) => EventDomainOperation::TransferBridge,
1217                            ScheduledNode::Operation(_)
1218                            | ScheduledNode::Collective(_)
1219                            | ScheduledNode::Barrier(_) => EventDomainOperation::Enqueue,
1220                        },
1221                        node_index: Some(node_index),
1222                        expected: dependency.domain(),
1223                        actual,
1224                    },
1225                ));
1226            }
1227            match node {
1228                ScheduledNode::Transfer(transfer) => {
1229                    let source = transfer.source_event_domain();
1230                    if actual == destination {
1231                        admitted.push(dependency_completion);
1232                    } else if actual == source {
1233                        dependency_completion.wait().map_err(|source_error| {
1234                            event_domain_error(EventDomainError::DependencyWaitFailed {
1235                                operation: EventDomainOperation::TransferBridge,
1236                                node_index: Some(node_index),
1237                                expected: destination,
1238                                actual,
1239                                source: Box::new(source_error),
1240                            })
1241                        })?;
1242                    } else {
1243                        return Err(event_domain_error(
1244                            EventDomainError::DependencyDomainMismatch {
1245                                operation: EventDomainOperation::TransferBridge,
1246                                node_index: Some(node_index),
1247                                expected: source,
1248                                actual,
1249                            },
1250                        ));
1251                    }
1252                }
1253                ScheduledNode::Operation(_)
1254                | ScheduledNode::Collective(_)
1255                | ScheduledNode::Barrier(_) => {
1256                    if actual != destination {
1257                        return Err(event_domain_error(
1258                            EventDomainError::DependencyDomainMismatch {
1259                                operation: EventDomainOperation::Enqueue,
1260                                node_index: Some(node_index),
1261                                expected: destination,
1262                                actual,
1263                            },
1264                        ));
1265                    }
1266                    admitted.push(dependency_completion);
1267                }
1268            }
1269        }
1270        Ok(admitted)
1271    }
1272
1273    fn run_index(&self, domain: super::EventDomainId) -> Result<usize> {
1274        if let Some(index) = self
1275            .runs
1276            .iter()
1277            .position(|run| run.requested_domain() == domain)
1278        {
1279            return Ok(index);
1280        }
1281        Err(missing_event_domain_driver(domain))
1282    }
1283
1284    pub(crate) fn drain(&mut self) -> Result<()> {
1285        let mut failures = Vec::new();
1286        for run in &mut self.runs {
1287            if let Err(error) = run.drain() {
1288                failures.push(error);
1289            }
1290        }
1291        let mut failures = failures.into_iter();
1292        let Some(mut error) = failures.next() else {
1293            return Ok(());
1294        };
1295        for failure in failures {
1296            error = Error::with_suppressed(error, failure);
1297        }
1298        Err(error)
1299    }
1300}
1301
1302#[derive(Debug)]
1303enum RuntimeOwnedEventDomainRunState {
1304    Pending(Box<dyn EventDomainRun>),
1305    Retired,
1306    Failed,
1307}
1308
1309#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1310enum EventDomainRunTerminalState {
1311    Retired,
1312    Failed,
1313}
1314
1315impl fmt::Display for EventDomainRunTerminalState {
1316    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1317        match self {
1318            Self::Retired => formatter.write_str("retired"),
1319            Self::Failed => formatter.write_str("failed"),
1320        }
1321    }
1322}
1323
1324#[derive(Debug)]
1325struct RuntimeOwnedEventDomainRun {
1326    requested_domain: super::EventDomainId,
1327    state: RuntimeOwnedEventDomainRunState,
1328}
1329
1330impl RuntimeOwnedEventDomainRun {
1331    fn new(requested_domain: super::EventDomainId, inner: Box<dyn EventDomainRun>) -> Self {
1332        Self {
1333            requested_domain,
1334            state: RuntimeOwnedEventDomainRunState::Pending(inner),
1335        }
1336    }
1337
1338    fn requested_domain(&self) -> super::EventDomainId {
1339        self.requested_domain
1340    }
1341
1342    fn domain(&self, operation: EventDomainOperation) -> Result<super::EventDomainId> {
1343        match &self.state {
1344            RuntimeOwnedEventDomainRunState::Pending(run) => Ok(run.domain()),
1345            RuntimeOwnedEventDomainRunState::Retired => Err(event_domain_run_state_error(
1346                operation,
1347                self.requested_domain,
1348                EventDomainRunTerminalState::Retired,
1349            )),
1350            RuntimeOwnedEventDomainRunState::Failed => Err(event_domain_run_state_error(
1351                operation,
1352                self.requested_domain,
1353                EventDomainRunTerminalState::Failed,
1354            )),
1355        }
1356    }
1357
1358    fn enqueue(
1359        &mut self,
1360        dependencies: &[Arc<dyn EventToken>],
1361        launch: &mut dyn FnMut() -> Result<()>,
1362    ) -> Result<Arc<dyn EventToken>> {
1363        match &mut self.state {
1364            RuntimeOwnedEventDomainRunState::Pending(run) => run.enqueue(dependencies, launch),
1365            RuntimeOwnedEventDomainRunState::Retired => Err(event_domain_run_state_error(
1366                EventDomainOperation::Enqueue,
1367                self.requested_domain,
1368                EventDomainRunTerminalState::Retired,
1369            )),
1370            RuntimeOwnedEventDomainRunState::Failed => Err(event_domain_run_state_error(
1371                EventDomainOperation::Enqueue,
1372                self.requested_domain,
1373                EventDomainRunTerminalState::Failed,
1374            )),
1375        }
1376    }
1377
1378    fn drain(&mut self) -> Result<()> {
1379        let run = match std::mem::replace(&mut self.state, RuntimeOwnedEventDomainRunState::Failed)
1380        {
1381            RuntimeOwnedEventDomainRunState::Pending(run) => run,
1382            RuntimeOwnedEventDomainRunState::Retired => {
1383                self.state = RuntimeOwnedEventDomainRunState::Retired;
1384                return Err(event_domain_run_state_error(
1385                    EventDomainOperation::Drain,
1386                    self.requested_domain,
1387                    EventDomainRunTerminalState::Retired,
1388                ));
1389            }
1390            RuntimeOwnedEventDomainRunState::Failed => {
1391                self.state = RuntimeOwnedEventDomainRunState::Failed;
1392                return Err(event_domain_run_state_error(
1393                    EventDomainOperation::Drain,
1394                    self.requested_domain,
1395                    EventDomainRunTerminalState::Failed,
1396                ));
1397            }
1398        };
1399        let domain = self.requested_domain;
1400        let mut run = run;
1401        let drain_result = catch_unwind(AssertUnwindSafe(|| run.drain()));
1402        drop_event_domain_run(run);
1403        match drain_result {
1404            Ok(Ok(())) => {
1405                self.state = RuntimeOwnedEventDomainRunState::Retired;
1406                Ok(())
1407            }
1408            Ok(Err(error)) => {
1409                self.state = RuntimeOwnedEventDomainRunState::Failed;
1410                Err(error)
1411            }
1412            Err(payload) => {
1413                self.state = RuntimeOwnedEventDomainRunState::Failed;
1414                Err(event_domain_error(EventDomainError::DrainPanicked {
1415                    operation: EventDomainOperation::Drain,
1416                    domain,
1417                    message: safe_event_domain_panic_message(payload),
1418                }))
1419            }
1420        }
1421    }
1422}
1423
1424/// Internal error returned when a runtime-owned event run is used after
1425/// explicit retirement or failure.
1426#[derive(Debug, thiserror::Error)]
1427#[error("{operation} used event-domain run {domain:?} after it reached terminal state {state}")]
1428pub(crate) struct EventDomainRunLifecycleError {
1429    operation: EventDomainOperation,
1430    domain: super::EventDomainId,
1431    state: EventDomainRunTerminalState,
1432}
1433
1434fn event_domain_run_state_error(
1435    operation: EventDomainOperation,
1436    domain: super::EventDomainId,
1437    state: EventDomainRunTerminalState,
1438) -> Error {
1439    Error::runtime_state_source(
1440        "Runtime::run_compiled",
1441        ErrorPhase::Execution,
1442        EventDomainRunLifecycleError {
1443            operation,
1444            domain,
1445            state,
1446        },
1447    )
1448}
1449
1450fn drop_event_domain_run(run: Box<dyn EventDomainRun>) {
1451    if catch_unwind(AssertUnwindSafe(|| drop(run))).is_err() {
1452        // The provider run has been consumed; its cleanup panic is contained.
1453    }
1454}
1455
1456impl Drop for RuntimeOwnedEventDomainRun {
1457    fn drop(&mut self) {
1458        let state = std::mem::replace(&mut self.state, RuntimeOwnedEventDomainRunState::Failed);
1459        if let RuntimeOwnedEventDomainRunState::Pending(run) = state {
1460            drop_event_domain_run(run);
1461        }
1462    }
1463}
1464
1465fn missing_event_domain_driver(domain: super::EventDomainId) -> Error {
1466    Error::from(EventDomainError::MissingDriver { domain })
1467}
1468
1469fn event_domain_error(source: EventDomainError) -> Error {
1470    Error::from(source)
1471}
1472
1473fn safe_event_domain_panic_message(payload: Box<dyn std::any::Any + Send + 'static>) -> String {
1474    match payload.downcast::<&'static str>() {
1475        Ok(message) => (*message).to_owned(),
1476        Err(payload) => match payload.downcast::<String>() {
1477            Ok(message) => *message,
1478            Err(_) => "non-string panic payload".to_owned(),
1479        },
1480    }
1481}
1482
1483fn scheduled_execution_cleanup_error(primary: Error, cleanup: Error) -> Error {
1484    Error::with_suppressed(primary, cleanup)
1485}
1486
1487fn instruction_execution<'a>(
1488    schedule: &'a ScheduledGraph,
1489    instruction: &ExecInstruction,
1490) -> Result<InstructionExecution<'a>> {
1491    let Some(operation_index) = instruction.semantic_operation_index else {
1492        let location = schedule.root_location();
1493        return Ok(InstructionExecution {
1494            witness: location.witness(),
1495            location,
1496        });
1497    };
1498    let location = schedule
1499        .operation_locations()
1500        .get(operation_index)
1501        .ok_or_else(|| {
1502            Error::runtime_state(
1503                "Runtime::run_compiled",
1504                ErrorPhase::Execution,
1505                format!(
1506                    "instruction references semantic operation {operation_index}, but prepared schedule has {} operations",
1507                    schedule.operation_locations().len()
1508                ),
1509            )
1510        })?;
1511    Ok(InstructionExecution {
1512        witness: location.witness(),
1513        location,
1514    })
1515}
1516
1517struct InstructionExecution<'a> {
1518    witness: &'a super::snapshot::ExecutableEngineSnapshot,
1519    location: &'a ExecutionLocation,
1520}
1521
1522impl InstructionExecution<'_> {
1523    fn executor(&self) -> &Arc<dyn ErasedTensorBackendExecutor> {
1524        self.witness.executor()
1525    }
1526
1527    fn location(&self) -> &ExecutionLocation {
1528        self.location
1529    }
1530}
1531
1532pub(crate) struct LocatedExecSlot<'input> {
1533    pub(crate) location: ExecutionLocation,
1534    pub(crate) value: ExecSlot<'input>,
1535}
1536
1537fn stage_instruction_inputs<'input>(
1538    instruction: &ExecInstruction,
1539    location: &ExecutionLocation,
1540    located: &mut [Vec<LocatedExecSlot<'input>>],
1541    staged: &mut [Option<ExecSlot<'input>>],
1542) -> Result<()> {
1543    for &slot in &instruction.input_slots {
1544        if staged
1545            .get(slot)
1546            .ok_or(tenferro_tensor::Error::MissingValue { slot })?
1547            .is_some()
1548        {
1549            continue;
1550        }
1551        let values = located
1552            .get_mut(slot)
1553            .ok_or(tenferro_tensor::Error::MissingValue { slot })?;
1554        let value_index = values
1555            .iter()
1556            .position(|value| &value.location == location)
1557            .ok_or(tenferro_tensor::Error::MissingValue { slot })?;
1558        staged[slot] = Some(values.swap_remove(value_index).value);
1559    }
1560    Ok(())
1561}
1562
1563fn validate_instruction_outputs(
1564    instruction_index: usize,
1565    instruction: &ExecInstruction,
1566    location: &ExecutionLocation,
1567    staged: &[Option<ExecSlot<'_>>],
1568) -> Result<()> {
1569    for &output_slot in &instruction.output_slots {
1570        let output = staged
1571            .get(output_slot)
1572            .and_then(Option::as_ref)
1573            .ok_or(tenferro_tensor::Error::MissingValue { slot: output_slot })?;
1574        let output = output.as_read();
1575        if !location
1576            .witness()
1577            .owns_resident_tensor(&output, location.storage_class())
1578        {
1579            return Err(Error::runtime_state_source(
1580                "Runtime::run_compiled",
1581                ErrorPhase::Execution,
1582                super::EngineExecutionContractError::OutputResidencyMismatch {
1583                    instruction_index,
1584                    output_slot,
1585                    engine_id: location.engine_id().clone(),
1586                    storage_class: location.storage_class().clone(),
1587                    backend_family: output.backend_family(),
1588                    allocation_domain: output.allocation_domain(),
1589                },
1590            ));
1591        }
1592    }
1593    Ok(())
1594}
1595
1596pub(crate) fn retain_instruction_results<'input>(
1597    instruction: &ExecInstruction,
1598    location: &ExecutionLocation,
1599    located: &mut [Vec<LocatedExecSlot<'input>>],
1600    staged: &mut [Option<ExecSlot<'input>>],
1601) -> Result<()> {
1602    for &slot in &instruction.input_slots {
1603        let is_output = instruction.output_slots.contains(&slot);
1604        let is_last_use = instruction
1605            .input_slots
1606            .iter()
1607            .enumerate()
1608            .any(|(index, &candidate)| {
1609                candidate == slot && instruction.last_use.get(index).copied().unwrap_or(false)
1610            });
1611        if is_last_use {
1612            located[slot].clear();
1613            if !is_output {
1614                staged[slot].take();
1615            }
1616        } else if !is_output {
1617            if let Some(value) = staged[slot].take() {
1618                located[slot].push(LocatedExecSlot {
1619                    location: location.clone(),
1620                    value,
1621                });
1622            }
1623        }
1624    }
1625
1626    for &slot in &instruction.output_slots {
1627        let value = staged
1628            .get_mut(slot)
1629            .and_then(Option::take)
1630            .ok_or(tenferro_tensor::Error::MissingValue { slot })?;
1631        let values = located
1632            .get_mut(slot)
1633            .ok_or(tenferro_tensor::Error::MissingValue { slot })?;
1634        values.clear();
1635        values.push(LocatedExecSlot {
1636            location: location.clone(),
1637            value,
1638        });
1639    }
1640    Ok(())
1641}
1642
1643fn validate_runtime_input_ingress(
1644    location: &ExecutionLocation,
1645    input: &TensorRead<'_>,
1646    slot: usize,
1647) -> Result<()> {
1648    let accepted = location
1649        .witness()
1650        .accepts_runtime_input(input, location.storage_class());
1651    if accepted {
1652        return Ok(());
1653    }
1654    Err(Error::runtime_state_source(
1655        "Runtime::run_compiled",
1656        ErrorPhase::Execution,
1657        super::InputIngressContractError::ResidencyMismatch {
1658            input_slot: slot,
1659            ingress_engine_id: location.engine_id().clone(),
1660            ingress_storage_class: location.storage_class().clone(),
1661            placement: input.placement().clone(),
1662            backend_family: input.backend_family(),
1663            allocation_domain: input.allocation_domain(),
1664        },
1665    ))
1666}
1667
1668fn execute_scheduled_transfer<'input>(
1669    transfer: &ScheduledTransfer,
1670    located: &mut [Vec<LocatedExecSlot<'input>>],
1671) -> Result<()> {
1672    let source = transfer.source_location();
1673    let destination = transfer.destination_location();
1674    let provider = transfer.provider();
1675    let values =
1676        located
1677            .get_mut(transfer.value_slot())
1678            .ok_or(tenferro_tensor::Error::MissingValue {
1679                slot: transfer.value_slot(),
1680            })?;
1681    if values.iter().any(|value| &value.location == destination) {
1682        return Err(Error::runtime_state_source(
1683            "Runtime::run_compiled",
1684            ErrorPhase::Execution,
1685            DuplicateTransferDestinationError {
1686                value_slot: transfer.value_slot(),
1687                destination: destination.clone(),
1688            },
1689        ));
1690    }
1691    let transferred = {
1692        let source_value = values
1693            .iter()
1694            .find(|value| &value.location == source)
1695            .ok_or(tenferro_tensor::Error::MissingValue {
1696                slot: transfer.value_slot(),
1697            })?;
1698        let source_read = source_value.value.as_read();
1699        let expected_dtype = source_read.dtype();
1700        let expected_shape = source_read.shape().to_vec();
1701        let transferred =
1702            provider.transfer_blocking(TransferRequest::new(source, destination, source_read))?;
1703        validate_transfer_output(destination, expected_dtype, &expected_shape, &transferred)?;
1704        transferred
1705    };
1706    values.push(LocatedExecSlot {
1707        location: destination.clone(),
1708        value: ExecSlot::Owned(transferred),
1709    });
1710    Ok(())
1711}
1712
1713fn validate_transfer_output(
1714    destination: &ExecutionLocation,
1715    expected_dtype: tenferro_tensor::DType,
1716    expected_shape: &[usize],
1717    output: &Tensor,
1718) -> Result<()> {
1719    let expected_elements = checked_transfer_element_count(expected_shape).map_err(|source| {
1720        Error::runtime_state_source(
1721            "Runtime::run_compiled",
1722            ErrorPhase::Execution,
1723            TransferError::ProviderContract { source },
1724        )
1725    })?;
1726    let contract_error = if output.dtype() != expected_dtype {
1727        Some(TransferProviderContractError::DTypeMismatch {
1728            expected: expected_dtype,
1729            actual: output.dtype(),
1730        })
1731    } else if output.shape() != expected_shape {
1732        Some(TransferProviderContractError::ShapeMismatch {
1733            expected: expected_shape.to_vec(),
1734            actual: output.shape().to_vec(),
1735        })
1736    } else if tensor_buffer_len(output) != expected_elements {
1737        Some(TransferProviderContractError::InvalidBufferLength {
1738            expected: expected_elements,
1739            actual: tensor_buffer_len(output),
1740        })
1741    } else if !destination
1742        .witness()
1743        .accepts_input_placement(output.placement(), destination.storage_class())
1744    {
1745        Some(
1746            TransferProviderContractError::DestinationPlacementMismatch {
1747                destination_engine_id: destination.engine_id().clone(),
1748                destination_storage_class: destination.storage_class().clone(),
1749                actual: output.placement().clone(),
1750            },
1751        )
1752    } else if !destination.witness().owns_resident_tensor(
1753        &TensorRead::from_tensor(output),
1754        destination.storage_class(),
1755    ) {
1756        Some(
1757            TransferProviderContractError::DestinationResidencyMismatch {
1758                destination_engine_id: destination.engine_id().clone(),
1759                destination_storage_class: destination.storage_class().clone(),
1760                actual_backend_family: TensorRead::from_tensor(output).backend_family(),
1761                actual_allocation_domain: TensorRead::from_tensor(output).allocation_domain(),
1762            },
1763        )
1764    } else {
1765        None
1766    };
1767    match contract_error {
1768        None => Ok(()),
1769        Some(source) => Err(Error::runtime_state_source(
1770            "Runtime::run_compiled",
1771            ErrorPhase::Execution,
1772            TransferError::ProviderContract { source },
1773        )),
1774    }
1775}
1776
1777fn checked_transfer_element_count(
1778    shape: &[usize],
1779) -> std::result::Result<usize, TransferProviderContractError> {
1780    tenferro_tensor::validate::checked_shape_product(
1781        "Runtime::run_compiled",
1782        "transfer source shape",
1783        shape,
1784    )
1785    .map_err(|source| TransferProviderContractError::LogicalElementCount { source })
1786}
1787
1788fn tensor_buffer_len(tensor: &Tensor) -> usize {
1789    match tensor {
1790        Tensor::F32(tensor) => tensor.buffer().len(),
1791        Tensor::F64(tensor) => tensor.buffer().len(),
1792        Tensor::I32(tensor) => tensor.buffer().len(),
1793        Tensor::I64(tensor) => tensor.buffer().len(),
1794        Tensor::Bool(tensor) => tensor.buffer().len(),
1795        Tensor::C32(tensor) => tensor.buffer().len(),
1796        Tensor::C64(tensor) => tensor.buffer().len(),
1797    }
1798}
1799
1800#[cfg(test)]
1801mod transfer_validation_tests {
1802    use std::error::Error as _;
1803
1804    use super::checked_transfer_element_count;
1805    use crate::TransferProviderContractError;
1806
1807    #[test]
1808    fn transfer_element_count_overflow_is_typed_and_preserves_source() {
1809        let error = checked_transfer_element_count(&[usize::MAX, 2]).unwrap_err();
1810
1811        assert!(matches!(
1812            error,
1813            TransferProviderContractError::LogicalElementCount { .. }
1814        ));
1815        assert!(error.source().is_some());
1816    }
1817}
1818
1819fn collect_located_outputs<'input>(
1820    program: &ExecProgram,
1821    located: &mut [Vec<LocatedExecSlot<'input>>],
1822) -> Result<Vec<Option<LocatedExecSlot<'input>>>> {
1823    let mut outputs = (0..program.n_slots).map(|_| None).collect::<Vec<_>>();
1824    for &slot in &program.output_slots {
1825        if outputs[slot].is_some() {
1826            continue;
1827        }
1828        let values = located
1829            .get_mut(slot)
1830            .ok_or(tenferro_tensor::Error::MissingValue { slot })?;
1831        let value = values
1832            .pop()
1833            .ok_or(tenferro_tensor::Error::MissingValue { slot })?;
1834        values.clear();
1835        outputs[slot] = Some(value);
1836    }
1837    located.iter_mut().for_each(Vec::clear);
1838    Ok(outputs)
1839}
1840
1841pub(super) fn collect_tensor_outputs_with<'input>(
1842    program: &ExecProgram,
1843    outputs: &mut [Option<LocatedExecSlot<'input>>],
1844    mut materialize: impl FnMut(&ExecutionLocation, ExecSlot<'input>) -> Result<Tensor>,
1845) -> Result<Vec<Tensor>> {
1846    program
1847        .output_slots
1848        .iter()
1849        .map(|&slot| {
1850            let located = outputs
1851                .get_mut(slot)
1852                .and_then(Option::take)
1853                .ok_or(tenferro_tensor::Error::MissingValue { slot })?;
1854            materialize(&located.location, located.value)
1855        })
1856        .collect()
1857}
1858
1859fn collect_value_outputs_with<'input>(
1860    program: &ExecProgram,
1861    outputs: &mut [Option<LocatedExecSlot<'input>>],
1862    mut materialize: impl FnMut(&ExecutionLocation, ExecSlot<'input>) -> Result<TensorValue>,
1863) -> Result<Vec<TensorValue>> {
1864    program
1865        .output_slots
1866        .iter()
1867        .map(|&slot| {
1868            let located = outputs
1869                .get_mut(slot)
1870                .and_then(Option::take)
1871                .ok_or(tenferro_tensor::Error::MissingValue { slot })?;
1872            materialize(&located.location, located.value)
1873        })
1874        .collect()
1875}
1876
1877fn input_signature(inputs: &[&Tensor]) -> Result<InputSignature> {
1878    let reads: RuntimeInputReads<'_> = inputs
1879        .iter()
1880        .map(|tensor| TensorRead::from_tensor(tensor))
1881        .collect();
1882    InputSignature::from_reads(&reads).map_err(|source| prepare_error(Arc::new(source)))
1883}
1884
1885fn resolve_input_refs<'a>(
1886    program: &'a CompiledGraph,
1887    inputs: &'a [&'a Tensor],
1888) -> Result<RuntimeInputRefs<'a>> {
1889    let resolved = if inputs.is_empty() {
1890        semantic_default_inputs(program)?
1891    } else {
1892        inputs.iter().copied().collect()
1893    };
1894    validate_ordered_input_metadata(program, &resolved)?;
1895    Ok(resolved)
1896}
1897
1898fn semantic_default_inputs(program: &CompiledGraph) -> Result<RuntimeInputRefs<'_>> {
1899    program
1900        .program()
1901        .inputs()
1902        .iter()
1903        .enumerate()
1904        .map(|(input_index, value)| {
1905            program
1906                .bindings()
1907                .tensor_ref_for_input(*value)
1908                .map(AsRef::as_ref)
1909                .ok_or_else(|| Error::UnboundPlaceholder {
1910                    input_key: format!("semantic input {input_index}"),
1911                })
1912        })
1913        .collect()
1914}
1915
1916fn validate_ordered_input_metadata(program: &CompiledGraph, inputs: &[&Tensor]) -> Result<()> {
1917    let expected = program.input_count();
1918    if inputs.len() != expected {
1919        return Err(Error::GraphInputCountMismatch {
1920            expected,
1921            actual: inputs.len(),
1922        });
1923    }
1924    let input_shapes: RuntimeInputShapes<'_> = inputs.iter().map(|tensor| tensor.shape()).collect();
1925    for (input_value, actual) in program.program().inputs().iter().zip(inputs) {
1926        let metadata = program
1927            .program()
1928            .value_metadata(*input_value)
1929            .map_err(|source| {
1930                Error::runtime_state_source("Runtime::run_compiled", ErrorPhase::Execution, source)
1931            })?;
1932        if metadata.dtype() != actual.dtype() {
1933            return Err(Error::PlaceholderDtypeMismatch {
1934                expected: metadata.dtype(),
1935                actual: actual.dtype(),
1936            });
1937        }
1938        if metadata.shape().len() != actual.shape().len() {
1939            return Err(Error::PlaceholderRankMismatch {
1940                expected: metadata.shape().len(),
1941                actual: actual.shape().len(),
1942            });
1943        }
1944        let mut expected_shape: RuntimeShapeScratch = actual.shape().iter().copied().collect();
1945        let mut exact_mismatch = false;
1946        for (axis, (extent, actual_size)) in metadata.shape().iter().zip(actual.shape()).enumerate()
1947        {
1948            match extent {
1949                ShapeExtent::Exact(expression) => {
1950                    let expected = expression.eval(&input_shapes).map_err(|source| {
1951                        Error::runtime_state_source(
1952                            "Runtime::run_compiled",
1953                            ErrorPhase::Execution,
1954                            source,
1955                        )
1956                    })?;
1957                    expected_shape[axis] = expected;
1958                    exact_mismatch |= expected != *actual_size;
1959                }
1960                ShapeExtent::UpperBound(expression) => {
1961                    let bound = expression.eval(&input_shapes).map_err(|source| {
1962                        Error::runtime_state_source(
1963                            "Runtime::run_compiled",
1964                            ErrorPhase::Execution,
1965                            source,
1966                        )
1967                    })?;
1968                    if *actual_size > bound {
1969                        return Err(Error::PlaceholderShapeBoundExceeded {
1970                            axis,
1971                            bound,
1972                            actual: *actual_size,
1973                        });
1974                    }
1975                }
1976                ShapeExtent::Unknown => {}
1977            }
1978        }
1979        if exact_mismatch {
1980            return Err(Error::PlaceholderShapeMismatch {
1981                expected: expected_shape.into_vec(),
1982                actual: actual.shape().to_vec(),
1983            });
1984        }
1985    }
1986    Ok(())
1987}
1988
1989fn validate_exec_input_count(program: &ExecProgram, actual: usize) -> Result<()> {
1990    let expected = program.input_slots.len();
1991    if actual != expected {
1992        return Err(Error::runtime_state(
1993            "Runtime::run_compiled",
1994            ErrorPhase::Execution,
1995            format!("expected {expected} inputs for execution program, got {actual}"),
1996        ));
1997    }
1998    Ok(())
1999}
2000
2001fn prepare_error(source: Arc<PrepareError>) -> Error {
2002    Error::runtime_state_source(
2003        "Runtime::run_compiled",
2004        ErrorPhase::Execution,
2005        SharedPrepareError(source),
2006    )
2007}
2008
2009#[derive(Clone, Debug)]
2010struct SharedPrepareError(Arc<PrepareError>);
2011
2012impl fmt::Display for SharedPrepareError {
2013    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
2014        write!(formatter, "{}", self.0)
2015    }
2016}
2017
2018impl StdError for SharedPrepareError {
2019    fn source(&self) -> Option<&(dyn StdError + 'static)> {
2020        Some(self.0.as_ref())
2021    }
2022}
2023
2024#[cfg(test)]
2025mod tests {
2026    use std::any::Any;
2027    use std::error::Error as StdError;
2028    use std::hash::Hasher;
2029    use std::num::NonZeroU64;
2030    use std::sync::atomic::{AtomicBool, Ordering};
2031    use std::sync::{Arc, TryLockError, Weak};
2032
2033    use tenferro_cpu::CpuBackend;
2034    use tenferro_ops::dim_expr::DimExpr;
2035    use tenferro_ops::ext_op::ExtensionOp;
2036    use tenferro_ops::SymDim;
2037    use tenferro_tensor::{
2038        BackendSession, BackendSessionHost, DType, Tensor, TensorBackend, TensorRead, TypedTensor,
2039    };
2040
2041    use crate::exec::{ExecInstruction, ExecOp, ExecProgram, ExecSlot};
2042    use crate::runtime::{
2043        CoreCapabilityBundle, EngineId, ErasedExecutionContext, EventDomainDriver, EventDomainId,
2044        ExecutableEngineContract, ExecutionContextIdentity, HardwareClassId, InputIngressContract,
2045        InputSignature, PreparedOperation, PreparedOperationBinding, PreparedOperationExecutor,
2046        PreparedOperationPlan, ProviderDeviceIdentity, ProviderId, RegistrationIdentity,
2047        RuntimeCacheOwner, RuntimeEpoch, RuntimeId, SpecializationProjection,
2048        SpecializationRequirements, StorageClass,
2049    };
2050    use crate::{Error, ErrorPhase, ExtensionCacheStore, Result};
2051
2052    use super::{
2053        DuplicateTransferDestinationError, ErasedTensorBackendExecutor, LocatedExecSlot,
2054        TensorBackendExecutor,
2055    };
2056    use crate::runtime::schedule::{
2057        EventCompletion, EventSlotId, ExecutionLocation, ScheduledTransfer,
2058    };
2059
2060    const LOCK_PROBE_FAMILY: &str = "runtime.lock-probe.v1";
2061    const REENTRANT_PROBE_FAMILY: &str = "runtime.reentrant-probe.v1";
2062
2063    #[derive(Clone, Debug)]
2064    struct LockProbeOp;
2065
2066    impl ExtensionOp for LockProbeOp {
2067        fn family_id(&self) -> &'static str {
2068            LOCK_PROBE_FAMILY
2069        }
2070
2071        fn payload_hash(&self, _hasher: &mut dyn Hasher) {}
2072
2073        fn payload_eq(&self, other: &dyn ExtensionOp) -> bool {
2074            other.as_any().downcast_ref::<Self>().is_some()
2075        }
2076
2077        fn clone_arc(&self) -> Arc<dyn ExtensionOp> {
2078            Arc::new(self.clone())
2079        }
2080
2081        fn as_any(&self) -> &dyn Any {
2082            self
2083        }
2084
2085        fn input_count(&self) -> usize {
2086            1
2087        }
2088
2089        fn output_count(&self) -> usize {
2090            1
2091        }
2092
2093        fn infer_output_meta(
2094            &self,
2095            ctx: &mut tenferro_ops::ExtensionShapeContext<'_>,
2096        ) -> tenferro_tensor::Result<Vec<(DType, Vec<SymDim>)>> {
2097            Ok(vec![(ctx.input_dtype(0)?, ctx.input_shape(0)?.to_vec())])
2098        }
2099    }
2100
2101    #[derive(Clone, Debug)]
2102    struct ReentrantProbeOp;
2103
2104    impl ExtensionOp for ReentrantProbeOp {
2105        fn family_id(&self) -> &'static str {
2106            REENTRANT_PROBE_FAMILY
2107        }
2108
2109        fn payload_hash(&self, _hasher: &mut dyn Hasher) {}
2110
2111        fn payload_eq(&self, other: &dyn ExtensionOp) -> bool {
2112            other.as_any().downcast_ref::<Self>().is_some()
2113        }
2114
2115        fn clone_arc(&self) -> Arc<dyn ExtensionOp> {
2116            Arc::new(self.clone())
2117        }
2118
2119        fn as_any(&self) -> &dyn Any {
2120            self
2121        }
2122
2123        fn input_count(&self) -> usize {
2124            1
2125        }
2126
2127        fn output_count(&self) -> usize {
2128            1
2129        }
2130
2131        fn infer_output_meta(
2132            &self,
2133            ctx: &mut tenferro_ops::ExtensionShapeContext<'_>,
2134        ) -> tenferro_tensor::Result<Vec<(DType, Vec<SymDim>)>> {
2135            Ok(vec![(ctx.input_dtype(0)?, ctx.input_shape(0)?.to_vec())])
2136        }
2137    }
2138
2139    #[derive(Debug)]
2140    struct LockProbePreparedOperation {
2141        binding: PreparedOperationBinding,
2142        specialization: SpecializationProjection,
2143        executor: Weak<TensorBackendExecutor<CpuBackend>>,
2144        observed_unlocked_state: Arc<AtomicBool>,
2145    }
2146
2147    impl PreparedOperation for LockProbePreparedOperation {
2148        fn binding(&self) -> &PreparedOperationBinding {
2149            &self.binding
2150        }
2151
2152        fn specialization(&self) -> &SpecializationProjection {
2153            &self.specialization
2154        }
2155
2156        fn retained_bytes(&self) -> usize {
2157            0
2158        }
2159    }
2160
2161    impl PreparedOperationExecutor for LockProbePreparedOperation {
2162        fn execute(
2163            &self,
2164            context: &mut ErasedExecutionContext<'_>,
2165            _extension_caches: &mut ExtensionCacheStore,
2166            inputs: &[TensorRead<'_>],
2167        ) -> Result<Vec<Tensor>> {
2168            let executor = self.executor.upgrade().expect("executor still alive");
2169            let unlocked = match executor.state.try_lock() {
2170                Ok(_guard) => true,
2171                Err(TryLockError::WouldBlock) => false,
2172                Err(TryLockError::Poisoned(_)) => false,
2173            };
2174            self.observed_unlocked_state
2175                .store(unlocked, Ordering::SeqCst);
2176            let backend = context
2177                .downcast_mut::<CpuBackend>(self.binding.context_identity())
2178                .map_err(|source| {
2179                    Error::runtime_state_source("lock_probe", ErrorPhase::Execution, source)
2180                })?;
2181            Ok(vec![backend.with_backend_session(|exec| {
2182                exec.to_contiguous_read(inputs[0].clone())
2183            })?])
2184        }
2185    }
2186
2187    #[derive(Debug)]
2188    struct ReentrantProbePreparedOperation {
2189        binding: PreparedOperationBinding,
2190        specialization: SpecializationProjection,
2191        executor: Weak<TensorBackendExecutor<CpuBackend>>,
2192        observed_reentrant_error: Arc<AtomicBool>,
2193    }
2194
2195    #[derive(Debug)]
2196    struct SessionProbePreparedOperation {
2197        binding: PreparedOperationBinding,
2198        specialization: SpecializationProjection,
2199        observed_session: Arc<AtomicBool>,
2200    }
2201
2202    impl PreparedOperation for SessionProbePreparedOperation {
2203        fn binding(&self) -> &PreparedOperationBinding {
2204            &self.binding
2205        }
2206
2207        fn specialization(&self) -> &SpecializationProjection {
2208            &self.specialization
2209        }
2210
2211        fn retained_bytes(&self) -> usize {
2212            0
2213        }
2214    }
2215
2216    impl PreparedOperationExecutor for SessionProbePreparedOperation {
2217        fn execute(
2218            &self,
2219            _context: &mut ErasedExecutionContext<'_>,
2220            _extension_caches: &mut ExtensionCacheStore,
2221            _inputs: &[TensorRead<'_>],
2222        ) -> Result<Vec<Tensor>> {
2223            Err(Error::unsupported(
2224                "session_probe",
2225                ErrorPhase::Execution,
2226                "session probe must use the scheduler-owned session path",
2227            ))
2228        }
2229
2230        fn supports_session(&self) -> bool {
2231            true
2232        }
2233
2234        fn execute_in_session(
2235            &self,
2236            session: &mut dyn BackendSession,
2237            _extension_caches: &mut ExtensionCacheStore,
2238            inputs: &[TensorRead<'_>],
2239        ) -> Result<Vec<Tensor>> {
2240            self.observed_session.store(true, Ordering::SeqCst);
2241            Ok(vec![session.to_contiguous_read(inputs[0].clone())?])
2242        }
2243    }
2244
2245    impl ReentrantProbePreparedOperation {
2246        fn probe_reentrant_call(&self, input: Tensor) {
2247            let executor = self.executor.upgrade().expect("executor still alive");
2248            let nested = ErasedTensorBackendExecutor::execute(
2249                executor.as_ref(),
2250                &passthrough_program(),
2251                &[],
2252                vec![input],
2253            );
2254            let observed = nested.is_err_and(|error| {
2255                error
2256                    .to_string()
2257                    .contains("reentrant tensor backend executor call would deadlock")
2258            });
2259            self.observed_reentrant_error
2260                .store(observed, Ordering::SeqCst);
2261        }
2262    }
2263
2264    impl PreparedOperation for ReentrantProbePreparedOperation {
2265        fn binding(&self) -> &PreparedOperationBinding {
2266            &self.binding
2267        }
2268
2269        fn specialization(&self) -> &SpecializationProjection {
2270            &self.specialization
2271        }
2272
2273        fn retained_bytes(&self) -> usize {
2274            0
2275        }
2276    }
2277
2278    impl PreparedOperationExecutor for ReentrantProbePreparedOperation {
2279        fn execute(
2280            &self,
2281            context: &mut ErasedExecutionContext<'_>,
2282            _extension_caches: &mut ExtensionCacheStore,
2283            inputs: &[TensorRead<'_>],
2284        ) -> Result<Vec<Tensor>> {
2285            let backend = context
2286                .downcast_mut::<CpuBackend>(self.binding.context_identity())
2287                .map_err(|source| {
2288                    Error::runtime_state_source("reentrant_probe", ErrorPhase::Execution, source)
2289                })?;
2290            let materialized =
2291                backend.with_backend_session(|exec| exec.to_contiguous_read(inputs[0].clone()))?;
2292            self.probe_reentrant_call(materialized.clone());
2293            Ok(vec![materialized])
2294        }
2295    }
2296
2297    fn lock_probe_program() -> ExecProgram {
2298        ExecProgram {
2299            instructions: vec![ExecInstruction {
2300                op: ExecOp::Extension(Arc::new(LockProbeOp)),
2301                semantic_operation_index: Some(0),
2302                input_slots: vec![0],
2303                output_slots: vec![1],
2304                dtype: DType::F64,
2305                output_shapes: vec![vec![DimExpr::InputDim {
2306                    input_idx: 0,
2307                    axis: 0,
2308                }]]
2309                .into(),
2310                output_extents: vec![vec![]].into(),
2311                last_use: vec![false],
2312            }],
2313            input_slots: vec![0],
2314            output_slots: vec![1],
2315            n_slots: 2,
2316            shape_guards: vec![],
2317        }
2318    }
2319
2320    fn partial_session_probe_program() -> ExecProgram {
2321        let output_shape = || {
2322            vec![DimExpr::InputDim {
2323                input_idx: 0,
2324                axis: 0,
2325            }]
2326        };
2327        ExecProgram {
2328            instructions: vec![
2329                ExecInstruction {
2330                    op: ExecOp::Extension(Arc::new(LockProbeOp)),
2331                    semantic_operation_index: Some(0),
2332                    input_slots: vec![0],
2333                    output_slots: vec![1],
2334                    dtype: DType::F64,
2335                    output_shapes: vec![output_shape()].into(),
2336                    output_extents: vec![vec![]].into(),
2337                    last_use: vec![false],
2338                },
2339                ExecInstruction {
2340                    op: ExecOp::Extension(Arc::new(LockProbeOp)),
2341                    semantic_operation_index: Some(1),
2342                    input_slots: vec![1],
2343                    output_slots: vec![2],
2344                    dtype: DType::F64,
2345                    output_shapes: vec![output_shape()].into(),
2346                    output_extents: vec![vec![]].into(),
2347                    last_use: vec![false],
2348                },
2349            ],
2350            input_slots: vec![0],
2351            output_slots: vec![2],
2352            n_slots: 3,
2353            shape_guards: vec![],
2354        }
2355    }
2356
2357    fn reentrant_probe_program() -> ExecProgram {
2358        ExecProgram {
2359            instructions: vec![ExecInstruction {
2360                op: ExecOp::Extension(Arc::new(ReentrantProbeOp)),
2361                semantic_operation_index: Some(0),
2362                input_slots: vec![0],
2363                output_slots: vec![1],
2364                dtype: DType::F64,
2365                output_shapes: vec![vec![DimExpr::InputDim {
2366                    input_idx: 0,
2367                    axis: 0,
2368                }]]
2369                .into(),
2370                output_extents: vec![vec![]].into(),
2371                last_use: vec![false],
2372            }],
2373            input_slots: vec![0],
2374            output_slots: vec![1],
2375            n_slots: 2,
2376            shape_guards: vec![],
2377        }
2378    }
2379
2380    fn passthrough_program() -> ExecProgram {
2381        ExecProgram {
2382            instructions: vec![],
2383            input_slots: vec![0],
2384            output_slots: vec![0],
2385            n_slots: 1,
2386            shape_guards: vec![],
2387        }
2388    }
2389
2390    fn f64_zeros(shape: Vec<usize>) -> Tensor {
2391        Tensor::F64(TypedTensor::zeros(shape).unwrap())
2392    }
2393
2394    fn nz(value: u64) -> NonZeroU64 {
2395        NonZeroU64::new(value).unwrap_or(NonZeroU64::MIN)
2396    }
2397
2398    fn probe_binding() -> PreparedOperationBinding {
2399        PreparedOperationBinding::new(
2400            RuntimeId::from_nonzero(nz(1)),
2401            RuntimeEpoch::from_nonzero(nz(2)),
2402            EngineId::new("tenferro.cpu").unwrap(),
2403            RegistrationIdentity::new(nz(3), nz(4)),
2404            ExecutionContextIdentity::of::<CpuBackend>(),
2405            HardwareClassId::new("tenferro.cpu.host").unwrap(),
2406        )
2407    }
2408
2409    fn probe_specialization() -> SpecializationProjection {
2410        SpecializationRequirements::polymorphic(0)
2411            .project(&InputSignature::new(Vec::new()))
2412            .unwrap()
2413    }
2414
2415    #[test]
2416    fn tensor_backend_executor_releases_state_lock_during_extension_execution() {
2417        let executor = Arc::new(TensorBackendExecutor::<CpuBackend>::new(CpuBackend::new()));
2418        let observed_unlocked_state = Arc::new(AtomicBool::new(false));
2419        let prepared = Arc::new(LockProbePreparedOperation {
2420            binding: probe_binding(),
2421            specialization: probe_specialization(),
2422            executor: Arc::downgrade(&executor),
2423            observed_unlocked_state: Arc::clone(&observed_unlocked_state),
2424        });
2425        let operations = vec![PreparedOperationPlan::executable(
2426            prepared.clone(),
2427            prepared,
2428        )];
2429
2430        let output = ErasedTensorBackendExecutor::execute(
2431            executor.as_ref(),
2432            &lock_probe_program(),
2433            &operations,
2434            vec![f64_zeros(vec![2])],
2435        )
2436        .expect("extension executes");
2437
2438        assert_eq!(output[0].shape(), &[2]);
2439        assert!(
2440            observed_unlocked_state.load(Ordering::SeqCst),
2441            "executor state lock must not be held while extension runtime callbacks execute"
2442        );
2443    }
2444
2445    #[test]
2446    fn scheduled_execution_cleanup_error_preserves_primary_error() {
2447        let primary = Error::runtime_state(
2448            "primary-execution",
2449            ErrorPhase::Execution,
2450            "primary execution failure",
2451        );
2452        let cleanup = Error::runtime_state(
2453            "event-domain-cleanup",
2454            ErrorPhase::Execution,
2455            "cleanup failure",
2456        );
2457        let primary_display = primary.to_string();
2458
2459        let combined = super::scheduled_execution_cleanup_error(primary, cleanup);
2460        let primary_error = combined.primary().expect("primary execution error");
2461        let cleanup_error = combined.suppressed().expect("cleanup error");
2462        assert_eq!(primary_error.to_string(), primary_display);
2463        assert_eq!(
2464            cleanup_error.to_string(),
2465            "event-domain-cleanup (Execution): runtime state failure: cleanup failure"
2466        );
2467        assert_eq!(
2468            StdError::source(&combined)
2469                .expect("primary error in the standard source chain")
2470                .to_string(),
2471            primary_display
2472        );
2473    }
2474
2475    #[test]
2476    fn duplicate_scheduled_transfer_destination_reports_typed_fields() {
2477        let domain = EventDomainId::runtime_created_for_test(
2478            RuntimeId::from_nonzero(nz(1)),
2479            RuntimeEpoch::from_nonzero(nz(1)),
2480            RegistrationIdentity::new(nz(1), nz(1)),
2481        );
2482        let source = ExecutionLocation::new(
2483            EngineId::new("tenferro-test.transfer-source").expect("source engine"),
2484            ProviderDeviceIdentity::new(
2485                ProviderId::new("tenferro-test.transfer").expect("provider id"),
2486                "source",
2487            )
2488            .expect("source provider target"),
2489            domain,
2490            StorageClass::new("tenferro-test.transfer-source").expect("source storage"),
2491        );
2492        let destination = ExecutionLocation::new(
2493            EngineId::new("tenferro-test.transfer-destination").expect("destination engine"),
2494            ProviderDeviceIdentity::new(
2495                ProviderId::new("tenferro-test.transfer").expect("provider id"),
2496                "destination",
2497            )
2498            .expect("destination provider target"),
2499            domain,
2500            StorageClass::new("tenferro-test.transfer-destination").expect("destination storage"),
2501        );
2502        let transfer = ScheduledTransfer::new(
2503            3,
2504            source,
2505            destination.clone(),
2506            [],
2507            EventCompletion::new(domain, EventSlotId::new(0), 0),
2508        );
2509        let mut located = vec![
2510            Vec::new(),
2511            Vec::new(),
2512            Vec::new(),
2513            vec![LocatedExecSlot {
2514                location: destination.clone(),
2515                value: ExecSlot::Owned(f64_zeros(vec![1])),
2516            }],
2517        ];
2518
2519        let error = super::execute_scheduled_transfer(&transfer, &mut located)
2520            .expect_err("duplicate transfer destination");
2521        let Error::RuntimeStateSource { source, .. } = error else {
2522            panic!("duplicate transfer destination must retain a typed source");
2523        };
2524        let duplicate = source
2525            .downcast_ref::<DuplicateTransferDestinationError>()
2526            .expect("typed duplicate transfer destination source");
2527        assert_eq!(duplicate.value_slot, 3);
2528        assert_eq!(duplicate.destination, destination);
2529    }
2530
2531    #[test]
2532    fn tensor_backend_executor_reentrant_call_returns_error_instead_of_deadlocking() {
2533        let executor = Arc::new(TensorBackendExecutor::<CpuBackend>::new(CpuBackend::new()));
2534        let observed_reentrant_error = Arc::new(AtomicBool::new(false));
2535        let prepared = Arc::new(ReentrantProbePreparedOperation {
2536            binding: probe_binding(),
2537            specialization: probe_specialization(),
2538            executor: Arc::downgrade(&executor),
2539            observed_reentrant_error: Arc::clone(&observed_reentrant_error),
2540        });
2541        let operations = vec![PreparedOperationPlan::executable(
2542            prepared.clone(),
2543            prepared,
2544        )];
2545
2546        let output = ErasedTensorBackendExecutor::execute(
2547            executor.as_ref(),
2548            &reentrant_probe_program(),
2549            &operations,
2550            vec![f64_zeros(vec![2])],
2551        )
2552        .expect("outer extension executes");
2553
2554        assert_eq!(output[0].shape(), &[2]);
2555        assert!(
2556            observed_reentrant_error.load(Ordering::SeqCst),
2557            "same-thread reentrant executor call must fail immediately instead of deadlocking"
2558        );
2559    }
2560
2561    #[test]
2562    fn tensor_backend_executor_dispatches_session_capable_extension_in_one_session_path() {
2563        let executor = TensorBackendExecutor::<CpuBackend>::new(CpuBackend::new());
2564        let observed_session = Arc::new(AtomicBool::new(false));
2565        let prepared = Arc::new(SessionProbePreparedOperation {
2566            binding: probe_binding(),
2567            specialization: probe_specialization(),
2568            observed_session: Arc::clone(&observed_session),
2569        });
2570        let operations = vec![PreparedOperationPlan::executable(
2571            prepared.clone(),
2572            prepared,
2573        )];
2574
2575        let output = ErasedTensorBackendExecutor::execute(
2576            &executor,
2577            &lock_probe_program(),
2578            &operations,
2579            vec![f64_zeros(vec![2])],
2580        )
2581        .expect("session-capable extension executes");
2582
2583        assert_eq!(output[0].shape(), &[2]);
2584        assert!(observed_session.load(Ordering::SeqCst));
2585    }
2586
2587    #[test]
2588    fn tensor_backend_executor_batches_session_capable_region_after_boundary() {
2589        let executor = Arc::new(TensorBackendExecutor::<CpuBackend>::new(CpuBackend::new()));
2590        let observed_unlocked_state = Arc::new(AtomicBool::new(false));
2591        let observed_session = Arc::new(AtomicBool::new(false));
2592        let ordinary = Arc::new(LockProbePreparedOperation {
2593            binding: probe_binding(),
2594            specialization: probe_specialization(),
2595            executor: Arc::downgrade(&executor),
2596            observed_unlocked_state: Arc::clone(&observed_unlocked_state),
2597        });
2598        let session = Arc::new(SessionProbePreparedOperation {
2599            binding: probe_binding(),
2600            specialization: probe_specialization(),
2601            observed_session: Arc::clone(&observed_session),
2602        });
2603        let operations = vec![
2604            PreparedOperationPlan::executable(ordinary.clone(), ordinary),
2605            PreparedOperationPlan::executable(session.clone(), session),
2606        ];
2607
2608        let output = ErasedTensorBackendExecutor::execute(
2609            executor.as_ref(),
2610            &partial_session_probe_program(),
2611            &operations,
2612            vec![f64_zeros(vec![2])],
2613        )
2614        .expect("mixed session regions execute");
2615
2616        assert_eq!(output[0].shape(), &[2]);
2617        assert!(observed_unlocked_state.load(Ordering::SeqCst));
2618        assert!(observed_session.load(Ordering::SeqCst));
2619
2620        let values = ErasedTensorBackendExecutor::execute_values(
2621            executor.as_ref(),
2622            &partial_session_probe_program(),
2623            &operations,
2624            vec![f64_zeros(vec![2])],
2625        )
2626        .expect("mixed session regions execute in value mode");
2627        assert_eq!(values[0].shape(), &[2]);
2628    }
2629
2630    #[test]
2631    fn tensor_backend_executor_bridge_does_not_require_clone_source_contract() {
2632        type ContractConstructor<B> = fn(
2633            ProviderDeviceIdentity,
2634            CoreCapabilityBundle,
2635            B,
2636            Arc<dyn EventDomainDriver>,
2637            InputIngressContract,
2638            Option<Arc<dyn RuntimeCacheOwner>>,
2639        ) -> ExecutableEngineContract;
2640
2641        fn factory_accepts_backend_without_clone_bound<B>()
2642        where
2643            B: TensorBackend + Send + Sync + 'static,
2644        {
2645            let _factory: fn(B) -> Arc<dyn ErasedTensorBackendExecutor> =
2646                super::erased_tensor_backend_executor::<B>;
2647        }
2648
2649        fn contract_accepts_backend_without_clone_bound<B>()
2650        where
2651            B: TensorBackend + Send + Sync + 'static,
2652        {
2653            let _constructor: ContractConstructor<B> = ExecutableEngineContract::new::<B>;
2654        }
2655
2656        factory_accepts_backend_without_clone_bound::<CpuBackend>();
2657        contract_accepts_backend_without_clone_bound::<CpuBackend>();
2658    }
2659}