Skip to main content

celox/backend/native/
runtime_image.rs

1//! Runtime-only loading and instantiation of compiler-produced native images.
2
3use std::sync::Arc;
4
5use celox_design::{DomainKind, StateAddr};
6use celox_runtime::backend::EventHandle;
7use celox_runtime::{DesignReflection, ReflectionSignal, SignalRef};
8use num_bigint::BigUint;
9
10use crate::{
11    NativeBackend, RuntimeEvent, RuntimeFormatContext, SharedNativeCode, SimBackend, SimulatorError,
12};
13
14use super::backend::NativeRuntimeSchema;
15use super::{NativeImageContainerError, NativeProgramImage};
16
17/// Failure while discovering or attaching a compiler-produced native image.
18#[derive(Debug, thiserror::Error)]
19pub enum NativeProgramLoadError {
20    #[error(transparent)]
21    Container(#[from] NativeImageContainerError),
22    #[error("no native program image is attached to the runtime")]
23    MissingImage,
24    #[error("failed to attach native machine code: {0}")]
25    Attach(#[source] SimulatorError),
26    #[error("failed to initialize native program state: {0}")]
27    Initialize(#[source] celox_runtime::SimulatorErrorCode),
28}
29
30/// Source-independent identity shared by reflected handles for one signal.
31#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
32pub enum NativeSignalIdentity {
33    /// Ordinary state aliases share their final native-memory location.
34    State(SignalRef),
35    /// Clock and reset aliases share their canonical event-domain address.
36    Event(StateAddr),
37}
38
39/// One independently mutable instance of a precompiled native program.
40///
41/// Construction needs only a serialized [`NativeProgramImage`]. Source text,
42/// frontend lookup tables, SIR, and compiler layout artifacts are not retained.
43pub struct NativeProgramInstance {
44    shared: Arc<SharedNativeCode>,
45    backend: NativeBackend,
46    runtime_event_read_seq: u64,
47    comb_observer_snapshots: Vec<Vec<(BigUint, BigUint)>>,
48    comb_observer_initial_eval: bool,
49    forced_values: crate::HashMap<NativeSignalIdentity, (BigUint, BigUint)>,
50}
51
52impl NativeProgramInstance {
53    /// Attach an already-decoded program image and allocate fresh state.
54    ///
55    /// # Safety
56    ///
57    /// The image's machine code must come from a trusted source. Image
58    /// validation is structural and does not authenticate executable code.
59    pub unsafe fn from_image(image: NativeProgramImage) -> Result<Self, NativeProgramLoadError> {
60        // Safety: upheld by this constructor's caller.
61        let shared = Arc::new(
62            unsafe { SharedNativeCode::from_image(image) }
63                .map_err(NativeProgramLoadError::Attach)?,
64        );
65        let backend = NativeBackend::from_shared(Arc::clone(&shared));
66        let mut instance = Self {
67            shared,
68            backend,
69            runtime_event_read_seq: 0,
70            comb_observer_snapshots: Vec::new(),
71            comb_observer_initial_eval: true,
72            forced_values: crate::HashMap::default(),
73        };
74        instance.comb_observer_snapshots = instance.snapshot_all_comb_observers();
75        instance
76            .eval_comb_checked(RuntimeFormatContext::default())
77            .map_err(NativeProgramLoadError::Initialize)?;
78        Ok(instance)
79    }
80
81    /// Discover a program image appended to arbitrary runtime bytes.
82    ///
83    /// # Safety
84    ///
85    /// The appended machine code must come from a trusted source. The
86    /// container checksum is not an authenticity check.
87    pub unsafe fn from_attached_bytes(bytes: &[u8]) -> Result<Self, NativeProgramLoadError> {
88        let appended = NativeProgramImage::discover_appended(bytes)?
89            .ok_or(NativeProgramLoadError::MissingImage)?;
90        // Safety: upheld by this constructor's caller.
91        unsafe { Self::from_image(appended.image) }
92    }
93
94    /// Discover the program image appended to the running executable.
95    ///
96    /// # Safety
97    ///
98    /// The running executable and its appended image must be trusted.
99    pub unsafe fn from_current_executable() -> Result<Self, NativeProgramLoadError> {
100        let appended = NativeProgramImage::discover_in_current_executable()?
101            .ok_or(NativeProgramLoadError::MissingImage)?;
102        // Safety: upheld by this constructor's caller.
103        unsafe { Self::from_image(appended.image) }
104    }
105
106    /// Elaborated hierarchy and signal metadata embedded by the compiler.
107    pub fn reflection(&self) -> &DesignReflection {
108        self.shared.program_image().reflection()
109    }
110
111    /// Resolve a fully qualified signal name such as `Top.clock`.
112    pub fn signal(&self, full_name: &str) -> Option<&ReflectionSignal> {
113        self.reflection()
114            .signal_by_name(full_name)
115            .map(|(_, signal)| signal)
116    }
117
118    /// Resolve only the compact state handle for a fully qualified signal.
119    pub fn signal_ref(&self, full_name: &str) -> Option<SignalRef> {
120        self.signal(full_name).map(|signal| signal.signal)
121    }
122
123    /// Resolve the canonical state/event identity behind a reflected handle.
124    pub fn signal_identity(
125        &self,
126        id: celox_runtime::ReflectionSignalId,
127    ) -> Option<NativeSignalIdentity> {
128        let signal = self.reflection().signal(id)?;
129        Some(if signal.domain_kind == DomainKind::Other {
130            NativeSignalIdentity::State(signal.signal)
131        } else {
132            NativeSignalIdentity::Event(
133                self.shared
134                    .program_image()
135                    .event_topology()
136                    .canonical(signal.state_address),
137            )
138        })
139    }
140
141    /// Execute combinational logic after one or more foreign writes.
142    pub fn eval_comb(&mut self) -> Result<(), celox_runtime::SimulatorErrorCode> {
143        self.eval_comb_checked(RuntimeFormatContext::default())
144    }
145
146    /// Settle foreign writes and commit all active source domains together.
147    ///
148    /// Callers apply the raw signal values first and pass the addresses whose
149    /// configured edge became active. Multiple domains use split eval/apply
150    /// entries so no domain can observe another domain's same-time commit.
151    pub fn settle_active_edges(
152        &mut self,
153        active_edges: &[StateAddr],
154    ) -> Result<(), celox_runtime::SimulatorErrorCode> {
155        self.settle_active_edges_with_context(active_edges, RuntimeFormatContext::default())
156    }
157
158    /// Settle foreign writes using host-provided diagnostic formatting data.
159    pub fn settle_active_edges_with_context(
160        &mut self,
161        active_edges: &[StateAddr],
162        context: RuntimeFormatContext<'_>,
163    ) -> Result<(), celox_runtime::SimulatorErrorCode> {
164        let start_seq = (!self.runtime_schema().runtime_event_sites.is_empty())
165            .then(|| crate::simulator::runtime_event_write_seq_for_backend(&self.backend));
166        self.eval_comb_checked(context)?;
167        let mut seen = crate::HashSet::default();
168        let mut events = Vec::new();
169        for address in active_edges {
170            let canonical = self
171                .shared
172                .program_image()
173                .event_topology()
174                .canonical(*address);
175            let Some(event) = self.backend.resolve_event_opt(&canonical) else {
176                continue;
177            };
178            if seen.insert(event.id()) {
179                events.push(event);
180            }
181        }
182
183        if events.len() == 1 {
184            self.backend
185                .eval_apply_ff_at(events[0])
186                .map_err(|error| self.decorate_runtime_error_since(error, start_seq, context))?;
187        } else if !events.is_empty() {
188            let split = events
189                .iter()
190                .map(|event| {
191                    Some((
192                        self.backend.resolve_eval_only_event(&event.addr())?,
193                        self.backend.resolve_apply_event(&event.addr())?,
194                    ))
195                })
196                .collect::<Option<Vec<_>>>();
197            if let Some(split) = split {
198                for (evaluate, _) in &split {
199                    self.backend.eval_only_ff_at(*evaluate).map_err(|error| {
200                        self.decorate_runtime_error_since(error, start_seq, context)
201                    })?;
202                }
203                for (_, apply) in &split {
204                    self.backend.apply_ff_at(*apply).map_err(|error| {
205                        self.decorate_runtime_error_since(error, start_seq, context)
206                    })?;
207                }
208            } else {
209                for event in events {
210                    self.backend.eval_apply_ff_at(event).map_err(|error| {
211                        self.decorate_runtime_error_since(error, start_seq, context)
212                    })?;
213                }
214            }
215        }
216        self.eval_comb_checked(context)?;
217        if let Some(start_seq) = start_seq {
218            self.check_fatal_events_since(start_seq, context)?;
219        }
220        Ok(())
221    }
222
223    /// Drain source-independent `$display` and assertion records emitted by
224    /// generated code since the preceding call.
225    pub fn drain_runtime_events(&mut self) -> Vec<RuntimeEvent> {
226        self.drain_runtime_events_with_context(RuntimeFormatContext::default())
227    }
228
229    /// Drain runtime events while formatting `%t` and `%m` from a host-provided
230    /// simulation context.
231    pub fn drain_runtime_events_with_context(
232        &mut self,
233        context: RuntimeFormatContext<'_>,
234    ) -> Vec<RuntimeEvent> {
235        crate::simulator::collect_runtime_events_for_backend(
236            &self.backend,
237            &self
238                .shared
239                .program_image()
240                .runtime_schema()
241                .runtime_event_sites,
242            &mut self.runtime_event_read_seq,
243            context,
244        )
245    }
246
247    /// Direct access used by foreign-interface adapters for value and event
248    /// operations. The compiler is not involved in these calls.
249    pub fn backend(&self) -> &NativeBackend {
250        &self.backend
251    }
252
253    pub fn backend_mut(&mut self) -> &mut NativeBackend {
254        &mut self.backend
255    }
256
257    /// Override one reflected signal at each combinational execution-unit
258    /// boundary until [`Self::release_signal`] is called.
259    pub fn force_signal(
260        &mut self,
261        id: celox_runtime::ReflectionSignalId,
262        value: BigUint,
263        mask: BigUint,
264    ) -> bool {
265        if !self.shared.supports_forces() {
266            return false;
267        }
268        let Some(identity) = self.signal_identity(id) else {
269            return false;
270        };
271        let aliases = self.signal_refs_for_identity(identity);
272        for signal in aliases {
273            self.backend
274                .set_four_state(signal, value.clone(), mask.clone());
275        }
276        self.forced_values.insert(identity, (value, mask));
277        true
278    }
279
280    /// Restore normal design-driver control for a reflected signal.
281    pub fn release_signal(&mut self, id: celox_runtime::ReflectionSignalId) {
282        if let Some(identity) = self.signal_identity(id) {
283            self.forced_values.remove(&identity);
284        }
285    }
286
287    fn signal_refs_for_identity(&self, identity: NativeSignalIdentity) -> Vec<SignalRef> {
288        let mut signals = self
289            .reflection()
290            .signals()
291            .iter()
292            .enumerate()
293            .filter_map(|(index, signal)| {
294                (self.signal_identity(celox_runtime::ReflectionSignalId(index as u32))
295                    == Some(identity))
296                .then_some(signal.signal)
297            })
298            .collect::<Vec<_>>();
299        signals.sort_unstable();
300        signals.dedup();
301        signals
302    }
303
304    fn runtime_schema(&self) -> &NativeRuntimeSchema {
305        self.shared.program_image().runtime_schema()
306    }
307
308    fn decorate_runtime_error(
309        &self,
310        error: celox_runtime::SimulatorErrorCode,
311    ) -> celox_runtime::SimulatorErrorCode {
312        let celox_runtime::SimulatorErrorCode::DetectedTrueLoopCode(code) = error else {
313            return error;
314        };
315        let Some(info) = self.runtime_schema().runtime_errors.get(&code) else {
316            return celox_runtime::SimulatorErrorCode::DetectedTrueLoop;
317        };
318        let signals = info
319            .signals
320            .iter()
321            .filter_map(|address| {
322                self.reflection()
323                    .signals()
324                    .iter()
325                    .find(|signal| signal.state_address == *address)
326                    .map(|signal| signal.full_name.clone())
327            })
328            .collect::<Vec<_>>();
329        if info.message == "Detected True Loop" {
330            celox_runtime::SimulatorErrorCode::DetectedTrueLoopAt { signals }
331        } else {
332            celox_runtime::SimulatorErrorCode::Runtime {
333                message: info.message.clone(),
334                signals,
335            }
336        }
337    }
338
339    fn decorate_runtime_error_since(
340        &self,
341        error: celox_runtime::SimulatorErrorCode,
342        start_seq: Option<u64>,
343        context: RuntimeFormatContext<'_>,
344    ) -> celox_runtime::SimulatorErrorCode {
345        if let Some(start_seq) = start_seq
346            && let Err(runtime_event_error) = self.check_fatal_events_since(start_seq, context)
347        {
348            return runtime_event_error;
349        }
350        self.decorate_runtime_error(error)
351    }
352
353    fn eval_comb_checked(
354        &mut self,
355        context: RuntimeFormatContext<'_>,
356    ) -> Result<(), celox_runtime::SimulatorErrorCode> {
357        if self.runtime_schema().runtime_event_sites.is_empty() {
358            return self.eval_comb_backend();
359        }
360
361        let start_seq = crate::simulator::runtime_event_write_seq_for_backend(&self.backend);
362        if self.runtime_schema().comb_observers.is_empty() {
363            let result = self.eval_comb_backend();
364            self.check_fatal_events_since(start_seq, context)?;
365            return result;
366        }
367
368        let before = self.snapshot_all_comb_observers();
369        let active_before = before
370            .iter()
371            .zip(&self.comb_observer_snapshots)
372            .map(|(current, previous)| current != previous)
373            .collect::<Vec<_>>();
374        let mut active_sites = vec![false; self.runtime_schema().runtime_event_sites.len()];
375        for (observer, active) in self
376            .runtime_schema()
377            .comb_observers
378            .iter()
379            .zip(active_before)
380        {
381            if active || self.comb_observer_initial_eval {
382                for group_observer in &self.runtime_schema().comb_observers {
383                    if group_observer.activation_group == observer.activation_group {
384                        active_sites[group_observer.site_id as usize] = true;
385                    }
386                }
387            }
388        }
389        self.backend.set_comb_capture_event_enabled(&active_sites);
390        let result = self.eval_comb_backend();
391        let after = self.snapshot_all_comb_observers();
392        self.backend.set_comb_capture_event_enabled(&vec![
393            false;
394            self.runtime_schema()
395                .runtime_event_sites
396                .len()
397        ]);
398        self.comb_observer_snapshots = after;
399        self.comb_observer_initial_eval = false;
400        self.check_fatal_events_since(start_seq, context)?;
401        result
402    }
403
404    fn eval_comb_backend(&mut self) -> Result<(), celox_runtime::SimulatorErrorCode> {
405        if self.forced_values.is_empty() {
406            return self
407                .backend
408                .eval_comb()
409                .map_err(|error| self.decorate_runtime_error(error));
410        }
411        let overrides = self
412            .forced_values
413            .iter()
414            .flat_map(|(identity, (value, mask))| {
415                self.signal_refs_for_identity(*identity)
416                    .into_iter()
417                    .map(|signal| (signal, value.clone(), mask.clone()))
418            })
419            .collect::<Vec<_>>();
420        for (signal, value, mask) in &overrides {
421            self.backend
422                .set_four_state(*signal, value.clone(), mask.clone());
423        }
424        self.backend
425            .eval_comb_units_with(|backend| {
426                for (signal, value, mask) in &overrides {
427                    backend.set_four_state(*signal, value.clone(), mask.clone());
428                }
429            })
430            .map_err(|error| self.decorate_runtime_error(error))
431    }
432
433    fn check_fatal_events_since(
434        &self,
435        start_seq: u64,
436        context: RuntimeFormatContext<'_>,
437    ) -> Result<(), celox_runtime::SimulatorErrorCode> {
438        let mut read_seq = start_seq;
439        let events = crate::simulator::collect_runtime_events_for_backend(
440            &self.backend,
441            &self.runtime_schema().runtime_event_sites,
442            &mut read_seq,
443            context,
444        );
445        if let Some(message) = events.into_iter().find_map(|event| match event {
446            RuntimeEvent::AssertFatal { message } => Some(message),
447            RuntimeEvent::Missed { count } => Some(format!(
448                "missed {count} runtime events; a fatal assertion may have been overwritten"
449            )),
450            RuntimeEvent::Display { .. }
451            | RuntimeEvent::Write { .. }
452            | RuntimeEvent::AssertContinue { .. } => None,
453        }) {
454            return Err(celox_runtime::SimulatorErrorCode::Runtime {
455                message,
456                signals: Vec::new(),
457            });
458        }
459        Ok(())
460    }
461
462    fn snapshot_all_comb_observers(&self) -> Vec<Vec<(BigUint, BigUint)>> {
463        self.runtime_schema()
464            .comb_observers
465            .iter()
466            .map(|observer| {
467                observer
468                    .sensitivity
469                    .iter()
470                    .map(|atom| {
471                        let signal = self.backend.resolve_signal(&atom.id);
472                        let (value, mask) = if signal.is_4state {
473                            self.backend.get_four_state(signal)
474                        } else {
475                            (self.backend.get(signal), BigUint::default())
476                        };
477                        (
478                            slice_biguint(&value, atom.access.lsb, atom.access.msb),
479                            slice_biguint(&mask, atom.access.lsb, atom.access.msb),
480                        )
481                    })
482                    .collect()
483            })
484            .collect()
485    }
486}
487
488fn slice_biguint(value: &BigUint, least: usize, most: usize) -> BigUint {
489    if most < least {
490        return BigUint::default();
491    }
492    let width = most - least + 1;
493    (value >> least) & ((BigUint::from(1u8) << width) - BigUint::from(1u8))
494}