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