Skip to main content

runmat_runtime/native/
site.rs

1use super::{NativeCall, NativeExit, NativeHostStatus};
2use std::ffi::c_void;
3
4#[repr(transparent)]
5#[derive(Clone, Copy, Debug, Eq, PartialEq)]
6pub struct NativeSitePhase(pub u32);
7
8impl NativeSitePhase {
9    pub const RVALUE: Self = Self(0);
10    pub const STATEMENT: Self = Self(1);
11    pub const TERMINATOR_RVALUE: Self = Self(2);
12    pub const TERMINATOR: Self = Self(3);
13
14    pub const fn is_known(self) -> bool {
15        self.0 <= Self::TERMINATOR.0
16    }
17}
18
19/// Stable, path-independent identity of one executable Native IR site.
20///
21/// The host maps this identity back to the immutable Native IR product. The
22/// ABI deliberately does not embed MIR, Rust enums, pointers, or serialized
23/// operation payloads in generated code.
24#[repr(C)]
25#[derive(Clone, Copy, Debug, Eq, PartialEq)]
26pub struct NativeSiteRequest {
27    pub function: u32,
28    pub block: u32,
29    pub position: u32,
30    pub phase: NativeSitePhase,
31    pub ordinal: u32,
32    pub reserved: u32,
33}
34
35#[repr(transparent)]
36#[derive(Clone, Copy, Debug, Eq, PartialEq)]
37pub struct NativeSiteOutcomeKind(pub u32);
38
39impl NativeSiteOutcomeKind {
40    /// Continue with the next instruction in the current compiled block.
41    pub const CONTINUE: Self = Self(0);
42    /// Transfer to the indexed outgoing edge of the current terminator.
43    pub const EDGE: Self = Self(1);
44    /// Return from generated code with the initialized `NativeExit`.
45    pub const EXIT: Self = Self(2);
46
47    pub const fn is_known(self) -> bool {
48        self.0 <= Self::EXIT.0
49    }
50}
51
52/// Host decision for one Native IR site.
53///
54/// `edge` is meaningful only for `EDGE`; zero is required for `CONTINUE` and
55/// `EXIT`. The generated function treats `NativeHostStatus` as an ABI/service
56/// result and this value as semantic control flow.
57#[repr(C)]
58#[derive(Clone, Copy, Debug, Eq, PartialEq)]
59pub struct NativeSiteOutcome {
60    pub kind: NativeSiteOutcomeKind,
61    pub edge: u32,
62    pub flags: u32,
63    pub reserved: u32,
64}
65
66impl NativeSiteOutcome {
67    pub const fn continue_execution() -> Self {
68        Self::new(NativeSiteOutcomeKind::CONTINUE, 0)
69    }
70
71    pub const fn edge(edge: u32) -> Self {
72        Self::new(NativeSiteOutcomeKind::EDGE, edge)
73    }
74
75    pub const fn exit() -> Self {
76        Self::new(NativeSiteOutcomeKind::EXIT, 0)
77    }
78
79    const fn new(kind: NativeSiteOutcomeKind, edge: u32) -> Self {
80        Self {
81            kind,
82            edge,
83            flags: 0,
84            reserved: 0,
85        }
86    }
87}
88
89/// Execute exactly one immutable Native IR site.
90///
91/// The callback must initialize `outcome` when it returns
92/// `NativeHostStatus::OK`, and must initialize `exit` when that outcome is
93/// `EXIT`. Panics and platform unwinding may not cross this boundary. `call`
94/// retains the frame, opaque value arena, result window, and host table for the
95/// complete generated invocation.
96pub type NativeExecuteSiteFn = unsafe extern "C" fn(
97    context: *mut c_void,
98    call: *mut NativeCall,
99    request: *const NativeSiteRequest,
100    outcome: *mut NativeSiteOutcome,
101    exit: *mut NativeExit,
102) -> NativeHostStatus;
103
104impl NativeSiteRequest {
105    pub fn validate(&self) -> Result<(), super::NativeAbiError> {
106        if !self.phase.is_known() {
107            return Err(super::NativeAbiError::new(
108                "native.site.phase",
109                "site phase is unknown",
110            ));
111        }
112        if self.reserved != 0 {
113            return Err(super::NativeAbiError::new(
114                "native.site.reserved",
115                "reserved bits must be zero for this ABI revision",
116            ));
117        }
118        Ok(())
119    }
120}
121
122impl NativeSiteOutcome {
123    pub fn validate(&self) -> Result<(), super::NativeAbiError> {
124        if !self.kind.is_known() {
125            return Err(super::NativeAbiError::new(
126                "native.site_outcome.kind",
127                "site outcome kind is unknown",
128            ));
129        }
130        if self.flags != 0 || self.reserved != 0 {
131            return Err(super::NativeAbiError::new(
132                "native.site_outcome.reserved",
133                "flags and reserved bits must be zero for this ABI revision",
134            ));
135        }
136        if self.kind != NativeSiteOutcomeKind::EDGE && self.edge != 0 {
137            return Err(super::NativeAbiError::new(
138                "native.site_outcome.edge",
139                "only an edge outcome may carry an edge index",
140            ));
141        }
142        Ok(())
143    }
144}
145
146#[cfg(test)]
147mod tests {
148    use super::*;
149
150    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
151    #[cfg_attr(not(target_arch = "wasm32"), test)]
152    fn site_identity_and_outcomes_reject_reserved_or_ambiguous_states() {
153        let request = NativeSiteRequest {
154            function: 7,
155            block: 3,
156            position: 2,
157            phase: NativeSitePhase::STATEMENT,
158            ordinal: 1,
159            reserved: 0,
160        };
161        assert_eq!(request.validate(), Ok(()));
162        assert_eq!(NativeSiteOutcome::continue_execution().validate(), Ok(()));
163        assert_eq!(NativeSiteOutcome::edge(4).validate(), Ok(()));
164        assert_eq!(NativeSiteOutcome::exit().validate(), Ok(()));
165
166        let invalid_request = NativeSiteRequest {
167            phase: NativeSitePhase(u32::MAX),
168            ..request
169        };
170        assert_eq!(
171            invalid_request.validate().unwrap_err().field,
172            "native.site.phase"
173        );
174
175        let invalid_outcome = NativeSiteOutcome {
176            kind: NativeSiteOutcomeKind::CONTINUE,
177            edge: 1,
178            flags: 0,
179            reserved: 0,
180        };
181        assert_eq!(
182            invalid_outcome.validate().unwrap_err().field,
183            "native.site_outcome.edge"
184        );
185    }
186}