Skip to main content

runmat_runtime/context/
services.rs

1use super::{RuntimeCapability, RuntimeCapabilityError};
2use crate::builtin::RuntimeBuiltinBinding;
3use crate::class_registry::RuntimeClass;
4use crate::warning_store::RuntimeWarning;
5use crate::RuntimeError;
6use runmat_types::{CallableIdentity, SourceId};
7use runmat_value::Value;
8use std::future::Future;
9use std::pin::Pin;
10use std::rc::Rc;
11
12pub type RuntimeServiceFuture<T> = Pin<Box<dyn Future<Output = T> + 'static>>;
13
14#[derive(Debug, Clone)]
15pub struct RuntimeCallRequest {
16    pub identity: CallableIdentity,
17    pub arguments: Vec<Value>,
18    pub requested_outputs: usize,
19}
20
21pub trait RuntimeCallService {
22    fn resolve(&self, name: &str) -> Option<usize>;
23
24    fn invoke(
25        &self,
26        request: RuntimeCallRequest,
27    ) -> RuntimeServiceFuture<Result<Value, RuntimeError>>;
28
29    fn source_functions(&self, _source_id: SourceId) -> Vec<(String, usize)> {
30        Vec::new()
31    }
32}
33
34/// Invocation-scoped builtin authority used by exact compiled products.
35/// When installed, the dispatcher must not fall back to process-global
36/// discovery for a missing name.
37pub trait RuntimeBuiltinService {
38    fn bindings_by_name(&self, name: &str) -> Vec<RuntimeBuiltinBinding>;
39}
40
41pub trait RuntimeWorkspaceService {
42    fn lookup(&self, name: &str) -> Option<Value>;
43    fn snapshot(&self) -> Vec<(String, Value)>;
44    fn global_names(&self) -> Vec<String>;
45    fn assign(&self, name: &str, value: Value) -> Result<(), RuntimeError>;
46    fn clear(&self) -> Result<(), RuntimeError>;
47    fn remove(&self, name: &str) -> Result<(), RuntimeError>;
48}
49
50pub trait RuntimeObjectService {
51    fn class(&self, name: &str) -> Option<RuntimeClass>;
52    fn register_class(&self, class: RuntimeClass) -> Result<(), RuntimeError>;
53    fn static_property(&self, class: &str, property: &str) -> Option<Value>;
54    fn set_static_property(
55        &self,
56        class: &str,
57        property: &str,
58        value: Value,
59    ) -> Result<(), RuntimeError>;
60}
61
62#[derive(Debug, Clone, PartialEq, Eq)]
63pub enum HostInteraction {
64    Line { prompt: String, echo: bool },
65    KeyPress { prompt: String },
66}
67
68pub trait RuntimeHostService {
69    fn console(&self, stream: crate::console::ConsoleStream, text: String);
70
71    fn interact(
72        &self,
73        request: HostInteraction,
74    ) -> RuntimeServiceFuture<Result<crate::interaction::InteractionResponse, RuntimeError>>;
75
76    fn warning(&self, warning: RuntimeWarning);
77}
78
79pub trait RuntimeErrorService {
80    fn report(&self, error: &RuntimeError);
81}
82
83pub trait RuntimeAccelerationService {
84    fn supports_operation(&self, operation: &str) -> bool;
85}
86
87/// Session-owned execution-placement authority. The runtime exposes only
88/// executor-neutral contracts; candidate generation and policy remain in their
89/// owning executor/acceleration crates.
90pub trait RuntimePlacementService {
91    fn plan(
92        &self,
93        request: runmat_execution::PlacementPlanRequest,
94    ) -> Result<runmat_execution::PlacementDecision, RuntimeError>;
95
96    fn observe(&self, feedback: runmat_execution::PlacementFeedback) -> Result<(), RuntimeError>;
97
98    fn invalidate(&self, invalidation: runmat_execution::PlacementInvalidation);
99}
100
101#[derive(Debug, Clone, Copy, PartialEq, Eq)]
102pub enum NativeCapability {
103    SharedLibrary,
104    ExecutableMemory,
105    ObjectEmission,
106}
107
108pub trait RuntimeNativeService {
109    fn supports(&self, capability: NativeCapability) -> bool;
110}
111
112#[derive(Debug, Clone)]
113pub struct ForeignCall {
114    pub adapter: String,
115    pub symbol: String,
116    pub arguments: Vec<Value>,
117    pub requested_outputs: usize,
118}
119
120pub trait RuntimeForeignService {
121    fn invoke(&self, call: ForeignCall) -> RuntimeServiceFuture<Result<Value, RuntimeError>>;
122}
123
124#[derive(Debug, Clone, Copy, PartialEq, Eq)]
125pub enum ParallelCapability {
126    Pool,
127    Parfor,
128    Spmd,
129    DistributedValues,
130    Collectives,
131}
132
133#[derive(Debug, Clone, Copy, PartialEq, Eq)]
134pub struct RuntimeParallelResources {
135    pub cpu_millicores_available: u32,
136    pub memory_available_bytes: Option<u64>,
137    pub epoch: u64,
138}
139
140impl Default for RuntimeParallelResources {
141    fn default() -> Self {
142        Self {
143            cpu_millicores_available: 1_000,
144            memory_available_bytes: None,
145            epoch: 0,
146        }
147    }
148}
149
150pub trait RuntimeParallelService {
151    fn supports(&self, capability: ParallelCapability) -> bool;
152
153    /// Side-effect-free scheduler capacity for placement admission. A future
154    /// RM-1067 pool/scheduler adapter overrides this with its current lease;
155    /// absence preserves the single-core local runtime budget.
156    fn placement_resources(&self) -> RuntimeParallelResources {
157        RuntimeParallelResources::default()
158    }
159}
160
161/// Narrow, typed ports composed by the host. An absent port is meaningful and
162/// produces a stable capability error through the corresponding `require_*`
163/// accessor; there is no string-keyed service locator.
164#[derive(Clone, Default)]
165pub struct RuntimeServicePorts {
166    call: Option<Rc<dyn RuntimeCallService>>,
167    builtin: Option<Rc<dyn RuntimeBuiltinService>>,
168    workspace: Option<Rc<dyn RuntimeWorkspaceService>>,
169    object: Option<Rc<dyn RuntimeObjectService>>,
170    host: Option<Rc<dyn RuntimeHostService>>,
171    error: Option<Rc<dyn RuntimeErrorService>>,
172    acceleration: Option<Rc<dyn RuntimeAccelerationService>>,
173    placement: Option<Rc<dyn RuntimePlacementService>>,
174    native: Option<Rc<dyn RuntimeNativeService>>,
175    foreign: Option<Rc<dyn RuntimeForeignService>>,
176    parallel: Option<Rc<dyn RuntimeParallelService>>,
177}
178
179impl std::fmt::Debug for RuntimeServicePorts {
180    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
181        formatter
182            .debug_struct("RuntimeServicePorts")
183            .field("call", &self.call.is_some())
184            .field("builtin", &self.builtin.is_some())
185            .field("workspace", &self.workspace.is_some())
186            .field("object", &self.object.is_some())
187            .field("host", &self.host.is_some())
188            .field("error", &self.error.is_some())
189            .field("acceleration", &self.acceleration.is_some())
190            .field("placement", &self.placement.is_some())
191            .field("native", &self.native.is_some())
192            .field("foreign", &self.foreign.is_some())
193            .field("parallel", &self.parallel.is_some())
194            .finish()
195    }
196}
197
198macro_rules! port_accessors {
199    ($with:ident, $get:ident, $require:ident, $field:ident, $trait_name:ident, $cap:ident) => {
200        pub fn $with(mut self, service: Rc<dyn $trait_name>) -> Self {
201            self.$field = Some(service);
202            self
203        }
204
205        pub fn $get(&self) -> Option<&Rc<dyn $trait_name>> {
206            self.$field.as_ref()
207        }
208
209        pub fn $require(
210            &self,
211            operation: impl Into<String>,
212        ) -> Result<&Rc<dyn $trait_name>, RuntimeCapabilityError> {
213            self.$field
214                .as_ref()
215                .ok_or_else(|| RuntimeCapabilityError::new(RuntimeCapability::$cap, operation))
216        }
217    };
218}
219
220impl RuntimeServicePorts {
221    port_accessors!(
222        with_call,
223        call,
224        require_call,
225        call,
226        RuntimeCallService,
227        Call
228    );
229    port_accessors!(
230        with_builtin,
231        builtin,
232        require_builtin,
233        builtin,
234        RuntimeBuiltinService,
235        Builtin
236    );
237    port_accessors!(
238        with_workspace,
239        workspace,
240        require_workspace,
241        workspace,
242        RuntimeWorkspaceService,
243        Workspace
244    );
245
246    /// Remove an inherited caller-workspace service before entering an
247    /// isolated procedure frame. The callee may then install its own scoped
248    /// workspace authority without exposing or mutating the caller frame.
249    pub fn without_workspace(mut self) -> Self {
250        self.workspace = None;
251        self
252    }
253    port_accessors!(
254        with_object,
255        object,
256        require_object,
257        object,
258        RuntimeObjectService,
259        Object
260    );
261    port_accessors!(
262        with_host,
263        host,
264        require_host,
265        host,
266        RuntimeHostService,
267        Host
268    );
269    port_accessors!(
270        with_error,
271        error,
272        require_error,
273        error,
274        RuntimeErrorService,
275        Error
276    );
277    port_accessors!(
278        with_acceleration,
279        acceleration,
280        require_acceleration,
281        acceleration,
282        RuntimeAccelerationService,
283        Acceleration
284    );
285    port_accessors!(
286        with_placement,
287        placement,
288        require_placement,
289        placement,
290        RuntimePlacementService,
291        Placement
292    );
293    port_accessors!(
294        with_native,
295        native,
296        require_native,
297        native,
298        RuntimeNativeService,
299        Native
300    );
301    port_accessors!(
302        with_foreign,
303        foreign,
304        require_foreign,
305        foreign,
306        RuntimeForeignService,
307        Foreign
308    );
309    port_accessors!(
310        with_parallel,
311        parallel,
312        require_parallel,
313        parallel,
314        RuntimeParallelService,
315        Parallel
316    );
317}
318
319#[cfg(test)]
320mod tests {
321    use super::*;
322
323    #[test]
324    fn absent_ports_report_stable_typed_capabilities() {
325        fn missing<T>(result: Result<T, RuntimeCapabilityError>) -> RuntimeCapabilityError {
326            match result {
327                Ok(_) => panic!("expected absent runtime port"),
328                Err(error) => error,
329            }
330        }
331        let ports = RuntimeServicePorts::default();
332        let failures = [
333            missing(ports.require_builtin("resolve builtin")),
334            missing(ports.require_call("invoke")),
335            missing(ports.require_workspace("lookup")),
336            missing(ports.require_object("class lookup")),
337            missing(ports.require_host("console write")),
338            missing(ports.require_error("report error")),
339            missing(ports.require_acceleration("resident operation")),
340            missing(ports.require_placement("placement plan")),
341            missing(ports.require_native("load library")),
342            missing(ports.require_foreign("foreign call")),
343            missing(ports.require_parallel("parfor")),
344        ];
345        assert_eq!(
346            failures
347                .iter()
348                .map(|failure| failure.capability)
349                .collect::<Vec<_>>(),
350            vec![
351                RuntimeCapability::Builtin,
352                RuntimeCapability::Call,
353                RuntimeCapability::Workspace,
354                RuntimeCapability::Object,
355                RuntimeCapability::Host,
356                RuntimeCapability::Error,
357                RuntimeCapability::Acceleration,
358                RuntimeCapability::Placement,
359                RuntimeCapability::Native,
360                RuntimeCapability::Foreign,
361                RuntimeCapability::Parallel,
362            ]
363        );
364        assert!(failures.iter().all(|failure| {
365            failure.clone().into_runtime_error().identifier()
366                == Some(RuntimeCapabilityError::IDENTIFIER)
367        }));
368    }
369}