Skip to main content

celox/backend/native/
backend.rs

1//! NativeBackend: SimBackend implementation using a custom host backend.
2//!
3//! Mirrors the structure of JitBackend but compiles through
4//! ISel → scalar MIR → regalloc → host emission instead of Cranelift.
5
6use std::sync::Arc;
7use std::sync::atomic::{AtomicUsize, Ordering};
8use std::time::{Duration, Instant};
9
10use bit_set::BitSet;
11use celox_design::{
12    InitialStateData, InitialStateValue, InitialStateWriteRun, RuntimeCombObserver,
13    RuntimeErrorInfo, RuntimeEventSite, RuntimeSchema,
14};
15use celox_runtime::DesignReflection;
16use celox_runtime::backend::SimBackend;
17use celox_testbench::TestbenchProgram;
18use num_bigint::BigUint;
19use serde::{Deserialize, Serialize};
20
21use crate::ir::{
22    AbsoluteAddr, BlockId, ExecutionUnit, LaidOutProgram, RegionedAbsoluteAddr, RegisterId,
23    SIRInstruction, SIROffset, SIRTerminator, SignalArrayLayout, SignalRef,
24};
25use crate::{CodegenError, HashMap, HashSet, SimulatorError, SimulatorOptions};
26
27use super::super::RuntimeEventBuffer;
28use super::super::compile_cancel::{CompileCancel, cancelled, cancelled_error};
29use super::super::traits::SimulatorErrorCode;
30use super::super::{MemoryLayout, get_byte_size, memory_image::MemoryImage};
31#[cfg(any(
32    feature = "x86_64-codegen",
33    all(target_arch = "x86_64", not(feature = "arm64-codegen"))
34))]
35use super::regalloc;
36use super::{emit, jit_mem};
37
38const NATIVE_FEATURE_BMI2: u8 = 1 << 0;
39const NATIVE_FEATURE_AVX: u8 = 1 << 1;
40const NATIVE_FEATURE_FS_STATE_BASE: u8 = 1 << 2;
41const NATIVE_FEATURE_GS_STATE_BASE: u8 = 1 << 3;
42const NATIVE_FEATURE_POPCNT: u8 = 1 << 4;
43const KNOWN_NATIVE_FEATURES: u8 = NATIVE_FEATURE_BMI2
44    | NATIVE_FEATURE_AVX
45    | NATIVE_FEATURE_FS_STATE_BASE
46    | NATIVE_FEATURE_GS_STATE_BASE
47    | NATIVE_FEATURE_POPCNT;
48
49fn current_native_feature_bits() -> u8 {
50    #[cfg(any(
51        feature = "x86_64-codegen",
52        all(target_arch = "x86_64", not(feature = "arm64-codegen"))
53    ))]
54    {
55        celox_backend_x86::native::features::detected_image_feature_bits()
56    }
57    #[cfg(any(
58        feature = "arm64-codegen",
59        all(target_arch = "aarch64", not(feature = "x86_64-codegen"))
60    ))]
61    {
62        0
63    }
64}
65
66fn format_native_feature_bits(bits: u8) -> String {
67    let mut names = Vec::new();
68    if bits & NATIVE_FEATURE_BMI2 != 0 {
69        names.push("BMI2");
70    }
71    if bits & NATIVE_FEATURE_AVX != 0 {
72        names.push("AVX");
73    }
74    if bits & NATIVE_FEATURE_POPCNT != 0 {
75        names.push("POPCNT");
76    }
77    if bits & NATIVE_FEATURE_FS_STATE_BASE != 0 {
78        names.push("FS state base");
79    }
80    if bits & NATIVE_FEATURE_GS_STATE_BASE != 0 {
81        names.push("GS state base");
82    }
83    names.join(", ")
84}
85
86// ────────────────────────────────────────────────────────────────
87// Event handle
88// ────────────────────────────────────────────────────────────────
89
90/// JIT function type: `fn(state: *mut u8) -> i64`
91#[cfg(all(target_arch = "x86_64", not(feature = "arm64-codegen")))]
92pub type NativeSimFunc = unsafe extern "sysv64" fn(*mut u8) -> i64;
93#[cfg(any(
94    feature = "arm64-codegen",
95    all(target_arch = "aarch64", not(feature = "x86_64-codegen")),
96    all(feature = "x86_64-codegen", not(target_arch = "x86_64"))
97))]
98pub type NativeSimFunc = unsafe extern "C" fn(*mut u8) -> i64;
99
100/// Time spent inside generated native simulator functions.
101///
102/// Timing is opt-in so normal simulation does not pay for host clock reads.
103/// A call may execute many ticks when the native tick loop is enabled.
104#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
105pub struct NativeExecutionTiming {
106    elapsed: Duration,
107    calls: u64,
108}
109
110impl NativeExecutionTiming {
111    pub fn elapsed(self) -> Duration {
112        self.elapsed
113    }
114
115    pub fn calls(self) -> u64 {
116        self.calls
117    }
118}
119
120/// Compiled event handle for native backend.
121/// Holds the function pointer directly — no indirection at call time.
122#[derive(Clone, Copy)]
123pub struct NativeEventRef {
124    pub func: NativeSimFunc,
125    pub comb_apply_func: NativeSimFunc,
126    pub addr: AbsoluteAddr,
127    pub id: usize,
128}
129
130impl std::fmt::Debug for NativeEventRef {
131    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
132        f.debug_struct("NativeEventRef")
133            .field("func", &(self.func as usize))
134            .field("comb_apply_func", &(self.comb_apply_func as usize))
135            .field("addr", &self.addr)
136            .field("id", &self.id)
137            .finish()
138    }
139}
140
141impl super::super::EventHandle for NativeEventRef {
142    fn id(&self) -> usize {
143        self.id
144    }
145    fn addr(&self) -> AbsoluteAddr {
146        self.addr
147    }
148}
149
150// ────────────────────────────────────────────────────────────────
151// Shared compiled code
152// ────────────────────────────────────────────────────────────────
153
154/// Shared compiled code for the native backend.
155/// Can be cloned (via Arc) to create multiple simulator instances
156/// that share the same compiled machine code.
157pub struct SharedNativeCode {
158    comb_func: NativeSimFunc,
159    comb_unit_funcs: Vec<NativeSimFunc>,
160    /// Keep the combined executable image alive so every entry pointer remains
161    /// valid. The image contains all native functions and their trailing
162    /// constant/literal data.
163    _jit_image: jit_mem::JitCode,
164    program_image: NativeProgramImage,
165
166    event_map: HashMap<AbsoluteAddr, NativeEventRef>,
167    eval_only_event_map: HashMap<AbsoluteAddr, NativeEventRef>,
168    apply_event_map: HashMap<AbsoluteAddr, NativeEventRef>,
169    id_to_addr: Vec<AbsoluteAddr>,
170    id_to_event: Vec<NativeEventRef>,
171    layout: MemoryLayout,
172    /// Simulation-state bytes plus the largest native spill/scratch arena
173    /// required by any compiled function.
174    pub(crate) native_memory_size: usize,
175    options: NativeRuntimeOptions,
176    /// (offset, byte_size) pairs for 4-state variables that need X initialization.
177    four_state_inits: Vec<(usize, usize)>,
178}
179
180// Safety: JitCode contains Mmap which is Send+Sync after creation.
181unsafe impl Send for SharedNativeCode {}
182unsafe impl Sync for SharedNativeCode {}
183
184impl SharedNativeCode {
185    /// Attach a compiler-produced image to the precompiled host runtime.
186    ///
187    /// # Safety
188    ///
189    /// The image's machine code must come from a trusted source. Structural
190    /// validation and the container checksum detect corruption, but do not
191    /// authenticate code before it is mapped executable and invoked.
192    pub unsafe fn from_image(program_image: NativeProgramImage) -> Result<Self, SimulatorError> {
193        program_image.validate().map_err(|message| {
194            codegen_message(format!("invalid native program image: {message}"))
195        })?;
196        let unavailable = program_image.required_native_features & !current_native_feature_bits();
197        if unavailable != 0 {
198            return Err(codegen_message(format!(
199                "native program image requires unavailable host features: {}",
200                format_native_feature_bits(unavailable)
201            )));
202        }
203        let symbols = program_image
204            .symbols
205            .iter()
206            .map(|symbol| jit_mem::JitSymbol {
207                offset: symbol.offset,
208                size: symbol.size,
209                name: symbol.name.clone(),
210            })
211            .collect::<Vec<_>>();
212        let jit_image = jit_mem::JitCode::new_named_with_symbols_profiled(
213            program_image.code_image(),
214            "celox_native_image",
215            &symbols,
216            program_image.options.perf_map,
217        )
218        .map_err(|source| codegen_err(CodegenError::NativeMemory { source }))?;
219        let materialize = |event: NativeEventImageRef| -> Result<NativeEventRef, SimulatorError> {
220            Ok(NativeEventRef {
221                func: native_function_at(&jit_image, event.func_offset)?,
222                comb_apply_func: native_function_at(&jit_image, event.comb_apply_offset)?,
223                addr: event.addr,
224                id: event.id,
225            })
226        };
227        let materialize_map = |source: &HashMap<AbsoluteAddr, NativeEventImageRef>| {
228            source
229                .iter()
230                .map(|(&addr, &event)| Ok((addr, materialize(event)?)))
231                .collect::<Result<HashMap<_, _>, SimulatorError>>()
232        };
233        let comb_func = native_function_at(&jit_image, program_image.comb_offset)?;
234        let comb_unit_funcs = program_image
235            .comb_unit_offsets
236            .iter()
237            .copied()
238            .map(|offset| native_function_at(&jit_image, offset))
239            .collect::<Result<Vec<_>, _>>()?;
240        let event_map = materialize_map(&program_image.event_map)?;
241        let eval_only_event_map = materialize_map(&program_image.eval_only_event_map)?;
242        let apply_event_map = materialize_map(&program_image.apply_event_map)?;
243        let id_to_event = program_image
244            .id_to_event
245            .iter()
246            .copied()
247            .map(materialize)
248            .collect::<Result<Vec<_>, _>>()?;
249
250        Ok(Self {
251            comb_func,
252            comb_unit_funcs,
253            _jit_image: jit_image,
254            event_map,
255            eval_only_event_map,
256            apply_event_map,
257            id_to_addr: program_image.id_to_addr.clone(),
258            id_to_event,
259            layout: program_image.layout.clone(),
260            native_memory_size: program_image.native_memory_size,
261            options: program_image.options,
262            four_state_inits: program_image.four_state_inits.clone(),
263            program_image,
264        })
265    }
266
267    /// Returns a reference to the memory layout.
268    pub fn layout(&self) -> &MemoryLayout {
269        &self.layout
270    }
271
272    /// Exact relocatable native image used by this compiled design.
273    ///
274    /// Entry addresses are intentionally not serialized: consumers copy this
275    /// image and resolve [`Self::code_entries`] relative to the new base.
276    pub fn code_image(&self) -> &[u8] {
277        self.program_image.code_image()
278    }
279
280    /// Named function entries inside [`Self::code_image`].
281    pub fn code_entries(&self) -> &[NativeCodeEntry] {
282        self.program_image.code_entries()
283    }
284
285    /// Pointer-free compiler artifact from which this runtime image was loaded.
286    pub fn program_image(&self) -> &NativeProgramImage {
287        &self.program_image
288    }
289
290    pub(crate) fn supports_forces(&self) -> bool {
291        self.options.native_force_support
292    }
293}
294
295/// One callable function in a packed native code image.
296#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
297pub struct NativeCodeEntry {
298    /// Stable diagnostic name for the emitted function.
299    pub name: String,
300    /// Byte offset from the start of the native image.
301    pub offset: usize,
302    /// Size of this function blob, including its private literal data.
303    pub size: usize,
304}
305
306#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
307struct NativeEventImageRef {
308    func_offset: usize,
309    comb_apply_offset: usize,
310    addr: AbsoluteAddr,
311    id: usize,
312}
313
314#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
315struct NativeCodeSymbol {
316    offset: usize,
317    size: usize,
318    name: String,
319}
320
321#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
322struct NativeRuntimeOptions {
323    four_state: bool,
324    native_tick_loop: bool,
325    native_force_support: bool,
326    perf_map: bool,
327}
328
329#[derive(Clone, Debug, Serialize, Deserialize)]
330pub(crate) struct NativeRuntimeSchema {
331    pub(crate) runtime_errors: HashMap<i64, RuntimeErrorInfo<AbsoluteAddr>>,
332    pub(crate) runtime_event_sites: Vec<RuntimeEventSite>,
333    pub(crate) comb_observers: Vec<RuntimeCombObserver<AbsoluteAddr>>,
334    pub(crate) testbench_read_roots: HashSet<AbsoluteAddr>,
335    pub(crate) rtl_writes: HashSet<celox_design::VarAtomBase<AbsoluteAddr>>,
336}
337
338/// Pointer-free native compiler artifact which can be attached to the
339/// precompiled Celox runtime.
340#[derive(Clone, Serialize, Deserialize)]
341pub struct NativeProgramImage {
342    code: Vec<u8>,
343    code_entries: Vec<NativeCodeEntry>,
344    symbols: Vec<NativeCodeSymbol>,
345    comb_offset: usize,
346    comb_unit_offsets: Vec<usize>,
347    required_native_features: u8,
348    event_map: HashMap<AbsoluteAddr, NativeEventImageRef>,
349    eval_only_event_map: HashMap<AbsoluteAddr, NativeEventImageRef>,
350    apply_event_map: HashMap<AbsoluteAddr, NativeEventImageRef>,
351    id_to_addr: Vec<AbsoluteAddr>,
352    id_to_event: Vec<NativeEventImageRef>,
353    reflection: DesignReflection,
354    design: crate::ir::RuntimeDesign,
355    testbench: Option<TestbenchProgram<AbsoluteAddr>>,
356    runtime_schema: NativeRuntimeSchema,
357    layout: MemoryLayout,
358    native_memory_size: usize,
359    options: NativeRuntimeOptions,
360    four_state_inits: Vec<(usize, usize)>,
361}
362
363impl NativeProgramImage {
364    /// Complete relocatable machine-code image.
365    pub fn code_image(&self) -> &[u8] {
366        &self.code
367    }
368
369    /// Named entry offsets in [`Self::code_image`].
370    pub fn code_entries(&self) -> &[NativeCodeEntry] {
371        &self.code_entries
372    }
373
374    /// Final state layout consumed by every generated entry.
375    pub fn layout(&self) -> &MemoryLayout {
376        &self.layout
377    }
378
379    /// Source-independent instance hierarchy and signal metadata.
380    pub fn reflection(&self) -> &DesignReflection {
381        &self.reflection
382    }
383
384    /// Source-independent runtime diagnostics and combinational observers.
385    pub(crate) fn runtime_schema(&self) -> &NativeRuntimeSchema {
386        &self.runtime_schema
387    }
388
389    /// Whether the image's generated code and state layout use four-state data.
390    pub(crate) fn four_state(&self) -> bool {
391        self.options.four_state
392    }
393
394    /// Canonical event-domain topology used by the runtime scheduler.
395    pub(crate) fn event_topology(&self) -> &celox_design::EventTopology<AbsoluteAddr> {
396        &self.design.events
397    }
398
399    /// Reconstruct the source-independent runtime metadata retained by this
400    /// image. No frontend parsing or SIR/layout work is needed on the
401    /// execution side of a host-codegen workflow.
402    pub(crate) fn runtime_program(&self) -> crate::ir::RuntimeProgram {
403        crate::ir::RuntimeProgram {
404            design: self.design.clone(),
405            runtime_schema: RuntimeSchema {
406                runtime_errors: self.runtime_schema.runtime_errors.clone(),
407                runtime_event_sites: self.runtime_schema.runtime_event_sites.clone(),
408                comb_observers: self.runtime_schema.comb_observers.clone(),
409                testbench_read_roots: self.runtime_schema.testbench_read_roots.clone(),
410                rtl_writes: self.runtime_schema.rtl_writes.clone(),
411            },
412            testbench: self.testbench.clone(),
413        }
414    }
415
416    pub(super) fn validate(&self) -> Result<(), String> {
417        self.design
418            .validate()
419            .map_err(|error| format!("invalid runtime design: {error}"))?;
420        self.reflection
421            .validate()
422            .map_err(|error| format!("invalid design reflection: {error}"))?;
423        if self.code.is_empty() {
424            return Err("code image is empty".into());
425        }
426        let mut entry_offsets = HashSet::default();
427        let mut previous_end = 0usize;
428        for entry in &self.code_entries {
429            if !entry.offset.is_multiple_of(NATIVE_CODE_ENTRY_ALIGNMENT) {
430                return Err(format!("entry `{}` is not aligned", entry.name));
431            }
432            if entry.size == 0 {
433                return Err(format!("entry `{}` is empty", entry.name));
434            }
435            let end = entry
436                .offset
437                .checked_add(entry.size)
438                .ok_or_else(|| format!("entry `{}` range overflows", entry.name))?;
439            if entry.offset < previous_end || end > self.code.len() {
440                return Err(format!("entry `{}` is outside the code image", entry.name));
441            }
442            if !entry_offsets.insert(entry.offset) {
443                return Err(format!("entry `{}` duplicates an offset", entry.name));
444            }
445            previous_end = end;
446        }
447        if !entry_offsets.contains(&self.comb_offset) {
448            return Err("eval_comb offset does not name an image entry".into());
449        }
450        if self
451            .comb_unit_offsets
452            .iter()
453            .any(|offset| !entry_offsets.contains(offset))
454        {
455            return Err("a combinational unit offset does not name an image entry".into());
456        }
457        if self.required_native_features & !KNOWN_NATIVE_FEATURES != 0 {
458            return Err("native image contains unknown feature requirements".into());
459        }
460        for symbol in &self.symbols {
461            let end = symbol
462                .offset
463                .checked_add(symbol.size)
464                .ok_or_else(|| format!("symbol `{}` range overflows", symbol.name))?;
465            if symbol.size == 0 || end > self.code.len() {
466                return Err(format!(
467                    "symbol `{}` is outside the code image",
468                    symbol.name
469                ));
470            }
471        }
472        for event in self
473            .event_map
474            .values()
475            .chain(self.eval_only_event_map.values())
476            .chain(self.apply_event_map.values())
477            .chain(self.id_to_event.iter())
478        {
479            if !entry_offsets.contains(&event.func_offset)
480                || !entry_offsets.contains(&event.comb_apply_offset)
481            {
482                return Err(format!(
483                    "event {} references a missing image entry",
484                    event.id
485                ));
486            }
487        }
488        let semantic_size = self
489            .layout
490            .merged_total_size
491            .checked_add(self.layout.triggered_bits_total_size)
492            .ok_or_else(|| "semantic memory size overflows".to_string())?;
493        if self.native_memory_size < semantic_size {
494            return Err("native memory is smaller than the semantic state".into());
495        }
496        for &(offset, size) in &self.four_state_inits {
497            let end = size
498                .checked_mul(2)
499                .and_then(|size| offset.checked_add(size))
500                .ok_or_else(|| "four-state initialization range overflows".to_string())?;
501            if end > self.native_memory_size {
502                return Err("four-state initialization exceeds native memory".into());
503            }
504        }
505        Ok(())
506    }
507}
508
509// ────────────────────────────────────────────────────────────────
510// Compilation
511// ────────────────────────────────────────────────────────────────
512
513fn codegen_err(error: CodegenError) -> SimulatorError {
514    error.into()
515}
516
517fn codegen_message(message: impl Into<String>) -> SimulatorError {
518    codegen_err(CodegenError::message(message))
519}
520
521struct CompiledNativeFunction {
522    code: Vec<u8>,
523    symbols: Vec<jit_mem::JitSymbol>,
524    trace: Option<emit::NativeFunctionTrace>,
525    required_state_size: usize,
526    required_native_features: u8,
527}
528
529#[cfg_attr(
530    any(
531        all(feature = "arm64-codegen", not(target_arch = "aarch64")),
532        all(feature = "x86_64-codegen", not(target_arch = "x86_64"))
533    ),
534    allow(dead_code)
535)]
536pub(crate) struct NativeCodegenTrace {
537    pub optimized_sir: String,
538    pub mir: String,
539    pub reactive_graph: String,
540    pub state_layout: String,
541}
542
543fn prepare_merged_sir(
544    units: &[&crate::ir::ExecutionUnit<crate::ir::RegionedAbsoluteAddr>],
545    layout: &MemoryLayout,
546    four_state: bool,
547    label: &str,
548    first_ff_unit: Option<usize>,
549    diagnostics: &crate::optimizer::SirDiagnostics,
550    cancel: Option<&CompileCancel>,
551) -> Result<crate::ir::ExecutionUnit<crate::ir::RegionedAbsoluteAddr>, SimulatorError> {
552    let verify_enabled = cfg!(debug_assertions) || diagnostics.verify_boundaries;
553    if verify_enabled {
554        for (unit_index, unit) in units.iter().enumerate() {
555            if let Err(error) = unit.verify_result() {
556                return Err(codegen_err(CodegenError::SirVerification {
557                    phase: format!(
558                        "invalid SIR before x86 source-unit merge: {label} source unit {unit_index}"
559                    ),
560                    source: error,
561                }));
562            }
563        }
564    }
565
566    let (mut sir_eu, merge_provenance) = celox_sir::merge_sir_eu_refs_with_provenance(units);
567    let boundaries = merge_provenance.unit_entries[1..].to_vec();
568    let verify = |eu: &crate::ir::ExecutionUnit<crate::ir::RegionedAbsoluteAddr>,
569                  phase: &'static str| {
570        if verify_enabled {
571            eu.verify_result().map_err(|source| {
572                codegen_err(CodegenError::SirVerification {
573                    phase: phase.to_string(),
574                    source,
575                })
576            })
577        } else {
578            Ok(())
579        }
580    };
581
582    verify(&sir_eu, "before x86 merged-SIR optimization")?;
583    if cancelled(cancel) {
584        return Err(cancelled_error());
585    }
586    if let Some(first_ff_unit) = first_ff_unit {
587        let removed = crate::optimizer::sir::eliminate_unobserved_comb_state_stores(
588            &mut sir_eu,
589            &merge_provenance,
590            first_ff_unit,
591        )
592        .map_err(|source| {
593            codegen_err(CodegenError::Optimization {
594                context: "comb/FF state-publication DSE",
595                source,
596            })
597        })?;
598        if removed != 0 {
599            crate::optimizer::sir::remove_dead_sir_definitions(&mut sir_eu);
600            verify(&sir_eu, "after comb/FF state-publication DSE")?;
601        }
602    }
603    if label == "eval_comb_apply_ff"
604        && crate::optimizer::sir::promote_fused_comb_static_slots(&mut sir_eu).map_err(
605            |source| {
606                codegen_err(CodegenError::Optimization {
607                    context: "final fused comb StateSSA promotion",
608                    source,
609                })
610            },
611        )?
612    {
613        crate::optimizer::sir::remove_dead_sir_definitions(&mut sir_eu);
614        verify(&sir_eu, "after final fused comb StateSSA promotion")?;
615    }
616    crate::optimizer::sir::pass_eliminate_working_round_trip::eliminate_working_round_trip(
617        &mut sir_eu,
618        &boundaries,
619    );
620    verify(&sir_eu, "after x86 direct working rewrite")?;
621    if cancelled(cancel) {
622        return Err(cancelled_error());
623    }
624    let promoted_working =
625        crate::optimizer::sir::promote_eval_apply_working_round_trips(&mut sir_eu);
626    if promoted_working {
627        verify(&sir_eu, "after x86 working StateSSA")?;
628        crate::optimizer::sir::remove_dead_sir_definitions(&mut sir_eu);
629        verify(&sir_eu, "after x86 working StateSSA DCE")?;
630    }
631    if cancelled(cancel) {
632        return Err(cancelled_error());
633    }
634    crate::optimizer::sir::optimize_native_merged_chain(
635        &mut sir_eu,
636        layout,
637        four_state,
638        label == "eval_comb_apply_ff",
639        diagnostics,
640        || cancelled(cancel),
641    )
642    .map_err(|source| {
643        if source.kind() == celox_sir_opt::OptimizationErrorKind::Cancelled {
644            codegen_err(CodegenError::Cancelled)
645        } else {
646            codegen_err(CodegenError::Optimization {
647                context: "native merged-chain optimization",
648                source,
649            })
650        }
651    })?;
652    verify(&sir_eu, "after x86 merged-chain cleanup")?;
653    Ok(sir_eu)
654}
655
656fn compile_units(
657    units: &[crate::ir::ExecutionUnit<crate::ir::RegionedAbsoluteAddr>],
658    layout: &MemoryLayout,
659    four_state: bool,
660    label: &str,
661    x86_options: &crate::backend::X86BackendOptions,
662    capture_trace: bool,
663    diagnostics: &crate::optimizer::SirDiagnostics,
664    cancel: Option<&CompileCancel>,
665) -> Result<CompiledNativeFunction, SimulatorError> {
666    let units = units.iter().collect::<Vec<_>>();
667    compile_unit_refs(
668        &units,
669        layout,
670        four_state,
671        label,
672        None,
673        x86_options,
674        capture_trace,
675        diagnostics,
676        cancel,
677    )
678}
679
680fn compile_unit_refs(
681    units: &[&crate::ir::ExecutionUnit<crate::ir::RegionedAbsoluteAddr>],
682    layout: &MemoryLayout,
683    four_state: bool,
684    label: &str,
685    first_ff_unit: Option<usize>,
686    x86_options: &crate::backend::X86BackendOptions,
687    capture_trace: bool,
688    diagnostics: &crate::optimizer::SirDiagnostics,
689    cancel: Option<&CompileCancel>,
690) -> Result<CompiledNativeFunction, SimulatorError> {
691    if cancelled(cancel) {
692        return Err(cancelled_error());
693    }
694    let timing = x86_options.diagnostics.phase_timing;
695    if units.is_empty() {
696        // Empty function: just return 0
697        #[cfg(any(
698            feature = "x86_64-codegen",
699            all(target_arch = "x86_64", not(feature = "arm64-codegen"))
700        ))]
701        let (empty_result, empty_mir) = {
702            let mut empty_func =
703                super::mir::MFunction::new(super::mir::VRegAllocator::new(), vec![]);
704            let mut block = super::mir::MBlock::new(super::mir::BlockId(0));
705            block.push(super::mir::MInst::Return);
706            empty_func.push_block(block);
707            let empty_result = emit::emit(&empty_func, &regalloc::AssignmentMap::default(), 0)
708                .map_err(|source| codegen_err(CodegenError::NativeEmission { source }))?;
709            let empty_mir = empty_func.to_string();
710            (empty_result, empty_mir)
711        };
712        #[cfg(any(
713            feature = "arm64-codegen",
714            all(target_arch = "aarch64", not(feature = "x86_64-codegen"))
715        ))]
716        let (empty_result, empty_mir) = {
717            let state_size = layout
718                .merged_total_size
719                .checked_add(layout.triggered_bits_total_size)
720                .ok_or_else(|| {
721                    codegen_err(CodegenError::NativeEmission {
722                        source: emit::EmitError::Range("AArch64 simulation-state size overflow"),
723                    })
724                })?;
725            let result = emit::emit_empty(state_size)
726                .map_err(|source| codegen_err(CodegenError::NativeEmission { source }))?;
727            (result, "bb0:\n  Return\n".to_string())
728        };
729        let trace = capture_trace.then(|| emit::NativeFunctionTrace {
730            optimized_sir: "<empty native function>\n".into(),
731            reactive_graph: String::new(),
732            state_layout: String::new(),
733            mir_before_regalloc: empty_mir.clone(),
734            mir_after_late_memory_folds: empty_mir.clone(),
735            mir_after_scheduling: empty_mir.clone(),
736            mir_after_regalloc: empty_mir,
737            register_assignment: String::new(),
738            spill_frame_size: 0,
739            disassembly: emit::disassemble(&empty_result.code[..empty_result.text_size], 0),
740        });
741        let symbols = perf_symbols_for_emit_result(label, &empty_result);
742        return Ok(CompiledNativeFunction {
743            code: empty_result.code,
744            symbols,
745            trace,
746            required_state_size: empty_result.required_state_size as usize,
747            #[cfg(any(
748                feature = "x86_64-codegen",
749                all(target_arch = "x86_64", not(feature = "arm64-codegen"))
750            ))]
751            required_native_features: empty_result.required_image_features,
752            #[cfg(any(
753                feature = "arm64-codegen",
754                all(target_arch = "aarch64", not(feature = "x86_64-codegen"))
755            ))]
756            required_native_features: 0,
757        });
758    }
759
760    // Merge all EUs and compile the exact SIR/MIR function used at runtime.
761    if timing {
762        tracing::debug!(
763            "[native-timing] compile_units start label={label} eus={}",
764            units.len()
765        );
766    }
767    let start = timing.then(crate::timing::now);
768    let sir_eu = prepare_merged_sir(
769        units,
770        layout,
771        four_state,
772        label,
773        first_ff_unit,
774        diagnostics,
775        cancel,
776    )?;
777    if cancelled(cancel) {
778        return Err(cancelled_error());
779    }
780    let mut trace = capture_trace.then(emit::NativeFunctionTrace::default);
781    #[cfg(any(
782        feature = "x86_64-codegen",
783        all(target_arch = "x86_64", not(feature = "arm64-codegen"))
784    ))]
785    let emit_result = emit::emit_prepared_eu(
786        &sir_eu,
787        layout,
788        four_state,
789        label,
790        x86_options,
791        trace.as_mut(),
792        || cancelled(cancel),
793    )
794    .map_err(|source| {
795        if matches!(source, emit::ChainedEmitError::Cancelled) {
796            codegen_err(CodegenError::Cancelled)
797        } else {
798            codegen_err(CodegenError::NativePipeline { source })
799        }
800    })?;
801    #[cfg(any(
802        feature = "arm64-codegen",
803        all(target_arch = "aarch64", not(feature = "x86_64-codegen"))
804    ))]
805    let emit_result = emit::emit_prepared_eu(
806        &sir_eu,
807        layout,
808        four_state,
809        label,
810        x86_options.native_tick_loop,
811        trace.as_mut(),
812        || cancelled(cancel),
813    )
814    .map_err(|source| {
815        if matches!(source, emit::ChainedEmitError::Cancelled) {
816            codegen_err(CodegenError::Cancelled)
817        } else {
818            codegen_err(CodegenError::NativePipeline { source })
819        }
820    })?;
821    if let Some(start) = start {
822        tracing::debug!(
823            "[native-timing] compile_units done label={label} bytes={} elapsed={:?}",
824            emit_result.code.len(),
825            start.elapsed()
826        );
827    }
828    let symbols = perf_symbols_for_emit_result(label, &emit_result);
829    let required_state_size = emit_result.required_state_size as usize;
830    Ok(CompiledNativeFunction {
831        code: emit_result.code,
832        symbols,
833        trace,
834        required_state_size,
835        #[cfg(any(
836            feature = "x86_64-codegen",
837            all(target_arch = "x86_64", not(feature = "arm64-codegen"))
838        ))]
839        required_native_features: emit_result.required_image_features,
840        #[cfg(any(
841            feature = "arm64-codegen",
842            all(target_arch = "aarch64", not(feature = "x86_64-codegen"))
843        ))]
844        required_native_features: 0,
845    })
846}
847
848fn perf_symbols_for_emit_result(label: &str, result: &emit::EmitResult) -> Vec<jit_mem::JitSymbol> {
849    let code_len = result.text_size;
850    if result.block_offsets.is_empty() {
851        return Vec::new();
852    }
853
854    let mut blocks = result.block_offsets.clone();
855    blocks.sort_by_key(|(_, offset)| *offset);
856
857    let mut symbols = Vec::with_capacity(blocks.len() + 2);
858    let first_offset = blocks[0].1 as usize;
859    if first_offset > 0 {
860        symbols.push(jit_mem::JitSymbol {
861            offset: 0,
862            size: first_offset,
863            name: format!("{label}.prologue"),
864        });
865    }
866
867    for (idx, (block_id, offset)) in blocks.iter().enumerate() {
868        let start = *offset as usize;
869        let end = blocks
870            .get(idx + 1)
871            .map(|(_, next)| *next as usize)
872            .unwrap_or(code_len);
873        if end > start {
874            symbols.push(jit_mem::JitSymbol {
875                offset: start,
876                size: end - start,
877                name: format!("{label}.bb{}", block_id.0),
878            });
879        }
880    }
881
882    symbols
883}
884
885const NATIVE_CODE_ENTRY_ALIGNMENT: usize = 16;
886
887fn append_native_code(
888    image: &mut Vec<u8>,
889    entries: &mut Vec<NativeCodeEntry>,
890    image_symbols: &mut Vec<NativeCodeSymbol>,
891    name: String,
892    compiled: &CompiledNativeFunction,
893) -> Result<usize, SimulatorError> {
894    let offset = image
895        .len()
896        .checked_add(NATIVE_CODE_ENTRY_ALIGNMENT - 1)
897        .map(|value| value & !(NATIVE_CODE_ENTRY_ALIGNMENT - 1))
898        .ok_or_else(|| codegen_message("packed native code image alignment overflow"))?;
899    image.resize(offset, 0);
900    let end = offset
901        .checked_add(compiled.code.len())
902        .ok_or_else(|| codegen_message("packed native code image size overflow"))?;
903    image.extend_from_slice(&compiled.code);
904
905    if compiled.symbols.is_empty() {
906        image_symbols.push(NativeCodeSymbol {
907            offset,
908            size: compiled.code.len(),
909            name: name.clone(),
910        });
911    } else {
912        for symbol in &compiled.symbols {
913            let symbol_end = symbol
914                .offset
915                .checked_add(symbol.size)
916                .ok_or_else(|| codegen_message("native function symbol range overflow"))?;
917            if symbol_end > compiled.code.len() {
918                return Err(codegen_message(format!(
919                    "native function symbol `{}` exceeds its emitted code",
920                    symbol.name
921                )));
922            }
923            image_symbols.push(NativeCodeSymbol {
924                offset: offset + symbol.offset,
925                size: symbol.size,
926                name: format!("{name}.{}", symbol.name),
927            });
928        }
929    }
930
931    entries.push(NativeCodeEntry {
932        name,
933        offset,
934        size: end - offset,
935    });
936    Ok(offset)
937}
938
939fn native_function_at(
940    image: &jit_mem::JitCode,
941    offset: usize,
942) -> Result<NativeSimFunc, SimulatorError> {
943    let ptr = image
944        .entry_ptr(offset)
945        .ok_or_else(|| codegen_message("native function entry exceeds packed code image"))?;
946    // Safety: `offset` was returned by `append_native_code` for a complete
947    // function emitted with `NativeSimFunc`'s target ABI. `image` owns the
948    // executable allocation for at least as long as the returned pointer.
949    Ok(unsafe { std::mem::transmute::<*const u8, NativeSimFunc>(ptr) })
950}
951
952struct NativeCompileTask<'a> {
953    units: Vec<&'a crate::ir::ExecutionUnit<crate::ir::RegionedAbsoluteAddr>>,
954    label: &'static str,
955    first_ff_unit: Option<usize>,
956    bindings: Vec<String>,
957}
958
959type NativeTaskBindings = HashMap<(&'static str, AbsoluteAddr), usize>;
960
961fn collect_ff_compile_tasks(
962    sir: &LaidOutProgram,
963) -> (Vec<NativeCompileTask<'_>>, NativeTaskBindings) {
964    let mut tasks = Vec::new();
965    let mut task_bindings = HashMap::default();
966    collect_ff_compile_tasks_from(
967        sir,
968        &sir.sir.eval_apply_ffs,
969        "eval_apply_ff",
970        &mut tasks,
971        &mut task_bindings,
972    );
973    collect_ff_compile_tasks_from(
974        sir,
975        &sir.sir.eval_only_ffs,
976        "eval_only_ff",
977        &mut tasks,
978        &mut task_bindings,
979    );
980    collect_ff_compile_tasks_from(
981        sir,
982        &sir.sir.apply_ffs,
983        "apply_ff",
984        &mut tasks,
985        &mut task_bindings,
986    );
987    collect_comb_apply_compile_tasks(sir, &mut tasks, &mut task_bindings);
988    (tasks, task_bindings)
989}
990
991fn collect_comb_apply_compile_tasks<'a>(
992    sir: &'a LaidOutProgram,
993    tasks: &mut Vec<NativeCompileTask<'a>>,
994    task_bindings: &mut NativeTaskBindings,
995) {
996    const LABEL: &str = "eval_comb_apply_ff";
997    for (addr, ff_units) in &sir.sir.eval_apply_ffs {
998        let fused_units = sir.sir.eval_comb_apply_ffs.get(addr);
999        let (unit_refs, first_ff_unit) = if let Some(fused_units) = fused_units {
1000            (fused_units.iter().collect::<Vec<_>>(), None)
1001        } else {
1002            let mut unit_refs = sir.sir.eval_comb.iter().collect::<Vec<_>>();
1003            let first_ff_unit =
1004                (!unit_refs.is_empty() && !ff_units.is_empty()).then_some(unit_refs.len());
1005            unit_refs.extend(ff_units);
1006            (unit_refs, first_ff_unit)
1007        };
1008        let binding = format!("{LABEL} trigger={}", sir.get_path(addr));
1009        let index = if let Some(index) = tasks.iter().position(|task| {
1010            task.label == LABEL && task.first_ff_unit == first_ff_unit && task.units == unit_refs
1011        }) {
1012            tasks[index].bindings.push(binding);
1013            index
1014        } else {
1015            let index = tasks.len();
1016            tasks.push(NativeCompileTask {
1017                units: unit_refs,
1018                label: LABEL,
1019                first_ff_unit,
1020                bindings: vec![binding],
1021            });
1022            index
1023        };
1024        task_bindings.insert((LABEL, *addr), index);
1025    }
1026}
1027
1028fn collect_ff_compile_tasks_from<'a>(
1029    sir: &LaidOutProgram,
1030    ff_map: &'a HashMap<
1031        AbsoluteAddr,
1032        Vec<crate::ir::ExecutionUnit<crate::ir::RegionedAbsoluteAddr>>,
1033    >,
1034    label: &'static str,
1035    tasks: &mut Vec<NativeCompileTask<'a>>,
1036    task_bindings: &mut NativeTaskBindings,
1037) {
1038    for (addr, units) in ff_map {
1039        let unit_refs = units.iter().collect::<Vec<_>>();
1040        let binding = format!("{label} trigger={}", sir.get_path(addr));
1041        let index = if let Some(index) = tasks.iter().position(|task| task.units == unit_refs) {
1042            tasks[index].bindings.push(binding);
1043            index
1044        } else {
1045            let index = tasks.len();
1046            tasks.push(NativeCompileTask {
1047                units: unit_refs,
1048                label,
1049                first_ff_unit: None,
1050                bindings: vec![binding],
1051            });
1052            index
1053        };
1054        task_bindings.insert((label, *addr), index);
1055    }
1056}
1057
1058fn append_native_function_trace(
1059    optimized_sir: &mut String,
1060    mir: &mut String,
1061    reactive_graph: &mut String,
1062    state_layout: &mut String,
1063    name: &str,
1064    bindings: &[String],
1065    trace: &emit::NativeFunctionTrace,
1066) {
1067    let mut bindings = bindings.to_vec();
1068    bindings.sort();
1069    bindings.dedup();
1070
1071    optimized_sir.push_str(&format!("=== Native function {name} ===\n"));
1072    if !bindings.is_empty() {
1073        optimized_sir.push_str("Bindings:\n");
1074        for binding in &bindings {
1075            optimized_sir.push_str(&format!("  {binding}\n"));
1076        }
1077    }
1078    optimized_sir.push_str(&trace.optimized_sir);
1079    if !trace.optimized_sir.ends_with('\n') {
1080        optimized_sir.push('\n');
1081    }
1082    optimized_sir.push('\n');
1083
1084    mir.push_str(&format!("=== Native function {name} ===\n"));
1085    if !bindings.is_empty() {
1086        mir.push_str("Bindings:\n");
1087        for binding in &bindings {
1088            mir.push_str(&format!("  {binding}\n"));
1089        }
1090    }
1091    mir.push_str("--- MIR after main optimization, before regalloc-owned late folds ---\n");
1092    mir.push_str(&trace.mir_before_regalloc);
1093    if !trace.mir_before_regalloc.ends_with('\n') {
1094        mir.push('\n');
1095    }
1096    mir.push_str("--- MIR after late memory folds, before allocation-owned scheduling ---\n");
1097    mir.push_str(&trace.mir_after_late_memory_folds);
1098    if !trace.mir_after_late_memory_folds.ends_with('\n') {
1099        mir.push('\n');
1100    }
1101    mir.push_str("--- MIR after allocation-owned scheduling, before spill reconstruction ---\n");
1102    mir.push_str(&trace.mir_after_scheduling);
1103    if !trace.mir_after_scheduling.ends_with('\n') {
1104        mir.push('\n');
1105    }
1106    mir.push_str("--- MIR after register allocation and post-RA peepholes ---\n");
1107    mir.push_str(&trace.mir_after_regalloc);
1108    if !trace.mir_after_regalloc.ends_with('\n') {
1109        mir.push('\n');
1110    }
1111    mir.push_str(&format!("Spill frame: {} bytes\n", trace.spill_frame_size));
1112    mir.push_str("Register assignment:\n");
1113    mir.push_str(&trace.register_assignment);
1114    #[cfg(any(
1115        feature = "x86_64-codegen",
1116        all(target_arch = "x86_64", not(feature = "arm64-codegen"))
1117    ))]
1118    mir.push_str("x86-64 disassembly of emitted function:\n");
1119    #[cfg(any(
1120        feature = "arm64-codegen",
1121        all(target_arch = "aarch64", not(feature = "x86_64-codegen"))
1122    ))]
1123    mir.push_str("AArch64 disassembly of emitted function:\n");
1124    mir.push_str(&trace.disassembly);
1125    if !trace.disassembly.ends_with('\n') {
1126        mir.push('\n');
1127    }
1128    mir.push('\n');
1129
1130    if !trace.reactive_graph.is_empty() {
1131        reactive_graph.push_str(&format!("=== Native function {name} ===\n"));
1132        if !bindings.is_empty() {
1133            reactive_graph.push_str("Bindings:\n");
1134            for binding in &bindings {
1135                reactive_graph.push_str(&format!("  {binding}\n"));
1136            }
1137        }
1138        reactive_graph.push_str(&trace.reactive_graph);
1139        if !trace.reactive_graph.ends_with('\n') {
1140            reactive_graph.push('\n');
1141        }
1142        reactive_graph.push('\n');
1143    }
1144
1145    if !trace.state_layout.is_empty() {
1146        state_layout.push_str(&format!("=== Native function {name} ===\n"));
1147        if !bindings.is_empty() {
1148            state_layout.push_str("Bindings:\n");
1149            for binding in &bindings {
1150                state_layout.push_str(&format!("  {binding}\n"));
1151            }
1152        }
1153        state_layout.push_str(&trace.state_layout);
1154        if !trace.state_layout.ends_with('\n') {
1155            state_layout.push('\n');
1156        }
1157        state_layout.push('\n');
1158    }
1159}
1160
1161fn format_native_codegen_trace(
1162    comb: &CompiledNativeFunction,
1163    ff_codes: &HashMap<usize, CompiledNativeFunction>,
1164    tasks: &[NativeCompileTask<'_>],
1165) -> NativeCodegenTrace {
1166    let mut optimized_sir = String::from("=== Optimized SIR used by native emission ===\n");
1167    let mut mir = String::from("=== MIR used by native emission ===\n");
1168    let mut reactive_graph = String::from("=== Reactive clock-event projection oracle ===\n");
1169    let mut state_layout =
1170        String::from("=== Profile-selected native state-layout feasibility ===\n");
1171    append_native_function_trace(
1172        &mut optimized_sir,
1173        &mut mir,
1174        &mut reactive_graph,
1175        &mut state_layout,
1176        "eval_comb",
1177        &[],
1178        comb.trace
1179            .as_ref()
1180            .expect("explicit native trace must capture eval_comb"),
1181    );
1182
1183    let mut ff_entries = ff_codes
1184        .keys()
1185        .map(|&task_id| {
1186            let task = &tasks[task_id];
1187            let mut sort_key = task.bindings.clone();
1188            sort_key.sort();
1189            (sort_key, task_id, task)
1190        })
1191        .collect::<Vec<_>>();
1192    ff_entries.sort_by(|left, right| left.0.cmp(&right.0));
1193    let mut label_indices = HashMap::<&str, usize>::default();
1194    for (_, task_id, task) in ff_entries {
1195        let index = label_indices.entry(task.label).or_default();
1196        let name = format!("{}[{index}]", task.label);
1197        *index += 1;
1198        append_native_function_trace(
1199            &mut optimized_sir,
1200            &mut mir,
1201            &mut reactive_graph,
1202            &mut state_layout,
1203            &name,
1204            &task.bindings,
1205            ff_codes[&task_id]
1206                .trace
1207                .as_ref()
1208                .expect("explicit native trace must capture every FF function"),
1209        );
1210    }
1211    NativeCodegenTrace {
1212        optimized_sir,
1213        mir,
1214        reactive_graph,
1215        state_layout,
1216    }
1217}
1218
1219fn offset_registers(offset: &SIROffset, registers: &mut Vec<RegisterId>) {
1220    match offset {
1221        SIROffset::Dynamic(register) => registers.push(*register),
1222        SIROffset::Element {
1223            index,
1224            dynamic_bit_offset,
1225            ..
1226        } => {
1227            registers.push(*index);
1228            registers.extend(dynamic_bit_offset);
1229        }
1230        SIROffset::Static(_) | SIROffset::PackedElements { .. } => {}
1231    }
1232}
1233
1234fn instruction_registers<A>(instruction: &SIRInstruction<A>) -> Vec<RegisterId> {
1235    let mut registers = Vec::new();
1236    match instruction {
1237        SIRInstruction::Imm(..) => {}
1238        SIRInstruction::Binary(_, lhs, _, rhs) => registers.extend([*lhs, *rhs]),
1239        SIRInstruction::Unary(_, _, source) | SIRInstruction::Slice(_, source, _, _) => {
1240            registers.push(*source);
1241        }
1242        SIRInstruction::Load(_, _, offset, _) => offset_registers(offset, &mut registers),
1243        SIRInstruction::Store(_, offset, _, source, _, _) => {
1244            registers.push(*source);
1245            offset_registers(offset, &mut registers);
1246        }
1247        SIRInstruction::Commit(..) => {}
1248        SIRInstruction::Concat(_, sources) => registers.extend(sources),
1249        SIRInstruction::Mux(_, condition, then_value, else_value) => {
1250            registers.extend([*condition, *then_value, *else_value]);
1251        }
1252        SIRInstruction::RuntimeEvent { args, .. }
1253        | SIRInstruction::CombCaptureEvent { args, .. } => registers.extend(args),
1254        SIRInstruction::CombCaptureEnableIfChanged { old, new, .. } => {
1255            registers.extend([*old, *new]);
1256        }
1257    }
1258    registers
1259}
1260
1261fn comb_block_execution_order<A>(unit: &ExecutionUnit<A>) -> Vec<BlockId> {
1262    fn visit<A>(
1263        unit: &ExecutionUnit<A>,
1264        block_id: BlockId,
1265        visited: &mut HashSet<BlockId>,
1266        postorder: &mut Vec<BlockId>,
1267    ) {
1268        if !visited.insert(block_id) {
1269            return;
1270        }
1271        for successor in celox_sir::cfg::terminator_successors(&unit.blocks[&block_id].terminator) {
1272            visit(unit, successor, visited, postorder);
1273        }
1274        postorder.push(block_id);
1275    }
1276
1277    let mut visited = HashSet::default();
1278    let mut postorder = Vec::with_capacity(unit.blocks.len());
1279    visit(unit, unit.entry_block_id, &mut visited, &mut postorder);
1280    postorder.reverse();
1281    postorder
1282}
1283
1284fn is_comb_runtime_effect(instruction: &SIRInstruction<RegionedAbsoluteAddr>) -> bool {
1285    matches!(
1286        instruction,
1287        SIRInstruction::RuntimeEvent { .. }
1288            | SIRInstruction::CombCaptureEvent { .. }
1289            | SIRInstruction::CombCaptureEnableIfChanged { .. }
1290    )
1291}
1292
1293fn interleave_comb_runtime_effects(
1294    unit: &ExecutionUnit<RegionedAbsoluteAddr>,
1295    ordered_stores: &[(BlockId, usize)],
1296    store_units: Vec<ExecutionUnit<RegionedAbsoluteAddr>>,
1297) -> Vec<ExecutionUnit<RegionedAbsoluteAddr>> {
1298    let ordered_sites = comb_block_execution_order(unit)
1299        .into_iter()
1300        .flat_map(|block_id| {
1301            (0..unit.blocks[&block_id].instructions.len()).map(move |index| (block_id, index))
1302        })
1303        .collect::<Vec<_>>();
1304    let positions = ordered_sites
1305        .iter()
1306        .enumerate()
1307        .map(|(position, &site)| (site, position))
1308        .collect::<HashMap<_, _>>();
1309    let mut effect_groups = vec![Vec::new(); ordered_stores.len() + 1];
1310    for site in ordered_sites {
1311        if !is_comb_runtime_effect(&unit.blocks[&site.0].instructions[site.1]) {
1312            continue;
1313        }
1314        let boundary = ordered_stores
1315            .iter()
1316            .filter(|store| positions[store] < positions[&site])
1317            .count();
1318        effect_groups[boundary].push(site);
1319    }
1320    if effect_groups.iter().all(Vec::is_empty) {
1321        return store_units;
1322    }
1323
1324    let mut result = Vec::with_capacity(store_units.len() + effect_groups.len());
1325    let mut stores = store_units.into_iter();
1326    for (boundary, group) in effect_groups.into_iter().enumerate() {
1327        if !group.is_empty() {
1328            let group = group.into_iter().collect::<HashSet<_>>();
1329            let mut events = unit.clone();
1330            for (block_id, block) in &mut events.blocks {
1331                block.instructions = std::mem::take(&mut block.instructions)
1332                    .into_iter()
1333                    .enumerate()
1334                    .filter_map(|(index, instruction)| {
1335                        let site = (*block_id, index);
1336                        if matches!(
1337                            instruction,
1338                            SIRInstruction::Store(..) | SIRInstruction::Commit(..)
1339                        ) {
1340                            None
1341                        } else if is_comb_runtime_effect(&instruction) {
1342                            group.contains(&site).then_some(instruction)
1343                        } else {
1344                            Some(instruction)
1345                        }
1346                    })
1347                    .collect();
1348            }
1349            result.push(events);
1350        }
1351        if boundary < ordered_stores.len() {
1352            result.push(stores.next().unwrap());
1353        }
1354    }
1355    result
1356}
1357
1358fn split_comb_execution_unit(
1359    unit: &ExecutionUnit<RegionedAbsoluteAddr>,
1360) -> Vec<ExecutionUnit<RegionedAbsoluteAddr>> {
1361    if unit.blocks.len() != 1 {
1362        let definitions = unit
1363            .blocks
1364            .iter()
1365            .flat_map(|(block_id, block)| {
1366                block
1367                    .instructions
1368                    .iter()
1369                    .enumerate()
1370                    .filter_map(|(index, instruction)| {
1371                        instruction
1372                            .defined_register()
1373                            .map(|register| (register, (*block_id, index)))
1374                    })
1375            })
1376            .collect::<HashMap<_, _>>();
1377        let store_sites = comb_block_execution_order(unit)
1378            .into_iter()
1379            .flat_map(|block_id| {
1380                let block = &unit.blocks[&block_id];
1381                block
1382                    .instructions
1383                    .iter()
1384                    .enumerate()
1385                    .filter(|(_, instruction)| {
1386                        matches!(
1387                            instruction,
1388                            SIRInstruction::Store(..) | SIRInstruction::Commit(..)
1389                        )
1390                    })
1391                    .map(move |(index, _)| (block_id, index))
1392            })
1393            .collect::<Vec<_>>();
1394        if store_sites.is_empty() {
1395            return vec![unit.clone()];
1396        }
1397
1398        let instruction_at = |site: (BlockId, usize)| &unit.blocks[&site.0].instructions[site.1];
1399        let register_dependencies = store_sites
1400            .iter()
1401            .copied()
1402            .map(|store_site| {
1403                let mut dependencies = HashSet::default();
1404                let mut pending = instruction_registers(instruction_at(store_site));
1405                while let Some(register) = pending.pop() {
1406                    if !dependencies.insert(register) {
1407                        continue;
1408                    }
1409                    if let Some(&definition) = definitions.get(&register) {
1410                        pending.extend(instruction_registers(instruction_at(definition)));
1411                    }
1412                }
1413                (store_site, dependencies)
1414            })
1415            .collect::<HashMap<_, _>>();
1416        let store_source = |site| match instruction_at(site) {
1417            SIRInstruction::Store(_, _, _, source, _, _) => Some(*source),
1418            _ => None,
1419        };
1420        let mut remaining = store_sites;
1421        let mut ordered_stores = Vec::with_capacity(remaining.len());
1422        while !remaining.is_empty() {
1423            let next = remaining
1424                .iter()
1425                .position(|candidate| {
1426                    let candidate_source = store_source(*candidate);
1427                    !remaining.iter().any(|predecessor| {
1428                        if predecessor == candidate {
1429                            return false;
1430                        }
1431                        store_source(*predecessor).is_some_and(|source| {
1432                            Some(source) != candidate_source
1433                                && register_dependencies[candidate].contains(&source)
1434                        })
1435                    })
1436                })
1437                .unwrap_or(0);
1438            ordered_stores.push(remaining.remove(next));
1439        }
1440
1441        let split = ordered_stores
1442            .iter()
1443            .enumerate()
1444            .map(|(order, &target)| {
1445                let reloads = ordered_stores[..order]
1446                    .iter()
1447                    .filter_map(|&prior_site| {
1448                        let SIRInstruction::Store(address, offset, bits, source, _, _) =
1449                            instruction_at(prior_site)
1450                        else {
1451                            return None;
1452                        };
1453                        (register_dependencies[&target].contains(source)
1454                            && store_source(target) != Some(*source)
1455                            && unit.register_map[source].width() == *bits)
1456                            .then(|| (*source, (*address, offset.clone(), *bits)))
1457                    })
1458                    .collect::<HashMap<_, _>>();
1459                let mut extracted = unit.clone();
1460                for (block_id, block) in &mut extracted.blocks {
1461                    block.instructions = std::mem::take(&mut block.instructions)
1462                        .into_iter()
1463                        .enumerate()
1464                        .filter_map(|(index, instruction)| {
1465                            let site = (*block_id, index);
1466                            match instruction {
1467                                SIRInstruction::Store(..) | SIRInstruction::Commit(..) => {
1468                                    (site == target).then_some(instruction)
1469                                }
1470                                SIRInstruction::RuntimeEvent { .. }
1471                                | SIRInstruction::CombCaptureEvent { .. }
1472                                | SIRInstruction::CombCaptureEnableIfChanged { .. } => None,
1473                                _ => {
1474                                    if let Some(register) = instruction.defined_register()
1475                                        && let Some((address, offset, bits)) =
1476                                            reloads.get(&register)
1477                                    {
1478                                        Some(SIRInstruction::Load(
1479                                            register,
1480                                            *address,
1481                                            offset.clone(),
1482                                            *bits,
1483                                        ))
1484                                    } else {
1485                                        Some(instruction)
1486                                    }
1487                                }
1488                            }
1489                        })
1490                        .collect();
1491                }
1492                extracted
1493            })
1494            .collect::<Vec<_>>();
1495
1496        return interleave_comb_runtime_effects(unit, &ordered_stores, split);
1497    }
1498    let block = &unit.blocks[&unit.entry_block_id];
1499    if !block.params.is_empty() || block.terminator != SIRTerminator::Return {
1500        return vec![unit.clone()];
1501    }
1502
1503    let definitions = block
1504        .instructions
1505        .iter()
1506        .enumerate()
1507        .filter_map(|(index, instruction)| {
1508            instruction
1509                .defined_register()
1510                .map(|register| (register, index))
1511        })
1512        .collect::<HashMap<_, _>>();
1513    let store_indices = block
1514        .instructions
1515        .iter()
1516        .enumerate()
1517        .filter_map(|(index, instruction)| {
1518            matches!(
1519                instruction,
1520                SIRInstruction::Store(..) | SIRInstruction::Commit(..)
1521            )
1522            .then_some(index)
1523        })
1524        .collect::<Vec<_>>();
1525    if store_indices.is_empty() {
1526        return vec![unit.clone()];
1527    }
1528
1529    let register_dependencies = store_indices
1530        .iter()
1531        .copied()
1532        .map(|store_index| {
1533            let mut dependencies = HashSet::default();
1534            let mut pending = instruction_registers(&block.instructions[store_index]);
1535            while let Some(register) = pending.pop() {
1536                if !dependencies.insert(register) {
1537                    continue;
1538                }
1539                if let Some(&definition) = definitions.get(&register) {
1540                    pending.extend(instruction_registers(&block.instructions[definition]));
1541                }
1542            }
1543            (store_index, dependencies)
1544        })
1545        .collect::<HashMap<_, _>>();
1546    let store_source = |index| match &block.instructions[index] {
1547        SIRInstruction::Store(_, _, _, source, _, _) => Some(*source),
1548        _ => None,
1549    };
1550    let mut remaining = store_indices.clone();
1551    let mut ordered_stores = Vec::with_capacity(remaining.len());
1552    while !remaining.is_empty() {
1553        let next = remaining
1554            .iter()
1555            .position(|candidate| {
1556                let candidate_source = store_source(*candidate);
1557                !remaining.iter().any(|predecessor| {
1558                    if predecessor == candidate {
1559                        return false;
1560                    }
1561                    store_source(*predecessor).is_some_and(|source| {
1562                        Some(source) != candidate_source
1563                            && register_dependencies[candidate].contains(&source)
1564                    })
1565                })
1566            })
1567            .unwrap_or(0);
1568        ordered_stores.push(remaining.remove(next));
1569    }
1570
1571    let split = ordered_stores
1572        .iter()
1573        .enumerate()
1574        .map(|(order, &store_index)| {
1575            let reloads = ordered_stores[..order]
1576                .iter()
1577                .filter_map(|&prior_index| {
1578                    let SIRInstruction::Store(address, offset, bits, source, _, _) =
1579                        &block.instructions[prior_index]
1580                    else {
1581                        return None;
1582                    };
1583                    (register_dependencies[&store_index].contains(source)
1584                        && store_source(store_index) != Some(*source)
1585                        && unit.register_map[source].width() == *bits)
1586                        .then(|| (*source, (*address, offset.clone(), *bits)))
1587                })
1588                .collect::<HashMap<_, _>>();
1589            let mut prefix = HashSet::<usize>::default();
1590            let mut pending = instruction_registers(&block.instructions[store_index]);
1591            while let Some(register) = pending.pop() {
1592                let Some(&definition) = definitions.get(&register) else {
1593                    continue;
1594                };
1595                if !prefix.insert(definition) {
1596                    continue;
1597                }
1598                if let Some((_, offset, _)) = reloads.get(&register) {
1599                    offset_registers(offset, &mut pending);
1600                    continue;
1601                }
1602                pending.extend(instruction_registers(&block.instructions[definition]));
1603            }
1604            let mut prefix = prefix.into_iter().collect::<Vec<_>>();
1605            prefix.sort_unstable();
1606            let mut instructions = prefix
1607                .into_iter()
1608                .map(|index| {
1609                    let instruction = &block.instructions[index];
1610                    if let Some(register) = instruction.defined_register()
1611                        && let Some((address, offset, bits)) = reloads.get(&register)
1612                    {
1613                        return SIRInstruction::Load(register, *address, offset.clone(), *bits);
1614                    }
1615                    instruction.clone()
1616                })
1617                .collect::<Vec<_>>();
1618            instructions.push(block.instructions[store_index].clone());
1619            let split_block = celox_sir::BasicBlock {
1620                id: BlockId(0),
1621                params: Vec::new(),
1622                instructions,
1623                terminator: SIRTerminator::Return,
1624            };
1625            ExecutionUnit {
1626                entry_block_id: BlockId(0),
1627                blocks: [(BlockId(0), split_block)].into_iter().collect(),
1628                register_map: unit.register_map.clone(),
1629            }
1630        })
1631        .collect::<Vec<_>>();
1632    let ordered_store_sites = ordered_stores
1633        .iter()
1634        .map(|&index| (unit.entry_block_id, index))
1635        .collect::<Vec<_>>();
1636    interleave_comb_runtime_effects(unit, &ordered_store_sites, split)
1637}
1638
1639fn compile_program(
1640    laid_out: &LaidOutProgram,
1641    options: &SimulatorOptions,
1642    capture_trace: bool,
1643    cancel: Option<&CompileCancel>,
1644) -> Result<(NativeProgramImage, Option<NativeCodegenTrace>), SimulatorError> {
1645    const MAX_PARALLEL_NATIVE_FUNCTIONS: usize = 4;
1646
1647    let sir = laid_out;
1648    let layout = laid_out.layout();
1649    let (compile_tasks, task_bindings) = collect_ff_compile_tasks(sir);
1650    let next_task = AtomicUsize::new(0);
1651    let (comb_jit, compiled_ff_codes) = std::thread::scope(|scope| {
1652        let four_state = options.four_state;
1653        let x86_options = &options.x86_options;
1654        let comb_handle = scope.spawn(move || {
1655            if cancelled(cancel) {
1656                return Err(cancelled_error());
1657            }
1658            compile_units(
1659                &sir.sir.eval_comb,
1660                layout,
1661                four_state,
1662                "eval_comb",
1663                x86_options,
1664                capture_trace,
1665                &options.optimize_options.diagnostics,
1666                cancel,
1667            )
1668        });
1669        let task_worker_count = compile_tasks
1670            .len()
1671            .min(MAX_PARALLEL_NATIVE_FUNCTIONS.saturating_sub(1));
1672        let task_handles = (0..task_worker_count)
1673            .map(|_| {
1674                let next_task = &next_task;
1675                let compile_tasks = &compile_tasks;
1676                scope.spawn(move || {
1677                    let mut compiled = Vec::new();
1678                    loop {
1679                        if cancelled(cancel) {
1680                            return Err(cancelled_error());
1681                        }
1682                        let task_id = next_task.fetch_add(1, Ordering::Relaxed);
1683                        let Some(task) = compile_tasks.get(task_id) else {
1684                            break;
1685                        };
1686                        let code = compile_unit_refs(
1687                            &task.units,
1688                            layout,
1689                            four_state,
1690                            task.label,
1691                            task.first_ff_unit,
1692                            x86_options,
1693                            capture_trace,
1694                            &options.optimize_options.diagnostics,
1695                            cancel,
1696                        )?;
1697                        compiled.push((task_id, code));
1698                    }
1699                    Ok::<_, SimulatorError>(compiled)
1700                })
1701            })
1702            .collect::<Vec<_>>();
1703
1704        let comb_jit = comb_handle
1705            .join()
1706            .map_err(|_| codegen_message("native eval_comb compile thread panicked"))??;
1707        let mut compiled_ff_codes = HashMap::default();
1708        for handle in task_handles {
1709            let compiled = handle
1710                .join()
1711                .map_err(|_| codegen_message("native FF compile thread panicked"))??;
1712            compiled_ff_codes.extend(compiled);
1713        }
1714        Ok::<_, SimulatorError>((comb_jit, compiled_ff_codes))
1715    })?;
1716    // A foreign-interface image can request per-unit entries so force/release
1717    // can reapply overrides between procedural store boundaries. Ordinary
1718    // images do not compile or retain this duplicate combinational code.
1719    let force_store_boundaries = options.optimize_options.opt_level() == crate::OptLevel::O0;
1720    let comb_runtime_units = if options.native_force_support {
1721        sir.sir
1722            .eval_comb
1723            .iter()
1724            .flat_map(|unit| {
1725                if force_store_boundaries {
1726                    split_comb_execution_unit(unit)
1727                } else {
1728                    vec![unit.clone()]
1729                }
1730            })
1731            .collect::<Vec<_>>()
1732    } else {
1733        Vec::new()
1734    };
1735    let comb_unit_jits = comb_runtime_units
1736        .iter()
1737        .enumerate()
1738        .map(|(index, unit)| {
1739            if cancelled(cancel) {
1740                return Err(cancelled_error());
1741            }
1742            compile_unit_refs(
1743                &[unit],
1744                layout,
1745                options.four_state,
1746                &format!("eval_comb_unit[{index}]"),
1747                None,
1748                &options.x86_options,
1749                false,
1750                &options.optimize_options.diagnostics,
1751                cancel,
1752            )
1753        })
1754        .collect::<Result<Vec<_>, _>>()?;
1755    let codegen_trace = capture_trace
1756        .then(|| format_native_codegen_trace(&comb_jit, &compiled_ff_codes, &compile_tasks));
1757    let semantic_memory_size = layout
1758        .merged_total_size
1759        .checked_add(layout.triggered_bits_total_size)
1760        .expect("native semantic-memory size overflow");
1761    let native_memory_size = std::iter::once(comb_jit.required_state_size)
1762        .chain(
1763            compiled_ff_codes
1764                .values()
1765                .map(|compiled| compiled.required_state_size),
1766        )
1767        .chain(
1768            comb_unit_jits
1769                .iter()
1770                .map(|compiled| compiled.required_state_size),
1771        )
1772        .fold(semantic_memory_size, usize::max);
1773    let required_native_features = std::iter::once(comb_jit.required_native_features)
1774        .chain(
1775            compiled_ff_codes
1776                .values()
1777                .map(|compiled| compiled.required_native_features),
1778        )
1779        .chain(
1780            comb_unit_jits
1781                .iter()
1782                .map(|compiled| compiled.required_native_features),
1783        )
1784        .fold(0, |features, required| features | required);
1785    let mut packed_image = Vec::new();
1786    let mut code_entries = Vec::with_capacity(1 + compiled_ff_codes.len());
1787    let mut image_symbols = Vec::new();
1788    let comb_offset = append_native_code(
1789        &mut packed_image,
1790        &mut code_entries,
1791        &mut image_symbols,
1792        "eval_comb".into(),
1793        &comb_jit,
1794    )?;
1795    let mut comb_unit_offsets = Vec::with_capacity(comb_unit_jits.len());
1796    for (index, compiled) in comb_unit_jits.iter().enumerate() {
1797        comb_unit_offsets.push(append_native_code(
1798            &mut packed_image,
1799            &mut code_entries,
1800            &mut image_symbols,
1801            format!("eval_comb_unit[{index}]"),
1802            compiled,
1803        )?);
1804    }
1805    let mut compiled_ff_keys = compiled_ff_codes.keys().copied().collect::<Vec<_>>();
1806    compiled_ff_keys.sort_unstable();
1807    let mut task_offsets = HashMap::default();
1808    let mut label_indices = HashMap::<&str, usize>::default();
1809    for &task_id in &compiled_ff_keys {
1810        let task = &compile_tasks[task_id];
1811        let index = label_indices.entry(task.label).or_default();
1812        let name = format!("{}[{index}]", task.label);
1813        *index += 1;
1814        let offset = append_native_code(
1815            &mut packed_image,
1816            &mut code_entries,
1817            &mut image_symbols,
1818            name,
1819            &compiled_ff_codes[&task_id],
1820        )?;
1821        task_offsets.insert(task_id, offset);
1822    }
1823    // Bind semantic event identities to image-relative function offsets. The
1824    // precompiled runtime turns these into process-local pointers after it has
1825    // copied the image into executable memory.
1826    let mut next_id = 0usize;
1827    let mut id_to_addr = Vec::new();
1828    let mut id_to_event = Vec::new();
1829    let mut event_map = HashMap::default();
1830    let mut eval_only_event_map = HashMap::default();
1831    let mut apply_event_map = HashMap::default();
1832    let mut addr_to_id = HashMap::default();
1833    let compile_ff_group = |ff_map: &HashMap<
1834        AbsoluteAddr,
1835        Vec<crate::ir::ExecutionUnit<crate::ir::RegionedAbsoluteAddr>>,
1836    >,
1837                            label: &'static str,
1838                            event_map_out: &mut HashMap<AbsoluteAddr, NativeEventImageRef>,
1839                            addr_to_id: &mut HashMap<AbsoluteAddr, usize>,
1840                            compiled_ff_cache: &HashMap<usize, usize>,
1841                            comb_apply_label: Option<&'static str>,
1842                            next_id: &mut usize,
1843                            id_to_addr: &mut Vec<AbsoluteAddr>,
1844                            id_to_event: &mut Vec<NativeEventImageRef>|
1845     -> Result<(), SimulatorError> {
1846        for addr in ff_map.keys() {
1847            let canonical = sir.design.events.canonical(*addr);
1848            if let Some(&event) = event_map_out.get(&canonical) {
1849                event_map_out.insert(*addr, event);
1850                continue;
1851            }
1852
1853            let task_id = task_bindings[&(label, *addr)];
1854            let func_offset = compiled_ff_cache[&task_id];
1855            let comb_apply_offset = comb_apply_label
1856                .map(|label| {
1857                    let task_id = task_bindings[&(label, *addr)];
1858                    compiled_ff_cache[&task_id]
1859                })
1860                .unwrap_or(func_offset);
1861
1862            let (id, is_new_id) = if let Some(&id) = addr_to_id.get(&canonical) {
1863                (id, false)
1864            } else {
1865                let id = *next_id;
1866                *next_id += 1;
1867                addr_to_id.insert(canonical, id);
1868                id_to_addr.push(canonical);
1869                (id, true)
1870            };
1871
1872            let event = NativeEventImageRef {
1873                func_offset,
1874                comb_apply_offset,
1875                addr: canonical,
1876                id,
1877            };
1878            event_map_out.insert(canonical, event);
1879            if *addr != canonical {
1880                event_map_out.insert(*addr, event);
1881            }
1882            if is_new_id {
1883                id_to_event.push(event);
1884            }
1885        }
1886        Ok(())
1887    };
1888
1889    compile_ff_group(
1890        &sir.sir.eval_apply_ffs,
1891        "eval_apply_ff",
1892        &mut event_map,
1893        &mut addr_to_id,
1894        &task_offsets,
1895        Some("eval_comb_apply_ff"),
1896        &mut next_id,
1897        &mut id_to_addr,
1898        &mut id_to_event,
1899    )?;
1900    compile_ff_group(
1901        &sir.sir.eval_only_ffs,
1902        "eval_only_ff",
1903        &mut eval_only_event_map,
1904        &mut addr_to_id,
1905        &task_offsets,
1906        None,
1907        &mut next_id,
1908        &mut id_to_addr,
1909        &mut id_to_event,
1910    )?;
1911    compile_ff_group(
1912        &sir.sir.apply_ffs,
1913        "apply_ff",
1914        &mut apply_event_map,
1915        &mut addr_to_id,
1916        &task_offsets,
1917        None,
1918        &mut next_id,
1919        &mut id_to_addr,
1920        &mut id_to_event,
1921    )?;
1922    // Pre-compute 4-state initialization regions
1923    let mut four_state_inits = Vec::new();
1924    if options.four_state {
1925        for (addr, &offset) in &layout.offsets {
1926            let is_4state = layout.is_4states.get(addr).copied().unwrap_or(false);
1927            if is_4state {
1928                let allocated_size = layout.plane_size(addr);
1929                four_state_inits.push((offset, allocated_size));
1930            }
1931        }
1932        for (addr, &rel_offset) in &layout.working_offsets {
1933            let offset = layout.working_base_offset + rel_offset;
1934            let is_4state = layout.is_4states.get(addr).copied().unwrap_or(false);
1935            if is_4state {
1936                let allocated_size = layout.plane_size(addr);
1937                four_state_inits.push((offset, allocated_size));
1938            }
1939        }
1940    }
1941
1942    Ok((
1943        NativeProgramImage {
1944            code: packed_image,
1945            code_entries,
1946            symbols: image_symbols,
1947            comb_offset,
1948            comb_unit_offsets,
1949            required_native_features,
1950            event_map,
1951            eval_only_event_map,
1952            apply_event_map,
1953            id_to_addr,
1954            id_to_event,
1955            reflection: sir.runtime().build_design_reflection(layout),
1956            design: sir.runtime().design.clone(),
1957            testbench: sir.runtime().testbench.clone(),
1958            runtime_schema: NativeRuntimeSchema {
1959                runtime_errors: sir.runtime().runtime_schema.runtime_errors.clone(),
1960                runtime_event_sites: sir.runtime().runtime_schema.runtime_event_sites.clone(),
1961                comb_observers: sir.runtime().runtime_schema.comb_observers.clone(),
1962                testbench_read_roots: sir.runtime().runtime_schema.testbench_read_roots.clone(),
1963                rtl_writes: sir.runtime().runtime_schema.rtl_writes.clone(),
1964            },
1965            layout: layout.clone(),
1966            native_memory_size,
1967            options: NativeRuntimeOptions {
1968                four_state: options.four_state,
1969                native_tick_loop: options.x86_options.native_tick_loop,
1970                native_force_support: options.native_force_support,
1971                perf_map: options.x86_options.diagnostics.perf_map,
1972            },
1973            four_state_inits,
1974        },
1975        codegen_trace,
1976    ))
1977}
1978
1979// ────────────────────────────────────────────────────────────────
1980// NativeBackend
1981// ────────────────────────────────────────────────────────────────
1982
1983pub struct NativeBackend {
1984    compiled: Arc<SharedNativeCode>,
1985    memory: MemoryImage,
1986    runtime_event_buffer: Arc<RuntimeEventBuffer>,
1987    comb_capture_enabled: Vec<u8>,
1988    execution_timing: Option<NativeExecutionTiming>,
1989}
1990
1991fn write_bits_to_memory_from(
1992    memory: &mut [u8],
1993    destination_bit_offset: usize,
1994    bit_width: usize,
1995    source: &[u8],
1996    source_bit_offset: usize,
1997) {
1998    for bit in 0..bit_width {
1999        let source_bit = source_bit_offset + bit;
2000        let value = (source[source_bit / 8] >> (source_bit % 8)) & 1;
2001        let destination_bit = destination_bit_offset + bit;
2002        let destination = &mut memory[destination_bit / 8];
2003        let mask = 1u8 << (destination_bit % 8);
2004        if value == 0 {
2005            *destination &= !mask;
2006        } else {
2007            *destination |= mask;
2008        }
2009    }
2010}
2011
2012fn write_bits_to_memory(
2013    memory: &mut [u8],
2014    destination_bit_offset: usize,
2015    bit_width: usize,
2016    source: &[u8],
2017) {
2018    write_bits_to_memory_from(memory, destination_bit_offset, bit_width, source, 0);
2019}
2020
2021fn write_initial_run_to_plane(
2022    memory: &mut [u8],
2023    signal: SignalRef,
2024    mask_plane: bool,
2025    run: &InitialStateWriteRun,
2026    source: &[u8],
2027) {
2028    let Some(array) = signal.array_layout else {
2029        let plane_size = signal.width.div_ceil(8);
2030        let destination_bit_offset =
2031            (signal.offset + usize::from(mask_plane) * plane_size) * 8 + run.bit_offset;
2032        write_bits_to_memory(memory, destination_bit_offset, run.bit_width, source);
2033        return;
2034    };
2035
2036    let plane_offset = signal.offset + usize::from(mask_plane) * array.plane_size;
2037    let mut consumed = 0usize;
2038    while consumed < run.bit_width {
2039        let logical_offset = run.bit_offset + consumed;
2040        let element = logical_offset / array.element_width;
2041        let intra_element = logical_offset % array.element_width;
2042        let part_width = (run.bit_width - consumed).min(array.element_width - intra_element);
2043        let destination_bit_offset =
2044            (plane_offset + element * array.element_stride) * 8 + intra_element;
2045
2046        if consumed.is_multiple_of(8)
2047            && destination_bit_offset.is_multiple_of(8)
2048            && part_width.is_multiple_of(8)
2049        {
2050            let source_byte = consumed / 8;
2051            let destination_byte = destination_bit_offset / 8;
2052            let byte_width = part_width / 8;
2053            memory[destination_byte..destination_byte + byte_width]
2054                .copy_from_slice(&source[source_byte..source_byte + byte_width]);
2055        } else {
2056            write_bits_to_memory_from(memory, destination_bit_offset, part_width, source, consumed);
2057        }
2058        consumed += part_width;
2059    }
2060}
2061
2062impl NativeBackend {
2063    pub(crate) fn eval_comb_units_with(
2064        &mut self,
2065        mut after_unit: impl FnMut(&mut Self),
2066    ) -> Result<(), SimulatorErrorCode> {
2067        let funcs = self.compiled.comb_unit_funcs.clone();
2068        if funcs.is_empty() {
2069            return self.eval_comb();
2070        }
2071        for func in funcs {
2072            self.call_func_timed(func)?;
2073            after_unit(self);
2074        }
2075        Ok(())
2076    }
2077
2078    /// Compile a pointer-free native image without attaching it to executable
2079    /// memory. A precompiled runtime can load the result with
2080    /// [`SharedNativeCode::from_image`].
2081    pub fn compile_image(
2082        laid_out: &LaidOutProgram,
2083        options: &SimulatorOptions,
2084    ) -> Result<NativeProgramImage, SimulatorError> {
2085        let (image, trace) = compile_program(laid_out, options, false, None)?;
2086        debug_assert!(trace.is_none());
2087        Ok(image)
2088    }
2089
2090    /// Compile with cooperative cancellation observed at task and phase
2091    /// boundaries.
2092    ///
2093    /// When `cancel` is triggered the pipeline unwinds with
2094    /// [`CodegenError::Cancelled`] as soon as the unit in flight completes;
2095    /// an uncancelled run is unchanged apart from one flag load per
2096    /// boundary.
2097    pub(crate) fn compile_image_with_cancel(
2098        laid_out: &LaidOutProgram,
2099        options: &SimulatorOptions,
2100        cancel: &CompileCancel,
2101    ) -> Result<NativeProgramImage, SimulatorError> {
2102        let (image, trace) = compile_program(laid_out, options, false, Some(cancel))?;
2103        debug_assert!(trace.is_none());
2104        Ok(image)
2105    }
2106
2107    /// Compile with cooperative cancellation plus code-generation tracing.
2108    pub(crate) fn compile_image_with_cancel_and_trace(
2109        laid_out: &LaidOutProgram,
2110        options: &SimulatorOptions,
2111        cancel: &CompileCancel,
2112    ) -> Result<(NativeProgramImage, NativeCodegenTrace), SimulatorError> {
2113        let (image, trace) = compile_program(laid_out, options, true, Some(cancel))?;
2114        Ok((
2115            image,
2116            trace.expect("trace-enabled native compilation must return a trace"),
2117        ))
2118    }
2119
2120    pub(crate) fn compile_image_with_codegen_trace(
2121        laid_out: &LaidOutProgram,
2122        options: &SimulatorOptions,
2123    ) -> Result<(NativeProgramImage, NativeCodegenTrace), SimulatorError> {
2124        let (image, trace) = compile_program(laid_out, options, true, None)?;
2125        Ok((
2126            image,
2127            trace.expect("trace-enabled native compilation must return a trace"),
2128        ))
2129    }
2130
2131    /// Load a compiler-produced image into executable memory and create a
2132    /// backend instance for it.
2133    ///
2134    /// # Safety
2135    ///
2136    /// The image's machine code must come from a trusted compiler or image
2137    /// container. Structural validation does not authenticate code before it
2138    /// is mapped executable and invoked.
2139    pub unsafe fn from_image(image: NativeProgramImage) -> Result<Self, SimulatorError> {
2140        // Safety: upheld by this constructor's caller.
2141        let shared = Arc::new(unsafe { SharedNativeCode::from_image(image)? });
2142        Ok(Self::from_shared(shared))
2143    }
2144
2145    pub fn new(
2146        laid_out: &LaidOutProgram,
2147        options: &SimulatorOptions,
2148    ) -> Result<Self, SimulatorError> {
2149        let image = Self::compile_image(laid_out, options)?;
2150        // Safety: `image` was produced in-process by the Celox compiler above.
2151        unsafe { Self::from_image(image) }
2152    }
2153
2154    #[cfg(any(
2155        all(target_arch = "x86_64", not(feature = "arm64-codegen")),
2156        all(target_arch = "aarch64", not(feature = "x86_64-codegen"))
2157    ))]
2158    pub(crate) fn new_with_codegen_trace(
2159        laid_out: &LaidOutProgram,
2160        options: &SimulatorOptions,
2161    ) -> Result<(Self, NativeCodegenTrace), SimulatorError> {
2162        let (image, trace) = Self::compile_image_with_codegen_trace(laid_out, options)?;
2163        // Safety: `image` was produced in-process by the Celox compiler above.
2164        let shared = unsafe { SharedNativeCode::from_image(image)? };
2165        let backend = Self::from_shared(Arc::new(shared));
2166        Ok((backend, trace))
2167    }
2168
2169    /// Create a new backend instance from shared compiled code.
2170    /// Each instance gets its own simulation state memory.
2171    pub fn from_shared(shared: Arc<SharedNativeCode>) -> Self {
2172        let mem_size_words = shared.native_memory_size.div_ceil(8);
2173        let mut memory = MemoryImage::zeroed(mem_size_words + 1); // +1 for safety
2174        let runtime_event_buffer = Arc::new(RuntimeEventBuffer::new(
2175            shared.layout.runtime_event_buffer_size,
2176        ));
2177        let comb_capture_enabled = vec![0; shared.layout.runtime_event_site_layouts.len().max(1)];
2178
2179        // Initialize 4-state regions to X (v=1, m=1)
2180        for &(offset, allocated_size) in &shared.four_state_inits {
2181            unsafe {
2182                let base_ptr = (memory.as_mut_ptr() as *mut u8).add(offset);
2183                std::ptr::write_bytes(base_ptr, 0xFF, allocated_size);
2184                let mask_ptr = base_ptr.add(allocated_size);
2185                std::ptr::write_bytes(mask_ptr, 0xFF, allocated_size);
2186            }
2187        }
2188
2189        let mut backend = Self {
2190            compiled: shared,
2191            memory,
2192            runtime_event_buffer,
2193            comb_capture_enabled,
2194            execution_timing: None,
2195        };
2196        backend.install_event_buffers();
2197        let compiled = Arc::clone(&backend.compiled);
2198        backend.apply_initial_values(&compiled.program_image.design.initial_state);
2199        backend
2200    }
2201
2202    /// Build a backend from compiled code plus an existing live simulation
2203    /// state, e.g. when a tiered simulation promotes from the interpreter.
2204    ///
2205    /// The caller must guarantee the state was produced against the same
2206    /// laid-out program (identical layout), and that the event buffer `Arc`
2207    /// is the one referenced by the state header so its pointer stays valid.
2208    pub(crate) fn adopt_shared_with_state(
2209        shared: Arc<SharedNativeCode>,
2210        memory: MemoryImage,
2211        runtime_event_buffer: Arc<RuntimeEventBuffer>,
2212        comb_capture_enabled: Vec<u8>,
2213    ) -> Self {
2214        Self {
2215            compiled: shared,
2216            memory,
2217            runtime_event_buffer,
2218            comb_capture_enabled,
2219            execution_timing: None,
2220        }
2221    }
2222
2223    fn apply_initial_values(&mut self, initial_state: &[InitialStateValue<AbsoluteAddr>]) {
2224        for init in initial_state {
2225            let signal = self.resolve_signal(&init.address);
2226            match &init.data {
2227                InitialStateData::Packed {
2228                    value,
2229                    mask,
2230                    written_mask,
2231                } => {
2232                    let width_mask = if signal.width == 0 {
2233                        BigUint::default()
2234                    } else {
2235                        (BigUint::from(1u8) << signal.width) - BigUint::from(1u8)
2236                    };
2237                    let preserve_mask = &width_mask ^ (written_mask & &width_mask);
2238                    let (current_value, current_mask) = self.get_four_state(signal);
2239                    let value = (current_value & &preserve_mask) | (value & written_mask);
2240                    let mask = (current_mask & &preserve_mask) | (mask & written_mask);
2241                    if self.compiled.options.four_state && signal.is_4state {
2242                        self.set_four_state(signal, value, mask);
2243                    } else {
2244                        let known_mask = &width_mask ^ (&mask & &width_mask);
2245                        self.set_wide(signal, value & known_mask);
2246                    }
2247                }
2248                InitialStateData::Writes(runs) => self.apply_initial_memory_writes(signal, runs),
2249            }
2250        }
2251    }
2252
2253    fn apply_initial_memory_writes(&mut self, signal: SignalRef, runs: &[InitialStateWriteRun]) {
2254        let value_byte_size = signal.width.div_ceil(8);
2255        let write_mask = self.compiled.options.four_state && signal.is_4state;
2256        let mem = self.mem_bytes_mut();
2257
2258        for run in runs {
2259            if run.bit_width == 0 {
2260                continue;
2261            }
2262            if signal.array_layout.is_some() {
2263                write_initial_run_to_plane(mem, signal, false, run, &run.value_bytes);
2264                if write_mask {
2265                    write_initial_run_to_plane(mem, signal, true, run, &run.mask_bytes);
2266                }
2267                continue;
2268            }
2269            if run.bit_offset.is_multiple_of(8) && run.bit_width.is_multiple_of(8) {
2270                let byte_offset = run.bit_offset / 8;
2271                let byte_width = run.bit_width / 8;
2272                let value_offset = signal.offset + byte_offset;
2273                mem[value_offset..value_offset + byte_width]
2274                    .copy_from_slice(&run.value_bytes[..byte_width]);
2275                if write_mask {
2276                    let mask_offset = signal.offset + value_byte_size + byte_offset;
2277                    mem[mask_offset..mask_offset + byte_width]
2278                        .copy_from_slice(&run.mask_bytes[..byte_width]);
2279                }
2280                continue;
2281            }
2282
2283            write_bits_to_memory(
2284                mem,
2285                signal.offset * 8 + run.bit_offset,
2286                run.bit_width,
2287                &run.value_bytes,
2288            );
2289            if write_mask {
2290                write_bits_to_memory(
2291                    mem,
2292                    (signal.offset + value_byte_size) * 8 + run.bit_offset,
2293                    run.bit_width,
2294                    &run.mask_bytes,
2295                );
2296            }
2297        }
2298    }
2299
2300    /// Start a fresh opt-in measurement of generated native function calls.
2301    pub fn start_execution_timing(&mut self) {
2302        self.execution_timing = Some(NativeExecutionTiming::default());
2303    }
2304
2305    /// Stop timing and return the accumulated generated-code interval.
2306    pub fn finish_execution_timing(&mut self) -> Option<NativeExecutionTiming> {
2307        self.execution_timing.take()
2308    }
2309
2310    fn install_event_buffers(&mut self) {
2311        use crate::backend::memory_layout::{
2312            STATE_HEADER_COMB_CAPTURE_ENABLED_ADDR_OFFSET, STATE_HEADER_RUNTIME_EVENT_ADDR_OFFSET,
2313        };
2314
2315        let addr = self.runtime_event_buffer.as_mut_ptr() as u64;
2316        let ptr = unsafe {
2317            (self.memory.as_mut_ptr() as *mut u8).add(STATE_HEADER_RUNTIME_EVENT_ADDR_OFFSET)
2318                as *mut u64
2319        };
2320        unsafe {
2321            std::ptr::write_unaligned(ptr, addr);
2322        }
2323        let addr = self.comb_capture_enabled.as_ptr() as u64;
2324        let ptr = unsafe {
2325            (self.memory.as_mut_ptr() as *mut u8).add(STATE_HEADER_COMB_CAPTURE_ENABLED_ADDR_OFFSET)
2326                as *mut u64
2327        };
2328        unsafe {
2329            std::ptr::write_unaligned(ptr, addr);
2330        }
2331    }
2332
2333    /// Get the shared compiled code handle.
2334    pub fn shared_code(&self) -> Arc<SharedNativeCode> {
2335        Arc::clone(&self.compiled)
2336    }
2337
2338    fn mem_ptr(&self) -> *const u8 {
2339        self.memory.as_ptr() as *const u8
2340    }
2341
2342    fn mem_mut_ptr(&mut self) -> *mut u8 {
2343        self.memory.as_mut_ptr() as *mut u8
2344    }
2345
2346    fn mem_bytes(&self) -> &[u8] {
2347        let ptr = self.mem_ptr();
2348        let len = self.memory.len_words() * 8;
2349        unsafe { std::slice::from_raw_parts(ptr, len) }
2350    }
2351
2352    fn mem_bytes_mut(&mut self) -> &mut [u8] {
2353        let ptr = self.mem_mut_ptr();
2354        let len = self.memory.len_words() * 8;
2355        unsafe { std::slice::from_raw_parts_mut(ptr, len) }
2356    }
2357
2358    fn read_signal_plane(&self, signal: SignalRef, mask_plane: bool) -> BigUint {
2359        let bytes = self.mem_bytes();
2360        let Some(array) = signal.array_layout else {
2361            let byte_size = get_byte_size(signal.width);
2362            let plane_offset = signal.offset + usize::from(mask_plane) * byte_size;
2363            let mut value = BigUint::from_bytes_le(&bytes[plane_offset..plane_offset + byte_size]);
2364            if !signal.width.is_multiple_of(8) {
2365                value &= (BigUint::from(1u8) << signal.width) - BigUint::from(1u8);
2366            }
2367            return value;
2368        };
2369
2370        let plane_offset = signal.offset + usize::from(mask_plane) * array.plane_size;
2371        let element_bytes = get_byte_size(array.element_width);
2372        let element_mask = (BigUint::from(1u8) << array.element_width) - BigUint::from(1u8);
2373        let mut value = BigUint::from(0u8);
2374        for element in 0..array.element_count {
2375            let start = plane_offset + element * array.element_stride;
2376            let element_value =
2377                BigUint::from_bytes_le(&bytes[start..start + element_bytes]) & &element_mask;
2378            value |= element_value << (element * array.element_width);
2379        }
2380        value
2381    }
2382
2383    fn write_signal_plane(&mut self, signal: SignalRef, mask_plane: bool, value: &BigUint) {
2384        let Some(array) = signal.array_layout else {
2385            let byte_size = get_byte_size(signal.width);
2386            let plane_offset = signal.offset + usize::from(mask_plane) * byte_size;
2387            let bytes = self.mem_bytes_mut();
2388            bytes[plane_offset..plane_offset + byte_size].fill(0);
2389            let value_bytes = value.to_bytes_le();
2390            let copy_len = value_bytes.len().min(byte_size);
2391            bytes[plane_offset..plane_offset + copy_len].copy_from_slice(&value_bytes[..copy_len]);
2392            if !signal.width.is_multiple_of(8) && byte_size != 0 {
2393                bytes[plane_offset + byte_size - 1] &= (1u8 << (signal.width % 8)) - 1;
2394            }
2395            return;
2396        };
2397
2398        let plane_offset = signal.offset + usize::from(mask_plane) * array.plane_size;
2399        let element_bytes = get_byte_size(array.element_width);
2400        let element_mask = (BigUint::from(1u8) << array.element_width) - BigUint::from(1u8);
2401        let bytes = self.mem_bytes_mut();
2402        bytes[plane_offset..plane_offset + array.plane_size].fill(0);
2403        for element in 0..array.element_count {
2404            let element_value = (value >> (element * array.element_width)) & &element_mask;
2405            let value_bytes = element_value.to_bytes_le();
2406            let copy_len = value_bytes.len().min(element_bytes);
2407            let start = plane_offset + element * array.element_stride;
2408            bytes[start..start + copy_len].copy_from_slice(&value_bytes[..copy_len]);
2409        }
2410    }
2411
2412    fn call_func(memory: &mut [u64], func: NativeSimFunc) -> Result<(), SimulatorErrorCode> {
2413        let ptr = memory.as_mut_ptr() as *mut u8;
2414        let ret = unsafe { func(ptr) };
2415        match ret {
2416            0 => Ok(()),
2417            code if code > 0 => Err(SimulatorErrorCode::DetectedTrueLoopCode(code)),
2418            _ => Err(SimulatorErrorCode::InternalError),
2419        }
2420    }
2421
2422    fn call_func_timed(&mut self, func: NativeSimFunc) -> Result<(), SimulatorErrorCode> {
2423        let Some(_) = self.execution_timing else {
2424            return Self::call_func(self.memory.as_mut_slice(), func);
2425        };
2426        let start = Instant::now();
2427        let result = Self::call_func(self.memory.as_mut_slice(), func);
2428        let elapsed = start.elapsed();
2429        let timing = self
2430            .execution_timing
2431            .as_mut()
2432            .expect("native execution timing was enabled before the call");
2433        timing.elapsed = timing.elapsed.saturating_add(elapsed);
2434        timing.calls = timing.calls.saturating_add(1);
2435        result
2436    }
2437
2438    fn call_func_many(
2439        memory: &mut [u64],
2440        func: NativeSimFunc,
2441        count: u64,
2442    ) -> (u64, Result<(), SimulatorErrorCode>) {
2443        use crate::backend::memory_layout::STATE_HEADER_NATIVE_LOOP_REMAINING_OFFSET;
2444
2445        if count == 0 {
2446            return (0, Ok(()));
2447        }
2448        let remaining_word = STATE_HEADER_NATIVE_LOOP_REMAINING_OFFSET / 8;
2449        memory[remaining_word] = count;
2450        let ptr = memory.as_mut_ptr() as *mut u8;
2451        let ret = unsafe { func(ptr) };
2452        let completed = count.saturating_sub(memory[remaining_word]);
2453        let result = match ret {
2454            0 => Ok(()),
2455            code if code > 0 => Err(SimulatorErrorCode::DetectedTrueLoopCode(code)),
2456            _ => Err(SimulatorErrorCode::InternalError),
2457        };
2458        (completed, result)
2459    }
2460
2461    fn call_func_many_timed(
2462        &mut self,
2463        func: NativeSimFunc,
2464        count: u64,
2465    ) -> (u64, Result<(), SimulatorErrorCode>) {
2466        if self.execution_timing.is_none() || count == 0 {
2467            return Self::call_func_many(self.memory.as_mut_slice(), func, count);
2468        }
2469        let start = Instant::now();
2470        let result = Self::call_func_many(self.memory.as_mut_slice(), func, count);
2471        let elapsed = start.elapsed();
2472        let timing = self
2473            .execution_timing
2474            .as_mut()
2475            .expect("native execution timing was enabled before the call");
2476        timing.elapsed = timing.elapsed.saturating_add(elapsed);
2477        timing.calls = timing.calls.saturating_add(1);
2478        result
2479    }
2480}
2481
2482impl super::super::SimBackend for NativeBackend {
2483    type Event = NativeEventRef;
2484
2485    fn eval_comb(&mut self) -> Result<(), SimulatorErrorCode> {
2486        let func = self.compiled.comb_func;
2487        self.call_func_timed(func)
2488    }
2489
2490    fn eval_apply_ff_at(&mut self, event: NativeEventRef) -> Result<(), SimulatorErrorCode> {
2491        self.call_func_timed(event.func)
2492    }
2493
2494    fn eval_comb_apply_ff_at(&mut self, event: NativeEventRef) -> Result<(), SimulatorErrorCode> {
2495        self.call_func_timed(event.comb_apply_func)
2496    }
2497
2498    fn eval_comb_apply_ff_many_at(
2499        &mut self,
2500        event: NativeEventRef,
2501        count: u64,
2502    ) -> (u64, Result<(), SimulatorErrorCode>) {
2503        if self.compiled.options.native_tick_loop {
2504            self.call_func_many_timed(event.comb_apply_func, count)
2505        } else if count == 0 {
2506            (0, Ok(()))
2507        } else {
2508            (1, self.call_func_timed(event.comb_apply_func))
2509        }
2510    }
2511
2512    fn eval_only_ff_at(&mut self, event: NativeEventRef) -> Result<(), SimulatorErrorCode> {
2513        self.call_func_timed(event.func)
2514    }
2515
2516    fn apply_ff_at(&mut self, event: NativeEventRef) -> Result<(), SimulatorErrorCode> {
2517        self.call_func_timed(event.func)
2518    }
2519
2520    fn resolve_signal(&self, addr: &AbsoluteAddr) -> SignalRef {
2521        let layout = &self.compiled.layout;
2522        let offset = layout.offsets.get(addr).copied().unwrap_or(0);
2523        let width = layout.widths.get(addr).copied().unwrap_or(0);
2524        let is_4state = layout.is_4states.get(addr).copied().unwrap_or(false);
2525        let array_layout = layout
2526            .unpacked_arrays
2527            .get(addr)
2528            .map(|array| SignalArrayLayout {
2529                element_width: array.element_width,
2530                element_count: array.element_count,
2531                element_stride: array.element_stride,
2532                plane_size: array.plane_size,
2533            });
2534        SignalRef {
2535            offset,
2536            width,
2537            is_4state,
2538            array_layout,
2539        }
2540    }
2541
2542    fn resolve_event(&self, addr: &AbsoluteAddr) -> NativeEventRef {
2543        *self
2544            .compiled
2545            .event_map
2546            .get(addr)
2547            .unwrap_or_else(|| panic!("event not found for {:?}", addr))
2548    }
2549
2550    fn resolve_event_opt(&self, addr: &AbsoluteAddr) -> Option<NativeEventRef> {
2551        self.compiled.event_map.get(addr).copied()
2552    }
2553
2554    fn resolve_eval_only_event(&self, addr: &AbsoluteAddr) -> Option<NativeEventRef> {
2555        self.compiled.eval_only_event_map.get(addr).copied()
2556    }
2557
2558    fn resolve_apply_event(&self, addr: &AbsoluteAddr) -> Option<NativeEventRef> {
2559        self.compiled.apply_event_map.get(addr).copied()
2560    }
2561
2562    fn set<T: Copy>(&mut self, signal: SignalRef, val: T) {
2563        let allocated_size = get_byte_size(signal.width);
2564        let provided_size = std::mem::size_of::<T>();
2565        let clear_mask = self.compiled.options.four_state && signal.is_4state;
2566
2567        assert!(provided_size <= allocated_size);
2568
2569        if signal.array_layout.is_some() {
2570            let value_bytes =
2571                unsafe { std::slice::from_raw_parts(&val as *const T as *const u8, provided_size) };
2572            self.write_signal_plane(signal, false, &BigUint::from_bytes_le(value_bytes));
2573            if clear_mask {
2574                self.write_signal_plane(signal, true, &BigUint::from(0u8));
2575            }
2576            return;
2577        }
2578
2579        unsafe {
2580            let base_ptr = (self.memory.as_mut_ptr() as *mut u8).add(signal.offset);
2581            if !clear_mask && allocated_size == 1 {
2582                let raw = *(&val as *const T as *const u8);
2583                let byte = if signal.width < 8 {
2584                    raw & ((1u8 << signal.width) - 1)
2585                } else {
2586                    raw
2587                };
2588                *base_ptr = byte;
2589                return;
2590            }
2591
2592            if provided_size < allocated_size {
2593                std::ptr::write_bytes(base_ptr, 0, allocated_size);
2594            }
2595            std::ptr::write_unaligned(base_ptr as *mut T, val);
2596
2597            if clear_mask {
2598                let mask_ptr = base_ptr.add(allocated_size);
2599                std::ptr::write_bytes(mask_ptr, 0, allocated_size);
2600            }
2601        }
2602    }
2603
2604    fn set_wide(&mut self, signal: SignalRef, val: BigUint) {
2605        let clear_mask = self.compiled.options.four_state && signal.is_4state;
2606        self.write_signal_plane(signal, false, &val);
2607        if clear_mask {
2608            self.write_signal_plane(signal, true, &BigUint::from(0u8));
2609        }
2610    }
2611
2612    fn set_four_state(&mut self, signal: SignalRef, val: BigUint, mask: BigUint) {
2613        let write_mask = self.compiled.options.four_state && signal.is_4state;
2614        self.write_signal_plane(signal, false, &val);
2615        if write_mask {
2616            self.write_signal_plane(signal, true, &mask);
2617        }
2618    }
2619
2620    fn get(&self, signal: SignalRef) -> BigUint {
2621        self.read_signal_plane(signal, false)
2622    }
2623
2624    fn get_as<T: Default + Copy>(&self, signal: SignalRef) -> T {
2625        let bs = get_byte_size(signal.width);
2626        let provided_size = std::mem::size_of::<T>();
2627        if signal.array_layout.is_some() {
2628            let mut val = T::default();
2629            let value = self.read_signal_plane(signal, false).to_bytes_le();
2630            let val_bytes = unsafe {
2631                std::slice::from_raw_parts_mut(&mut val as *mut T as *mut u8, provided_size)
2632            };
2633            let copy_len = value.len().min(val_bytes.len());
2634            val_bytes[..copy_len].copy_from_slice(&value[..copy_len]);
2635            return val;
2636        }
2637        let ptr = unsafe { (self.memory.as_ptr() as *const u8).add(signal.offset) };
2638        if provided_size <= bs {
2639            return unsafe { std::ptr::read_unaligned(ptr as *const T) };
2640        }
2641
2642        let bytes = self.mem_bytes();
2643        let mut val = T::default();
2644        let val_bytes =
2645            unsafe { std::slice::from_raw_parts_mut(&mut val as *mut T as *mut u8, provided_size) };
2646        let copy_len = val_bytes.len().min(bs);
2647        val_bytes[..copy_len].copy_from_slice(&bytes[signal.offset..signal.offset + copy_len]);
2648        val
2649    }
2650
2651    fn get_four_state(&self, signal: SignalRef) -> (BigUint, BigUint) {
2652        let val = self.read_signal_plane(signal, false);
2653        let mask = if self.compiled.options.four_state && signal.is_4state {
2654            self.read_signal_plane(signal, true)
2655        } else {
2656            BigUint::from(0u32)
2657        };
2658        (val, mask)
2659    }
2660
2661    fn memory_as_ptr(&self) -> (*const u8, usize) {
2662        (self.mem_ptr(), self.memory.len_words() * 8)
2663    }
2664
2665    fn memory_as_mut_ptr(&mut self) -> (*mut u8, usize) {
2666        (self.mem_mut_ptr(), self.memory.len_words() * 8)
2667    }
2668
2669    fn memory_owner(&self) -> Option<Arc<dyn std::any::Any + Send + Sync>> {
2670        Some(self.memory.owner())
2671    }
2672
2673    fn runtime_event_buffer_as_ptr(&self) -> (*const u8, usize) {
2674        (
2675            self.runtime_event_buffer.as_ptr(),
2676            self.runtime_event_buffer.byte_size(),
2677        )
2678    }
2679
2680    fn runtime_event_buffer(&self) -> Option<Arc<RuntimeEventBuffer>> {
2681        Some(Arc::clone(&self.runtime_event_buffer))
2682    }
2683
2684    fn set_comb_capture_event_enabled(&mut self, active_sites: &[bool]) {
2685        self.comb_capture_enabled.fill(0);
2686        for (idx, active) in active_sites.iter().copied().enumerate() {
2687            if active && idx < self.comb_capture_enabled.len() {
2688                self.comb_capture_enabled[idx] = 1;
2689            }
2690        }
2691    }
2692
2693    fn stable_region_size(&self) -> usize {
2694        self.compiled.layout.total_size
2695    }
2696
2697    fn layout(&self) -> &MemoryLayout {
2698        &self.compiled.layout
2699    }
2700
2701    fn id_to_addr_slice(&self) -> &[AbsoluteAddr] {
2702        &self.compiled.id_to_addr
2703    }
2704
2705    fn id_to_event_slice(&self) -> &[NativeEventRef] {
2706        &self.compiled.id_to_event
2707    }
2708
2709    fn num_events(&self) -> usize {
2710        self.compiled.id_to_event.len()
2711    }
2712
2713    fn clear_triggered_bits(&mut self) {
2714        let offset = self.compiled.layout.triggered_bits_offset;
2715        let size = self.compiled.layout.triggered_bits_total_size;
2716        let bytes = self.mem_bytes_mut();
2717        bytes[offset..offset + size].fill(0);
2718    }
2719
2720    fn mark_triggered_bit(&mut self, id: usize) {
2721        let offset = self.compiled.layout.triggered_bits_offset;
2722        let byte_idx = offset + id / 8;
2723        let bit_idx = id % 8;
2724        self.mem_bytes_mut()[byte_idx] |= 1 << bit_idx;
2725    }
2726
2727    fn get_triggered_bits(&self) -> BitSet {
2728        let offset = self.compiled.layout.triggered_bits_offset;
2729        let size = self.compiled.layout.triggered_bits_total_size;
2730        let bytes = self.mem_bytes();
2731        let mut bs = BitSet::with_capacity(size * 8);
2732        for i in 0..size * 8 {
2733            let byte_idx = offset + i / 8;
2734            let bit_idx = i % 8;
2735            if bytes[byte_idx] & (1 << bit_idx) != 0 {
2736                bs.insert(i);
2737            }
2738        }
2739        bs
2740    }
2741}