Skip to main content

sim_lib_numbers_tensor/implementation/
execution.rs

1//! Tensor execution contract, default CPU executor, and eval-fabric site.
2
3use std::{fmt, sync::Arc};
4
5use sim_kernel::{
6    CapabilityName, ClassRef, Cx, DefaultFactory, Error, Factory, Object, Result, Symbol, Value,
7};
8
9use super::{
10    canonical_ops::{
11        canonical_tensor_op_symbols, execute_canonical_request, is_canonical_tensor_op,
12    },
13    cast::cast_tensor,
14    elementwise::{
15        execute_elementwise_binary_request, execute_elementwise_unary_request,
16        is_elementwise_binary_op, is_elementwise_unary_op, tensor_elementwise_op_symbols,
17    },
18    execution_ops::{
19        execute_tensor_math_request, is_tensor_executor_math_op, tensor_executor_math_op_symbols,
20    },
21    value::{Tensor, build_tensor_value, tensor_value_ref},
22};
23
24/// Symbol bound in a TensorSite child environment to the active executor.
25pub fn tensor_executor_symbol() -> Symbol {
26    Symbol::qualified("tensor", "executor")
27}
28
29/// Symbol naming the local tensor execution site exported by the tensor lib.
30pub fn tensor_site_symbol() -> Symbol {
31    Symbol::new("site/tensor")
32}
33
34/// Capability required by TensorSite when a request asks for tensor
35/// execution authority.
36pub fn tensor_execute_capability() -> CapabilityName {
37    CapabilityName::new("tensor.execute")
38}
39
40/// Returns the tensor executor currently bound in the active environment.
41pub fn active_tensor_executor(cx: &Cx) -> Option<Arc<dyn TensorExecutor>> {
42    cx.env().get(&tensor_executor_symbol()).and_then(|value| {
43        value
44            .object()
45            .downcast_ref::<TensorExecutorBinding>()
46            .map(TensorExecutorBinding::executor)
47    })
48}
49
50/// Open operation symbol for constructing a tensor from shape, dtype, and cells.
51pub fn tensor_op_symbol() -> Symbol {
52    Symbol::qualified("tensor", "op/tensor")
53}
54
55/// Open operation symbol for constructing a scalar tensor.
56pub fn scalar_op_symbol() -> Symbol {
57    Symbol::qualified("tensor", "op/scalar")
58}
59
60/// Open operation symbol for constructing a vector tensor.
61pub fn vec_op_symbol() -> Symbol {
62    Symbol::qualified("tensor", "op/vec")
63}
64
65/// Open operation symbol for constructing a matrix tensor.
66pub fn mat_op_symbol() -> Symbol {
67    Symbol::qualified("tensor", "op/mat")
68}
69
70/// Open operation symbol for indexing a tensor.
71pub fn index_op_symbol() -> Symbol {
72    Symbol::qualified("tensor", "op/index")
73}
74
75/// Open operation symbol for reshaping a tensor.
76pub fn reshape_op_symbol() -> Symbol {
77    Symbol::qualified("tensor", "op/reshape")
78}
79
80/// Open operation symbol for slicing a tensor.
81pub fn slice_op_symbol() -> Symbol {
82    Symbol::qualified("tensor", "op/slice")
83}
84
85/// Open operation symbol for mapping a callable over a tensor.
86pub fn map_op_symbol() -> Symbol {
87    Symbol::qualified("tensor", "op/map")
88}
89
90/// Open operation symbol for explicit tensor casts.
91pub fn cast_op_symbol() -> Symbol {
92    Symbol::qualified("tensor", "op/cast")
93}
94
95/// Tensor shape and dtype expected from an execution request.
96#[derive(Clone, Debug, PartialEq, Eq)]
97pub struct TensorMeta {
98    shape: Arc<[usize]>,
99    dtype: Symbol,
100}
101
102impl TensorMeta {
103    /// Builds tensor metadata from a shape and scalar dtype.
104    pub fn new(shape: Vec<usize>, dtype: Symbol) -> Self {
105        Self {
106            shape: shape.into(),
107            dtype,
108        }
109    }
110
111    /// Builds tensor metadata from an existing tensor value.
112    pub fn from_tensor(tensor: &Tensor) -> Self {
113        Self::new(tensor.shape().to_vec(), tensor.dtype().clone())
114    }
115
116    /// Returns the tensor shape, outermost axis first.
117    pub fn shape(&self) -> &[usize] {
118        &self.shape
119    }
120
121    /// Returns the scalar dtype every cell must have.
122    pub fn dtype(&self) -> &Symbol {
123        &self.dtype
124    }
125}
126
127/// Open operation descriptor carried by a tensor request.
128#[derive(Clone, Debug)]
129pub struct TensorOp {
130    /// Operation symbol, for example [`reshape_op_symbol`].
131    pub symbol: Symbol,
132    /// Open provider-specific attributes for the operation.
133    pub attributes: Value,
134}
135
136impl TensorOp {
137    /// Builds an operation descriptor with explicit attributes.
138    pub fn new(symbol: Symbol, attributes: Value) -> Self {
139        Self { symbol, attributes }
140    }
141
142    /// Builds an operation descriptor with nil attributes.
143    pub fn without_attributes(cx: &mut Cx, symbol: Symbol) -> Result<Self> {
144        Ok(Self::new(symbol, cx.factory().nil()?))
145    }
146}
147
148/// A checked tensor execution request.
149#[derive(Clone)]
150pub struct TensorRequest {
151    /// Operation to run.
152    pub operation: TensorOp,
153    /// Tensor inputs already validated by the caller.
154    pub inputs: Arc<[Tensor]>,
155    /// Expected output metadata.
156    pub output: TensorMeta,
157}
158
159impl TensorRequest {
160    /// Builds a tensor execution request.
161    pub fn new(operation: TensorOp, inputs: Vec<Tensor>, output: TensorMeta) -> Self {
162        Self {
163            operation,
164            inputs: inputs.into(),
165            output,
166        }
167    }
168}
169
170/// Result of submitting a tensor request to an executor.
171#[derive(Clone)]
172pub enum TensorExecution {
173    /// The request finished and produced a tensor.
174    Complete(Tensor),
175    /// The executor declined before taking ownership of the request.
176    Unsupported {
177        /// Reason the executor declined the request.
178        reason: Arc<str>,
179    },
180}
181
182/// Description of one tensor executor.
183#[derive(Clone, Debug, PartialEq, Eq)]
184pub struct TensorExecutorCard {
185    /// Stable executor symbol.
186    pub symbol: Symbol,
187    /// Human-readable provider label.
188    pub provider: String,
189    /// Placement locality this executor uses.
190    pub locality: Symbol,
191    /// Operation symbols the executor accepts.
192    pub operations: Arc<[Symbol]>,
193    /// Physical-device capability required by this executor, if any.
194    pub device_capability: Option<CapabilityName>,
195}
196
197impl TensorExecutorCard {
198    /// Builds an executor card.
199    pub fn new(
200        symbol: Symbol,
201        provider: impl Into<String>,
202        locality: Symbol,
203        operations: Vec<Symbol>,
204        device_capability: Option<CapabilityName>,
205    ) -> Self {
206        Self {
207            symbol,
208            provider: provider.into(),
209            locality,
210            operations: operations.into(),
211            device_capability,
212        }
213    }
214}
215
216/// Evidence returned after an executor has flushed accepted submissions.
217#[derive(Clone, Debug, PartialEq, Eq)]
218pub struct SubmissionEvidence {
219    /// Executor that produced the evidence.
220    pub executor: Symbol,
221    /// Number of accepted submissions represented by this flush.
222    pub accepted: usize,
223}
224
225impl SubmissionEvidence {
226    /// Builds flush evidence for an executor.
227    pub fn new(executor: Symbol, accepted: usize) -> Self {
228        Self { executor, accepted }
229    }
230}
231
232/// Error reported by tensor execution contracts.
233#[derive(Clone, Debug, PartialEq, Eq)]
234pub enum TensorExecError {
235    /// A required capability was absent.
236    CapabilityDenied {
237        /// The denied capability.
238        capability: CapabilityName,
239    },
240    /// The request is not valid for the executor contract.
241    InvalidRequest {
242        /// Explanation of the invalid request.
243        message: Arc<str>,
244    },
245    /// The executor does not support the requested operation.
246    Unsupported {
247        /// Operation that was declined.
248        operation: Symbol,
249        /// Explanation of the unsupported path.
250        reason: Arc<str>,
251    },
252    /// A result did not match the requested tensor metadata.
253    Shape {
254        /// Explanation of the shape or dtype mismatch.
255        message: Arc<str>,
256    },
257    /// Evaluation failed while realizing a tensor expression.
258    Eval {
259        /// Explanation of the evaluation failure.
260        message: Arc<str>,
261    },
262}
263
264impl TensorExecError {
265    pub(crate) fn invalid(message: impl Into<Arc<str>>) -> Self {
266        Self::InvalidRequest {
267            message: message.into(),
268        }
269    }
270
271    pub(crate) fn shape(message: impl Into<Arc<str>>) -> Self {
272        Self::Shape {
273            message: message.into(),
274        }
275    }
276
277    pub(crate) fn unsupported(operation: Symbol, reason: impl Into<Arc<str>>) -> Self {
278        Self::Unsupported {
279            operation,
280            reason: reason.into(),
281        }
282    }
283}
284
285impl fmt::Display for TensorExecError {
286    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
287        match self {
288            Self::CapabilityDenied { capability } => {
289                write!(f, "capability denied: {capability}")
290            }
291            Self::InvalidRequest { message } => f.write_str(message),
292            Self::Unsupported { operation, reason } => {
293                write!(f, "unsupported tensor operation {operation}: {reason}")
294            }
295            Self::Shape { message } => f.write_str(message),
296            Self::Eval { message } => f.write_str(message),
297        }
298    }
299}
300
301impl std::error::Error for TensorExecError {}
302
303impl From<Error> for TensorExecError {
304    fn from(error: Error) -> Self {
305        match error {
306            Error::CapabilityDenied { capability } => Self::CapabilityDenied { capability },
307            Error::WrongShape { diagnostics, .. } => {
308                let message = diagnostics
309                    .first()
310                    .map(|diagnostic| diagnostic.message.clone())
311                    .unwrap_or_else(|| "tensor result shape check failed".to_owned());
312                Self::Shape {
313                    message: Arc::from(message),
314                }
315            }
316            other => Self::Eval {
317                message: Arc::from(other.to_string()),
318            },
319        }
320    }
321}
322
323impl From<TensorExecError> for Error {
324    fn from(error: TensorExecError) -> Self {
325        match error {
326            TensorExecError::CapabilityDenied { capability } => {
327                Error::CapabilityDenied { capability }
328            }
329            other => Error::Eval(other.to_string()),
330        }
331    }
332}
333
334/// A loadable provider that executes checked tensor requests.
335pub trait TensorExecutor: Send + Sync + 'static {
336    /// Returns the executor descriptor.
337    fn card(&self) -> TensorExecutorCard;
338
339    /// Executes one checked tensor request.
340    fn execute(
341        &self,
342        cx: &mut Cx,
343        request: TensorRequest,
344    ) -> std::result::Result<TensorExecution, TensorExecError>;
345
346    /// Flushes accepted submissions and returns synchronization evidence.
347    fn flush(&self) -> std::result::Result<SubmissionEvidence, TensorExecError>;
348}
349
350/// Executes one tensor request through the active executor, defaulting to CPU.
351pub fn execute_tensor_request(cx: &mut Cx, request: TensorRequest) -> Result<Tensor> {
352    let operation = request.operation.symbol.clone();
353    let executor = active_tensor_executor(cx).unwrap_or_else(|| Arc::new(CpuTensorExecutor::new()));
354    match executor.execute(cx, request).map_err(Error::from)? {
355        TensorExecution::Complete(tensor) => Ok(tensor),
356        TensorExecution::Unsupported { reason } => {
357            Err(Error::from(TensorExecError::unsupported(operation, reason)))
358        }
359    }
360}
361
362pub(crate) fn tensor_executor_value(executor: Arc<dyn TensorExecutor>) -> Result<Value> {
363    DefaultFactory.opaque(Arc::new(TensorExecutorBinding { executor }))
364}
365
366struct TensorExecutorBinding {
367    executor: Arc<dyn TensorExecutor>,
368}
369
370impl TensorExecutorBinding {
371    fn executor(&self) -> Arc<dyn TensorExecutor> {
372        self.executor.clone()
373    }
374}
375
376impl Object for TensorExecutorBinding {
377    fn display(&self, _cx: &mut Cx) -> Result<String> {
378        let card = self.executor.card();
379        Ok(format!("#<tensor-executor {}>", card.symbol))
380    }
381
382    fn as_any(&self) -> &dyn std::any::Any {
383        self
384    }
385}
386
387impl sim_kernel::ObjectCompat for TensorExecutorBinding {
388    fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
389        if let Some(value) = cx
390            .registry()
391            .class_by_symbol(&Symbol::qualified("core", "Function"))
392        {
393            return Ok(value.clone());
394        }
395        DefaultFactory.class_stub(
396            sim_kernel::CORE_FUNCTION_CLASS_ID,
397            Symbol::qualified("core", "Function"),
398        )
399    }
400}
401
402/// Default host executor that delegates to the registered tensor functions.
403#[derive(Clone, Debug, Default)]
404pub struct CpuTensorExecutor;
405
406impl CpuTensorExecutor {
407    /// Builds the default CPU executor.
408    pub fn new() -> Self {
409        Self
410    }
411}
412
413impl TensorExecutor for CpuTensorExecutor {
414    fn card(&self) -> TensorExecutorCard {
415        TensorExecutorCard::new(
416            Symbol::qualified("tensor", "executor/cpu"),
417            "cpu",
418            Symbol::qualified("core", "local-fabric"),
419            vec![
420                tensor_op_symbol(),
421                scalar_op_symbol(),
422                vec_op_symbol(),
423                mat_op_symbol(),
424                reshape_op_symbol(),
425                cast_op_symbol(),
426            ]
427            .into_iter()
428            .chain(tensor_elementwise_op_symbols())
429            .chain(tensor_executor_math_op_symbols())
430            .chain(canonical_tensor_op_symbols())
431            .collect(),
432            None,
433        )
434    }
435
436    fn execute(
437        &self,
438        cx: &mut Cx,
439        request: TensorRequest,
440    ) -> std::result::Result<TensorExecution, TensorExecError> {
441        let operation = request.operation.symbol.clone();
442        let result = if operation == tensor_op_symbol() || operation == vec_op_symbol() {
443            execute_tensor(cx, &request)?
444        } else if operation == scalar_op_symbol() {
445            execute_scalar(&request)?
446        } else if operation == mat_op_symbol() {
447            execute_mat(cx, &request)?
448        } else if operation == reshape_op_symbol() {
449            execute_reshape(cx, &request)?
450        } else if operation == cast_op_symbol() {
451            execute_cast(&request)?
452        } else if operation == index_op_symbol() {
453            return Err(TensorExecError::unsupported(
454                operation,
455                "index returns a scalar value, not a tensor",
456            ));
457        } else if is_elementwise_binary_op(&operation) {
458            execute_elementwise_binary_request(cx, &request)?
459        } else if is_elementwise_unary_op(&operation) {
460            execute_elementwise_unary_request(cx, &request)?
461        } else if is_tensor_executor_math_op(&operation) {
462            execute_tensor_math_request(cx, &request)?
463        } else if is_canonical_tensor_op(&operation) {
464            execute_canonical_request(cx, &request)?
465        } else {
466            return Ok(TensorExecution::Unsupported {
467                reason: Arc::from("unknown tensor operation"),
468            });
469        };
470        check_output(&request.output, &result)?;
471        Ok(TensorExecution::Complete(result))
472    }
473
474    fn flush(&self) -> std::result::Result<SubmissionEvidence, TensorExecError> {
475        Ok(SubmissionEvidence::new(
476            Symbol::qualified("tensor", "executor/cpu"),
477            0,
478        ))
479    }
480}
481
482impl Object for CpuTensorExecutor {
483    fn display(&self, _cx: &mut Cx) -> Result<String> {
484        Ok("#<tensor-executor cpu>".to_owned())
485    }
486
487    fn as_any(&self) -> &dyn std::any::Any {
488        self
489    }
490}
491
492impl sim_kernel::ObjectCompat for CpuTensorExecutor {
493    fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
494        if let Some(value) = cx
495            .registry()
496            .class_by_symbol(&Symbol::qualified("core", "Function"))
497        {
498            return Ok(value.clone());
499        }
500        DefaultFactory.class_stub(
501            sim_kernel::CORE_FUNCTION_CLASS_ID,
502            Symbol::qualified("core", "Function"),
503        )
504    }
505}
506
507fn execute_tensor(
508    cx: &mut Cx,
509    request: &TensorRequest,
510) -> std::result::Result<Tensor, TensorExecError> {
511    let cells = request
512        .inputs
513        .iter()
514        .map(|tensor| {
515            if tensor.rank() == 0 {
516                tensor.cell(0)
517            } else {
518                Err(Error::Eval(
519                    "tensor op/tensor expects scalar tensor inputs as cells".to_owned(),
520                ))
521            }
522        })
523        .collect::<Result<Vec<_>>>()
524        .map_err(TensorExecError::from)?;
525    build_tensor_value(
526        cx,
527        request.output.shape().to_vec(),
528        Some(request.output.dtype().clone()),
529        cells,
530    )
531    .map_err(TensorExecError::from)
532    .and_then(|value| tensor_from_value(&value))
533}
534
535fn execute_scalar(request: &TensorRequest) -> std::result::Result<Tensor, TensorExecError> {
536    let [tensor] = request.inputs.as_ref() else {
537        return Err(TensorExecError::invalid(
538            "scalar operation expects exactly one tensor input",
539        ));
540    };
541    if tensor.rank() != 0 {
542        return Err(TensorExecError::invalid(
543            "scalar operation expects a rank-0 tensor input",
544        ));
545    }
546    Ok(tensor.clone())
547}
548
549fn execute_mat(
550    cx: &mut Cx,
551    request: &TensorRequest,
552) -> std::result::Result<Tensor, TensorExecError> {
553    if request.output.shape().len() != 2 {
554        return Err(TensorExecError::invalid(
555            "matrix operation expects rank-2 output metadata",
556        ));
557    }
558    let row_width = request.output.shape()[1];
559    let mut cells = Vec::new();
560    for row in request.inputs.iter() {
561        if row.shape() != [row_width] {
562            return Err(TensorExecError::invalid(
563                "matrix operation inputs must be rank-1 rows matching output width",
564            ));
565        }
566        cells.extend(row.cells().map_err(TensorExecError::from)?.iter().cloned());
567    }
568    build_tensor_value(
569        cx,
570        request.output.shape().to_vec(),
571        Some(request.output.dtype().clone()),
572        cells,
573    )
574    .map_err(TensorExecError::from)
575    .and_then(|value| tensor_from_value(&value))
576}
577
578fn execute_reshape(
579    cx: &mut Cx,
580    request: &TensorRequest,
581) -> std::result::Result<Tensor, TensorExecError> {
582    let [tensor] = request.inputs.as_ref() else {
583        return Err(TensorExecError::invalid(
584            "reshape operation expects exactly one tensor input",
585        ));
586    };
587    build_tensor_value(
588        cx,
589        request.output.shape().to_vec(),
590        Some(request.output.dtype().clone()),
591        tensor
592            .cells()
593            .map_err(TensorExecError::from)?
594            .iter()
595            .cloned()
596            .collect(),
597    )
598    .map_err(TensorExecError::from)
599    .and_then(|value| tensor_from_value(&value))
600}
601
602fn execute_cast(request: &TensorRequest) -> std::result::Result<Tensor, TensorExecError> {
603    let [tensor] = request.inputs.as_ref() else {
604        return Err(TensorExecError::invalid(
605            "cast operation expects exactly one tensor input",
606        ));
607    };
608    cast_tensor(tensor, request.output.dtype().clone()).map_err(TensorExecError::from)
609}
610
611fn tensor_from_value(value: &Value) -> std::result::Result<Tensor, TensorExecError> {
612    tensor_value_ref(value)
613        .cloned()
614        .ok_or_else(|| TensorExecError::invalid("tensor executor produced a non-tensor value"))
615}
616
617fn check_output(
618    expected: &TensorMeta,
619    result: &Tensor,
620) -> std::result::Result<(), TensorExecError> {
621    if expected.shape() != result.shape() {
622        return Err(TensorExecError::shape(format!(
623            "tensor result shape {:?} did not match {:?}",
624            result.shape(),
625            expected.shape()
626        )));
627    }
628    if expected.dtype() != result.dtype() {
629        return Err(TensorExecError::shape(format!(
630            "tensor result dtype {} did not match {}",
631            result.dtype(),
632            expected.dtype()
633        )));
634    }
635    Ok(())
636}