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