1use 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#[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#[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#[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
155pub struct SharedNativeCode {
163 comb_func: NativeSimFunc,
164 comb_unit_funcs: Vec<NativeSimFunc>,
165 _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 pub(crate) native_memory_size: usize,
180 options: NativeRuntimeOptions,
181 four_state_inits: Vec<(usize, usize)>,
183}
184
185unsafe impl Send for SharedNativeCode {}
187unsafe impl Sync for SharedNativeCode {}
188
189impl SharedNativeCode {
190 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 pub fn layout(&self) -> &MemoryLayout {
274 &self.layout
275 }
276
277 pub fn code_image(&self) -> &[u8] {
282 self.program_image.code_image()
283 }
284
285 pub fn code_entries(&self) -> &[NativeCodeEntry] {
287 self.program_image.code_entries()
288 }
289
290 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#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
302pub struct NativeCodeEntry {
303 pub name: String,
305 pub offset: usize,
307 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#[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 pub fn try_map_paths<E>(
374 &mut self,
375 mut map: impl FnMut(&std::path::Path) -> Result<std::path::PathBuf, E>,
376 ) -> Result<(), E> {
377 if let Some(testbench) = &mut self.testbench {
378 testbench.try_map_paths(&mut map)?;
379 }
380 Ok(())
381 }
382
383 pub fn code_image(&self) -> &[u8] {
385 &self.code
386 }
387
388 pub fn code_entries(&self) -> &[NativeCodeEntry] {
390 &self.code_entries
391 }
392
393 pub fn layout(&self) -> &MemoryLayout {
395 &self.layout
396 }
397
398 pub fn reflection(&self) -> &DesignReflection {
400 &self.reflection
401 }
402
403 pub(crate) fn runtime_schema(&self) -> &NativeRuntimeSchema {
405 &self.runtime_schema
406 }
407
408 pub(crate) fn four_state(&self) -> bool {
410 self.options.four_state
411 }
412
413 pub(crate) fn event_topology(&self) -> &celox_design::EventTopology<AbsoluteAddr> {
415 &self.design.events
416 }
417
418 pub(crate) fn runtime_program(&self) -> crate::ir::RuntimeProgram {
422 crate::ir::RuntimeProgram {
423 design: self.design.clone(),
424 runtime_schema: RuntimeSchema {
425 runtime_errors: self.runtime_schema.runtime_errors.clone(),
426 runtime_event_sites: self.runtime_schema.runtime_event_sites.clone(),
427 comb_observers: self.runtime_schema.comb_observers.clone(),
428 testbench_read_roots: self.runtime_schema.testbench_read_roots.clone(),
429 rtl_writes: self.runtime_schema.rtl_writes.clone(),
430 },
431 testbench: self.testbench.clone(),
432 }
433 }
434
435 pub(super) fn validate(&self) -> Result<(), String> {
436 self.design
437 .validate()
438 .map_err(|error| format!("invalid runtime design: {error}"))?;
439 self.reflection
440 .validate()
441 .map_err(|error| format!("invalid design reflection: {error}"))?;
442 if self.code.is_empty() {
443 return Err("code image is empty".into());
444 }
445 let mut entry_offsets = HashSet::default();
446 let mut previous_end = 0usize;
447 for entry in &self.code_entries {
448 if !entry.offset.is_multiple_of(NATIVE_CODE_ENTRY_ALIGNMENT) {
449 return Err(format!("entry `{}` is not aligned", entry.name));
450 }
451 if entry.size == 0 {
452 return Err(format!("entry `{}` is empty", entry.name));
453 }
454 let end = entry
455 .offset
456 .checked_add(entry.size)
457 .ok_or_else(|| format!("entry `{}` range overflows", entry.name))?;
458 if entry.offset < previous_end || end > self.code.len() {
459 return Err(format!("entry `{}` is outside the code image", entry.name));
460 }
461 if !entry_offsets.insert(entry.offset) {
462 return Err(format!("entry `{}` duplicates an offset", entry.name));
463 }
464 previous_end = end;
465 }
466 if !entry_offsets.contains(&self.comb_offset) {
467 return Err("eval_comb offset does not name an image entry".into());
468 }
469 if self
470 .comb_unit_offsets
471 .iter()
472 .any(|offset| !entry_offsets.contains(offset))
473 {
474 return Err("a combinational unit offset does not name an image entry".into());
475 }
476 if self.required_native_features & !KNOWN_NATIVE_FEATURES != 0 {
477 return Err("native image contains unknown feature requirements".into());
478 }
479 for symbol in &self.symbols {
480 let end = symbol
481 .offset
482 .checked_add(symbol.size)
483 .ok_or_else(|| format!("symbol `{}` range overflows", symbol.name))?;
484 if symbol.size == 0 || end > self.code.len() {
485 return Err(format!(
486 "symbol `{}` is outside the code image",
487 symbol.name
488 ));
489 }
490 }
491 for event in self
492 .event_map
493 .values()
494 .chain(self.eval_only_event_map.values())
495 .chain(self.apply_event_map.values())
496 .chain(self.id_to_event.iter())
497 {
498 if !entry_offsets.contains(&event.func_offset)
499 || !entry_offsets.contains(&event.comb_apply_offset)
500 {
501 return Err(format!(
502 "event {} references a missing image entry",
503 event.id
504 ));
505 }
506 }
507 let semantic_size = self
508 .layout
509 .merged_total_size
510 .checked_add(self.layout.triggered_bits_total_size)
511 .ok_or_else(|| "semantic memory size overflows".to_string())?;
512 if let Some(trace) = &self.layout.trace {
513 let metadata_start = self
514 .layout
515 .triggered_bits_offset
516 .checked_add(self.layout.triggered_bits_total_size)
517 .ok_or_else(|| "trace metadata offset overflows".to_string())?;
518 if !trace.validate(
519 self.layout.total_size,
520 metadata_start,
521 self.layout.scratch_base_offset,
522 ) {
523 return Err("invalid waveform activity layout".into());
524 }
525 }
526 if self.native_memory_size < semantic_size {
527 return Err("native memory is smaller than the semantic state".into());
528 }
529 for &(offset, size) in &self.four_state_inits {
530 let end = size
531 .checked_mul(2)
532 .and_then(|size| offset.checked_add(size))
533 .ok_or_else(|| "four-state initialization range overflows".to_string())?;
534 if end > self.native_memory_size {
535 return Err("four-state initialization exceeds native memory".into());
536 }
537 }
538 Ok(())
539 }
540}
541
542fn codegen_err(error: CodegenError) -> SimulatorError {
547 error.into()
548}
549
550fn codegen_message(message: impl Into<String>) -> SimulatorError {
551 codegen_err(CodegenError::message(message))
552}
553
554struct CompiledNativeFunction {
555 code: Vec<u8>,
556 symbols: Vec<jit_mem::JitSymbol>,
557 trace: Option<emit::NativeFunctionTrace>,
558 required_state_size: usize,
559 required_native_features: u8,
560}
561
562#[cfg_attr(
563 any(
564 all(feature = "arm64-codegen", not(target_arch = "aarch64")),
565 all(feature = "x86_64-codegen", not(target_arch = "x86_64"))
566 ),
567 allow(dead_code)
568)]
569pub(crate) struct NativeCodegenTrace {
570 pub optimized_sir: String,
571 pub mir: String,
572 pub reactive_graph: String,
573 pub state_layout: String,
574}
575
576fn prepare_merged_sir(
577 units: &[&crate::ir::ExecutionUnit<crate::ir::RegionedAbsoluteAddr>],
578 layout: &MemoryLayout,
579 four_state: bool,
580 label: &str,
581 first_ff_unit: Option<usize>,
582 diagnostics: &crate::optimizer::SirDiagnostics,
583 cancel: Option<&CompileCancel>,
584 baseline: bool,
585) -> Result<crate::ir::ExecutionUnit<crate::ir::RegionedAbsoluteAddr>, SimulatorError> {
586 let verify_enabled = cfg!(debug_assertions) || diagnostics.verify_boundaries;
587 let mut phase_start = diagnostics.pass_timing.then(crate::timing::now);
588 let mut report_phase = |phase: &str| {
589 if let Some(start) = phase_start {
590 tracing::debug!(
591 "[native-sir-timing] label={label} phase={phase} elapsed={:?}",
592 start.elapsed()
593 );
594 phase_start = Some(crate::timing::now());
595 }
596 };
597 if verify_enabled {
598 for (unit_index, unit) in units.iter().enumerate() {
599 if let Err(error) = unit.verify_result() {
600 return Err(codegen_err(CodegenError::SirVerification {
601 phase: format!(
602 "invalid SIR before x86 source-unit merge: {label} source unit {unit_index}"
603 ),
604 source: error,
605 }));
606 }
607 }
608 }
609
610 let (mut sir_eu, merge_provenance) = celox_sir::merge_sir_eu_refs_with_provenance(units);
611 let boundaries = merge_provenance.unit_entries[1..].to_vec();
612 let verify = |eu: &crate::ir::ExecutionUnit<crate::ir::RegionedAbsoluteAddr>,
613 phase: &'static str| {
614 if verify_enabled {
615 eu.verify_result().map_err(|source| {
616 codegen_err(CodegenError::SirVerification {
617 phase: phase.to_string(),
618 source,
619 })
620 })
621 } else {
622 Ok(())
623 }
624 };
625
626 verify(&sir_eu, "before x86 merged-SIR optimization")?;
627 report_phase("merge");
628 if cancelled(cancel) {
629 return Err(cancelled_error());
630 }
631 if baseline {
635 return Ok(sir_eu);
636 }
637 if let Some(first_ff_unit) = first_ff_unit {
638 let removed = crate::optimizer::sir::eliminate_unobserved_comb_state_stores(
639 &mut sir_eu,
640 &merge_provenance,
641 first_ff_unit,
642 )
643 .map_err(|source| {
644 codegen_err(CodegenError::Optimization {
645 context: "comb/FF state-publication DSE",
646 source,
647 })
648 })?;
649 if removed != 0 {
650 crate::optimizer::sir::remove_dead_sir_definitions(&mut sir_eu);
651 verify(&sir_eu, "after comb/FF state-publication DSE")?;
652 }
653 }
654 report_phase("comb_state_dse");
655 if label == "eval_comb_apply_ff"
656 && crate::optimizer::sir::promote_fused_comb_static_slots(&mut sir_eu).map_err(
657 |source| {
658 codegen_err(CodegenError::Optimization {
659 context: "final fused comb StateSSA promotion",
660 source,
661 })
662 },
663 )?
664 {
665 crate::optimizer::sir::remove_dead_sir_definitions(&mut sir_eu);
666 verify(&sir_eu, "after final fused comb StateSSA promotion")?;
667 }
668 report_phase("comb_state_ssa");
669 crate::optimizer::sir::pass_eliminate_working_round_trip::eliminate_working_round_trip(
670 &mut sir_eu,
671 &boundaries,
672 );
673 verify(&sir_eu, "after x86 direct working rewrite")?;
674 if cancelled(cancel) {
675 return Err(cancelled_error());
676 }
677 let promoted_working =
678 crate::optimizer::sir::promote_eval_apply_working_round_trips(&mut sir_eu);
679 if promoted_working {
680 verify(&sir_eu, "after x86 working StateSSA")?;
681 crate::optimizer::sir::remove_dead_sir_definitions(&mut sir_eu);
682 verify(&sir_eu, "after x86 working StateSSA DCE")?;
683 }
684 report_phase("working_state_ssa");
685 if cancelled(cancel) {
686 return Err(cancelled_error());
687 }
688 crate::optimizer::sir::optimize_native_merged_chain(
689 &mut sir_eu,
690 layout,
691 four_state,
692 label == "eval_comb_apply_ff",
693 diagnostics,
694 || cancelled(cancel),
695 )
696 .map_err(|source| {
697 if source.kind() == celox_sir_opt::OptimizationErrorKind::Cancelled {
698 codegen_err(CodegenError::Cancelled)
699 } else {
700 codegen_err(CodegenError::Optimization {
701 context: "native merged-chain optimization",
702 source,
703 })
704 }
705 })?;
706 verify(&sir_eu, "after x86 merged-chain cleanup")?;
707 report_phase("merged_chain");
708 Ok(sir_eu)
709}
710
711fn compile_units(
712 units: &[crate::ir::ExecutionUnit<crate::ir::RegionedAbsoluteAddr>],
713 layout: &MemoryLayout,
714 four_state: bool,
715 label: &str,
716 x86_options: &crate::backend::X86BackendOptions,
717 capture_trace: bool,
718 diagnostics: &crate::optimizer::SirDiagnostics,
719 cancel: Option<&CompileCancel>,
720) -> Result<CompiledNativeFunction, SimulatorError> {
721 let units = units.iter().collect::<Vec<_>>();
722 compile_unit_refs(
723 &units,
724 layout,
725 four_state,
726 label,
727 None,
728 x86_options,
729 capture_trace,
730 diagnostics,
731 cancel,
732 )
733}
734
735fn compile_unit_refs(
736 units: &[&crate::ir::ExecutionUnit<crate::ir::RegionedAbsoluteAddr>],
737 layout: &MemoryLayout,
738 four_state: bool,
739 label: &str,
740 first_ff_unit: Option<usize>,
741 x86_options: &crate::backend::X86BackendOptions,
742 capture_trace: bool,
743 diagnostics: &crate::optimizer::SirDiagnostics,
744 cancel: Option<&CompileCancel>,
745) -> Result<CompiledNativeFunction, SimulatorError> {
746 if cancelled(cancel) {
747 return Err(cancelled_error());
748 }
749 let timing = x86_options.diagnostics.phase_timing;
750 if units.is_empty() {
751 #[cfg(any(
753 feature = "x86_64-codegen",
754 all(target_arch = "x86_64", not(feature = "arm64-codegen"))
755 ))]
756 let (empty_result, empty_mir) = {
757 let mut empty_func =
758 super::mir::MFunction::new(super::mir::VRegAllocator::new(), vec![]);
759 let mut block = super::mir::MBlock::new(super::mir::BlockId(0));
760 block.push(super::mir::MInst::Return);
761 empty_func.push_block(block);
762 let empty_result = emit::emit(&empty_func, ®alloc::AssignmentMap::default(), 0)
763 .map_err(|source| codegen_err(CodegenError::NativeEmission { source }))?;
764 let empty_mir = empty_func.to_string();
765 (empty_result, empty_mir)
766 };
767 #[cfg(any(
768 feature = "arm64-codegen",
769 all(target_arch = "aarch64", not(feature = "x86_64-codegen"))
770 ))]
771 let (empty_result, empty_mir) = {
772 let state_size = layout
773 .merged_total_size
774 .checked_add(layout.triggered_bits_total_size)
775 .ok_or_else(|| {
776 codegen_err(CodegenError::NativeEmission {
777 source: emit::EmitError::Range("AArch64 simulation-state size overflow"),
778 })
779 })?;
780 let result = emit::emit_empty(state_size)
781 .map_err(|source| codegen_err(CodegenError::NativeEmission { source }))?;
782 (result, "bb0:\n Return\n".to_string())
783 };
784 let trace = capture_trace.then(|| emit::NativeFunctionTrace {
785 optimized_sir: "<empty native function>\n".into(),
786 reactive_graph: String::new(),
787 state_layout: String::new(),
788 mir_before_regalloc: empty_mir.clone(),
789 mir_after_late_memory_folds: empty_mir.clone(),
790 mir_after_scheduling: empty_mir.clone(),
791 mir_after_regalloc: empty_mir,
792 register_assignment: String::new(),
793 spill_frame_size: 0,
794 disassembly: emit::disassemble(&empty_result.code[..empty_result.text_size], 0),
795 });
796 let symbols = perf_symbols_for_emit_result(label, &empty_result);
797 return Ok(CompiledNativeFunction {
798 code: empty_result.code,
799 symbols,
800 trace,
801 required_state_size: empty_result.required_state_size as usize,
802 #[cfg(any(
803 feature = "x86_64-codegen",
804 all(target_arch = "x86_64", not(feature = "arm64-codegen"))
805 ))]
806 required_native_features: empty_result.required_image_features,
807 #[cfg(any(
808 feature = "arm64-codegen",
809 all(target_arch = "aarch64", not(feature = "x86_64-codegen"))
810 ))]
811 required_native_features: 0,
812 });
813 }
814
815 if timing {
817 tracing::debug!(
818 "[native-timing] compile_units start label={label} eus={}",
819 units.len()
820 );
821 }
822 let start = timing.then(crate::timing::now);
823 let sir_eu = prepare_merged_sir(
824 units,
825 layout,
826 four_state,
827 label,
828 first_ff_unit,
829 diagnostics,
830 cancel,
831 x86_options.baseline,
832 )?;
833 if cancelled(cancel) {
834 return Err(cancelled_error());
835 }
836 let mut trace = capture_trace.then(emit::NativeFunctionTrace::default);
837 #[cfg(any(
838 feature = "x86_64-codegen",
839 all(target_arch = "x86_64", not(feature = "arm64-codegen"))
840 ))]
841 let emit_result = emit::emit_owned_prepared_eu(
842 sir_eu,
843 layout,
844 four_state,
845 label,
846 x86_options,
847 trace.as_mut(),
848 || cancelled(cancel),
849 )
850 .map_err(|source| {
851 if matches!(source, emit::ChainedEmitError::Cancelled) {
852 codegen_err(CodegenError::Cancelled)
853 } else {
854 codegen_err(CodegenError::NativePipeline { source })
855 }
856 })?;
857 #[cfg(any(
858 feature = "arm64-codegen",
859 all(target_arch = "aarch64", not(feature = "x86_64-codegen"))
860 ))]
861 let emit_result = emit::emit_prepared_eu(
862 &sir_eu,
863 layout,
864 four_state,
865 label,
866 x86_options.native_tick_loop,
867 trace.as_mut(),
868 || cancelled(cancel),
869 )
870 .map_err(|source| {
871 if matches!(source, emit::ChainedEmitError::Cancelled) {
872 codegen_err(CodegenError::Cancelled)
873 } else {
874 codegen_err(CodegenError::NativePipeline { source })
875 }
876 })?;
877 if let Some(start) = start {
878 tracing::debug!(
879 "[native-timing] compile_units done label={label} bytes={} elapsed={:?}",
880 emit_result.code.len(),
881 start.elapsed()
882 );
883 }
884 let symbols = perf_symbols_for_emit_result(label, &emit_result);
885 let required_state_size = emit_result.required_state_size as usize;
886 Ok(CompiledNativeFunction {
887 code: emit_result.code,
888 symbols,
889 trace,
890 required_state_size,
891 #[cfg(any(
892 feature = "x86_64-codegen",
893 all(target_arch = "x86_64", not(feature = "arm64-codegen"))
894 ))]
895 required_native_features: emit_result.required_image_features,
896 #[cfg(any(
897 feature = "arm64-codegen",
898 all(target_arch = "aarch64", not(feature = "x86_64-codegen"))
899 ))]
900 required_native_features: 0,
901 })
902}
903
904fn perf_symbols_for_emit_result(label: &str, result: &emit::EmitResult) -> Vec<jit_mem::JitSymbol> {
905 let code_len = result.text_size;
906 if result.block_offsets.is_empty() {
907 return Vec::new();
908 }
909
910 let mut blocks = result.block_offsets.clone();
911 blocks.sort_by_key(|(_, offset)| *offset);
912
913 let mut symbols = Vec::with_capacity(blocks.len() + 2);
914 let first_offset = blocks[0].1 as usize;
915 if first_offset > 0 {
916 symbols.push(jit_mem::JitSymbol {
917 offset: 0,
918 size: first_offset,
919 name: format!("{label}.prologue"),
920 });
921 }
922
923 for (idx, (block_id, offset)) in blocks.iter().enumerate() {
924 let start = *offset as usize;
925 let end = blocks
926 .get(idx + 1)
927 .map(|(_, next)| *next as usize)
928 .unwrap_or(code_len);
929 if end > start {
930 symbols.push(jit_mem::JitSymbol {
931 offset: start,
932 size: end - start,
933 name: format!("{label}.bb{}", block_id.0),
934 });
935 }
936 }
937
938 symbols
939}
940
941const NATIVE_CODE_ENTRY_ALIGNMENT: usize = 16;
942
943fn append_native_code(
944 image: &mut Vec<u8>,
945 entries: &mut Vec<NativeCodeEntry>,
946 image_symbols: &mut Vec<NativeCodeSymbol>,
947 name: String,
948 compiled: &CompiledNativeFunction,
949) -> Result<usize, SimulatorError> {
950 let offset = image
951 .len()
952 .checked_add(NATIVE_CODE_ENTRY_ALIGNMENT - 1)
953 .map(|value| value & !(NATIVE_CODE_ENTRY_ALIGNMENT - 1))
954 .ok_or_else(|| codegen_message("packed native code image alignment overflow"))?;
955 image.resize(offset, 0);
956 let end = offset
957 .checked_add(compiled.code.len())
958 .ok_or_else(|| codegen_message("packed native code image size overflow"))?;
959 image.extend_from_slice(&compiled.code);
960
961 if compiled.symbols.is_empty() {
962 image_symbols.push(NativeCodeSymbol {
963 offset,
964 size: compiled.code.len(),
965 name: name.clone(),
966 });
967 } else {
968 for symbol in &compiled.symbols {
969 let symbol_end = symbol
970 .offset
971 .checked_add(symbol.size)
972 .ok_or_else(|| codegen_message("native function symbol range overflow"))?;
973 if symbol_end > compiled.code.len() {
974 return Err(codegen_message(format!(
975 "native function symbol `{}` exceeds its emitted code",
976 symbol.name
977 )));
978 }
979 image_symbols.push(NativeCodeSymbol {
980 offset: offset + symbol.offset,
981 size: symbol.size,
982 name: format!("{name}.{}", symbol.name),
983 });
984 }
985 }
986
987 entries.push(NativeCodeEntry {
988 name,
989 offset,
990 size: end - offset,
991 });
992 Ok(offset)
993}
994
995fn native_function_at(
996 image: &jit_mem::JitCode,
997 offset: usize,
998) -> Result<NativeSimFunc, SimulatorError> {
999 let ptr = image
1000 .entry_ptr(offset)
1001 .ok_or_else(|| codegen_message("native function entry exceeds packed code image"))?;
1002 Ok(unsafe { std::mem::transmute::<*const u8, NativeSimFunc>(ptr) })
1006}
1007
1008struct NativeCompileTask<'a> {
1009 units: Vec<&'a crate::ir::ExecutionUnit<crate::ir::RegionedAbsoluteAddr>>,
1010 label: &'static str,
1011 first_ff_unit: Option<usize>,
1012 bindings: Vec<String>,
1013}
1014
1015type NativeTaskBindings = HashMap<(&'static str, AbsoluteAddr), usize>;
1016
1017fn collect_ff_compile_tasks(
1018 sir: &LaidOutProgram,
1019) -> (Vec<NativeCompileTask<'_>>, NativeTaskBindings) {
1020 let mut tasks = Vec::new();
1021 let mut task_bindings = HashMap::default();
1022 collect_ff_compile_tasks_from(
1023 sir,
1024 &sir.sir.eval_apply_ffs,
1025 "eval_apply_ff",
1026 &mut tasks,
1027 &mut task_bindings,
1028 );
1029 collect_ff_compile_tasks_from(
1030 sir,
1031 &sir.sir.eval_only_ffs,
1032 "eval_only_ff",
1033 &mut tasks,
1034 &mut task_bindings,
1035 );
1036 collect_ff_compile_tasks_from(
1037 sir,
1038 &sir.sir.apply_ffs,
1039 "apply_ff",
1040 &mut tasks,
1041 &mut task_bindings,
1042 );
1043 collect_comb_apply_compile_tasks(sir, &mut tasks, &mut task_bindings);
1044 (tasks, task_bindings)
1045}
1046
1047fn collect_comb_apply_compile_tasks<'a>(
1048 sir: &'a LaidOutProgram,
1049 tasks: &mut Vec<NativeCompileTask<'a>>,
1050 task_bindings: &mut NativeTaskBindings,
1051) {
1052 const LABEL: &str = "eval_comb_apply_ff";
1053 for (addr, ff_units) in &sir.sir.eval_apply_ffs {
1054 let fused_units = sir.sir.eval_comb_apply_ffs.get(addr);
1055 let (unit_refs, first_ff_unit) = if let Some(fused_units) = fused_units {
1056 (fused_units.iter().collect::<Vec<_>>(), None)
1057 } else {
1058 let mut unit_refs = sir.sir.eval_comb.iter().collect::<Vec<_>>();
1059 let first_ff_unit =
1060 (!unit_refs.is_empty() && !ff_units.is_empty()).then_some(unit_refs.len());
1061 unit_refs.extend(ff_units);
1062 (unit_refs, first_ff_unit)
1063 };
1064 let binding = format!("{LABEL} trigger={}", sir.get_path(addr));
1065 let index = if let Some(index) = tasks.iter().position(|task| {
1066 task.label == LABEL && task.first_ff_unit == first_ff_unit && task.units == unit_refs
1067 }) {
1068 tasks[index].bindings.push(binding);
1069 index
1070 } else {
1071 let index = tasks.len();
1072 tasks.push(NativeCompileTask {
1073 units: unit_refs,
1074 label: LABEL,
1075 first_ff_unit,
1076 bindings: vec![binding],
1077 });
1078 index
1079 };
1080 task_bindings.insert((LABEL, *addr), index);
1081 }
1082}
1083
1084fn collect_ff_compile_tasks_from<'a>(
1085 sir: &LaidOutProgram,
1086 ff_map: &'a HashMap<
1087 AbsoluteAddr,
1088 Vec<crate::ir::ExecutionUnit<crate::ir::RegionedAbsoluteAddr>>,
1089 >,
1090 label: &'static str,
1091 tasks: &mut Vec<NativeCompileTask<'a>>,
1092 task_bindings: &mut NativeTaskBindings,
1093) {
1094 for (addr, units) in ff_map {
1095 let unit_refs = units.iter().collect::<Vec<_>>();
1096 let binding = format!("{label} trigger={}", sir.get_path(addr));
1097 let index = if let Some(index) = tasks.iter().position(|task| task.units == unit_refs) {
1098 tasks[index].bindings.push(binding);
1099 index
1100 } else {
1101 let index = tasks.len();
1102 tasks.push(NativeCompileTask {
1103 units: unit_refs,
1104 label,
1105 first_ff_unit: None,
1106 bindings: vec![binding],
1107 });
1108 index
1109 };
1110 task_bindings.insert((label, *addr), index);
1111 }
1112}
1113
1114fn append_native_function_trace(
1115 optimized_sir: &mut String,
1116 mir: &mut String,
1117 reactive_graph: &mut String,
1118 state_layout: &mut String,
1119 name: &str,
1120 bindings: &[String],
1121 trace: &emit::NativeFunctionTrace,
1122) {
1123 let mut bindings = bindings.to_vec();
1124 bindings.sort();
1125 bindings.dedup();
1126
1127 optimized_sir.push_str(&format!("=== Native function {name} ===\n"));
1128 if !bindings.is_empty() {
1129 optimized_sir.push_str("Bindings:\n");
1130 for binding in &bindings {
1131 optimized_sir.push_str(&format!(" {binding}\n"));
1132 }
1133 }
1134 optimized_sir.push_str(&trace.optimized_sir);
1135 if !trace.optimized_sir.ends_with('\n') {
1136 optimized_sir.push('\n');
1137 }
1138 optimized_sir.push('\n');
1139
1140 mir.push_str(&format!("=== Native function {name} ===\n"));
1141 if !bindings.is_empty() {
1142 mir.push_str("Bindings:\n");
1143 for binding in &bindings {
1144 mir.push_str(&format!(" {binding}\n"));
1145 }
1146 }
1147 mir.push_str("--- MIR after main optimization, before regalloc-owned late folds ---\n");
1148 mir.push_str(&trace.mir_before_regalloc);
1149 if !trace.mir_before_regalloc.ends_with('\n') {
1150 mir.push('\n');
1151 }
1152 mir.push_str("--- MIR after late memory folds, before allocation-owned scheduling ---\n");
1153 mir.push_str(&trace.mir_after_late_memory_folds);
1154 if !trace.mir_after_late_memory_folds.ends_with('\n') {
1155 mir.push('\n');
1156 }
1157 mir.push_str("--- MIR after allocation-owned scheduling, before spill reconstruction ---\n");
1158 mir.push_str(&trace.mir_after_scheduling);
1159 if !trace.mir_after_scheduling.ends_with('\n') {
1160 mir.push('\n');
1161 }
1162 mir.push_str("--- MIR after register allocation and post-RA peepholes ---\n");
1163 mir.push_str(&trace.mir_after_regalloc);
1164 if !trace.mir_after_regalloc.ends_with('\n') {
1165 mir.push('\n');
1166 }
1167 mir.push_str(&format!("Spill frame: {} bytes\n", trace.spill_frame_size));
1168 mir.push_str("Register assignment:\n");
1169 mir.push_str(&trace.register_assignment);
1170 #[cfg(any(
1171 feature = "x86_64-codegen",
1172 all(target_arch = "x86_64", not(feature = "arm64-codegen"))
1173 ))]
1174 mir.push_str("x86-64 disassembly of emitted function:\n");
1175 #[cfg(any(
1176 feature = "arm64-codegen",
1177 all(target_arch = "aarch64", not(feature = "x86_64-codegen"))
1178 ))]
1179 mir.push_str("AArch64 disassembly of emitted function:\n");
1180 mir.push_str(&trace.disassembly);
1181 if !trace.disassembly.ends_with('\n') {
1182 mir.push('\n');
1183 }
1184 mir.push('\n');
1185
1186 if !trace.reactive_graph.is_empty() {
1187 reactive_graph.push_str(&format!("=== Native function {name} ===\n"));
1188 if !bindings.is_empty() {
1189 reactive_graph.push_str("Bindings:\n");
1190 for binding in &bindings {
1191 reactive_graph.push_str(&format!(" {binding}\n"));
1192 }
1193 }
1194 reactive_graph.push_str(&trace.reactive_graph);
1195 if !trace.reactive_graph.ends_with('\n') {
1196 reactive_graph.push('\n');
1197 }
1198 reactive_graph.push('\n');
1199 }
1200
1201 if !trace.state_layout.is_empty() {
1202 state_layout.push_str(&format!("=== Native function {name} ===\n"));
1203 if !bindings.is_empty() {
1204 state_layout.push_str("Bindings:\n");
1205 for binding in &bindings {
1206 state_layout.push_str(&format!(" {binding}\n"));
1207 }
1208 }
1209 state_layout.push_str(&trace.state_layout);
1210 if !trace.state_layout.ends_with('\n') {
1211 state_layout.push('\n');
1212 }
1213 state_layout.push('\n');
1214 }
1215}
1216
1217fn format_native_codegen_trace(
1218 comb: &CompiledNativeFunction,
1219 ff_codes: &HashMap<usize, CompiledNativeFunction>,
1220 tasks: &[NativeCompileTask<'_>],
1221) -> NativeCodegenTrace {
1222 let mut optimized_sir = String::from("=== Optimized SIR used by native emission ===\n");
1223 let mut mir = String::from("=== MIR used by native emission ===\n");
1224 let mut reactive_graph = String::from("=== Reactive clock-event projection oracle ===\n");
1225 let mut state_layout =
1226 String::from("=== Profile-selected native state-layout feasibility ===\n");
1227 append_native_function_trace(
1228 &mut optimized_sir,
1229 &mut mir,
1230 &mut reactive_graph,
1231 &mut state_layout,
1232 "eval_comb",
1233 &[],
1234 comb.trace
1235 .as_ref()
1236 .expect("explicit native trace must capture eval_comb"),
1237 );
1238
1239 let mut ff_entries = ff_codes
1240 .keys()
1241 .map(|&task_id| {
1242 let task = &tasks[task_id];
1243 let mut sort_key = task.bindings.clone();
1244 sort_key.sort();
1245 (sort_key, task_id, task)
1246 })
1247 .collect::<Vec<_>>();
1248 ff_entries.sort_by(|left, right| left.0.cmp(&right.0));
1249 let mut label_indices = HashMap::<&str, usize>::default();
1250 for (_, task_id, task) in ff_entries {
1251 let index = label_indices.entry(task.label).or_default();
1252 let name = format!("{}[{index}]", task.label);
1253 *index += 1;
1254 append_native_function_trace(
1255 &mut optimized_sir,
1256 &mut mir,
1257 &mut reactive_graph,
1258 &mut state_layout,
1259 &name,
1260 &task.bindings,
1261 ff_codes[&task_id]
1262 .trace
1263 .as_ref()
1264 .expect("explicit native trace must capture every FF function"),
1265 );
1266 }
1267 NativeCodegenTrace {
1268 optimized_sir,
1269 mir,
1270 reactive_graph,
1271 state_layout,
1272 }
1273}
1274
1275fn offset_registers(offset: &SIROffset, registers: &mut Vec<RegisterId>) {
1276 match offset {
1277 SIROffset::Dynamic(register) => registers.push(*register),
1278 SIROffset::Element {
1279 index,
1280 dynamic_bit_offset,
1281 ..
1282 } => {
1283 registers.push(*index);
1284 registers.extend(dynamic_bit_offset);
1285 }
1286 SIROffset::Static(_) | SIROffset::PackedElements { .. } => {}
1287 }
1288}
1289
1290fn instruction_registers<A>(instruction: &SIRInstruction<A>) -> Vec<RegisterId> {
1291 let mut registers = Vec::new();
1292 match instruction {
1293 SIRInstruction::Imm(..) => {}
1294 SIRInstruction::Binary(_, lhs, _, rhs) => registers.extend([*lhs, *rhs]),
1295 SIRInstruction::Unary(_, _, source) | SIRInstruction::Slice(_, source, _, _) => {
1296 registers.push(*source);
1297 }
1298 SIRInstruction::Load(_, _, offset, _) => offset_registers(offset, &mut registers),
1299 SIRInstruction::Store(_, offset, _, source, _, _) => {
1300 registers.push(*source);
1301 offset_registers(offset, &mut registers);
1302 }
1303 SIRInstruction::Commit(..) => {}
1304 SIRInstruction::Concat(_, sources) => registers.extend(sources),
1305 SIRInstruction::Mux(_, condition, then_value, else_value) => {
1306 registers.extend([*condition, *then_value, *else_value]);
1307 }
1308 SIRInstruction::RuntimeEvent { args, .. }
1309 | SIRInstruction::CombCaptureEvent { args, .. } => registers.extend(args),
1310 SIRInstruction::CombCaptureEnableIfChanged { old, new, .. } => {
1311 registers.extend([*old, *new]);
1312 }
1313 }
1314 registers
1315}
1316
1317fn comb_block_execution_order<A>(unit: &ExecutionUnit<A>) -> Vec<BlockId> {
1318 fn visit<A>(
1319 unit: &ExecutionUnit<A>,
1320 block_id: BlockId,
1321 visited: &mut HashSet<BlockId>,
1322 postorder: &mut Vec<BlockId>,
1323 ) {
1324 if !visited.insert(block_id) {
1325 return;
1326 }
1327 for successor in celox_sir::cfg::terminator_successors(&unit.blocks[&block_id].terminator) {
1328 visit(unit, successor, visited, postorder);
1329 }
1330 postorder.push(block_id);
1331 }
1332
1333 let mut visited = HashSet::default();
1334 let mut postorder = Vec::with_capacity(unit.blocks.len());
1335 visit(unit, unit.entry_block_id, &mut visited, &mut postorder);
1336 postorder.reverse();
1337 postorder
1338}
1339
1340fn is_comb_runtime_effect(instruction: &SIRInstruction<RegionedAbsoluteAddr>) -> bool {
1341 matches!(
1342 instruction,
1343 SIRInstruction::RuntimeEvent { .. }
1344 | SIRInstruction::CombCaptureEvent { .. }
1345 | SIRInstruction::CombCaptureEnableIfChanged { .. }
1346 )
1347}
1348
1349fn interleave_comb_runtime_effects(
1350 unit: &ExecutionUnit<RegionedAbsoluteAddr>,
1351 ordered_stores: &[(BlockId, usize)],
1352 store_units: Vec<ExecutionUnit<RegionedAbsoluteAddr>>,
1353) -> Vec<ExecutionUnit<RegionedAbsoluteAddr>> {
1354 let ordered_sites = comb_block_execution_order(unit)
1355 .into_iter()
1356 .flat_map(|block_id| {
1357 (0..unit.blocks[&block_id].instructions.len()).map(move |index| (block_id, index))
1358 })
1359 .collect::<Vec<_>>();
1360 let positions = ordered_sites
1361 .iter()
1362 .enumerate()
1363 .map(|(position, &site)| (site, position))
1364 .collect::<HashMap<_, _>>();
1365 let mut effect_groups = vec![Vec::new(); ordered_stores.len() + 1];
1366 for site in ordered_sites {
1367 if !is_comb_runtime_effect(&unit.blocks[&site.0].instructions[site.1]) {
1368 continue;
1369 }
1370 let boundary = ordered_stores
1371 .iter()
1372 .filter(|store| positions[store] < positions[&site])
1373 .count();
1374 effect_groups[boundary].push(site);
1375 }
1376 if effect_groups.iter().all(Vec::is_empty) {
1377 return store_units;
1378 }
1379
1380 let mut result = Vec::with_capacity(store_units.len() + effect_groups.len());
1381 let mut stores = store_units.into_iter();
1382 for (boundary, group) in effect_groups.into_iter().enumerate() {
1383 if !group.is_empty() {
1384 let group = group.into_iter().collect::<HashSet<_>>();
1385 let mut events = unit.clone();
1386 for (block_id, block) in &mut events.blocks {
1387 block.instructions = std::mem::take(&mut block.instructions)
1388 .into_iter()
1389 .enumerate()
1390 .filter_map(|(index, instruction)| {
1391 let site = (*block_id, index);
1392 if matches!(
1393 instruction,
1394 SIRInstruction::Store(..) | SIRInstruction::Commit(..)
1395 ) {
1396 None
1397 } else if is_comb_runtime_effect(&instruction) {
1398 group.contains(&site).then_some(instruction)
1399 } else {
1400 Some(instruction)
1401 }
1402 })
1403 .collect();
1404 }
1405 result.push(events);
1406 }
1407 if boundary < ordered_stores.len() {
1408 result.push(stores.next().unwrap());
1409 }
1410 }
1411 result
1412}
1413
1414fn split_comb_execution_unit(
1415 unit: &ExecutionUnit<RegionedAbsoluteAddr>,
1416) -> Vec<ExecutionUnit<RegionedAbsoluteAddr>> {
1417 if unit.blocks.len() != 1 {
1418 let definitions = unit
1419 .blocks
1420 .iter()
1421 .flat_map(|(block_id, block)| {
1422 block
1423 .instructions
1424 .iter()
1425 .enumerate()
1426 .filter_map(|(index, instruction)| {
1427 instruction
1428 .defined_register()
1429 .map(|register| (register, (*block_id, index)))
1430 })
1431 })
1432 .collect::<HashMap<_, _>>();
1433 let store_sites = comb_block_execution_order(unit)
1434 .into_iter()
1435 .flat_map(|block_id| {
1436 let block = &unit.blocks[&block_id];
1437 block
1438 .instructions
1439 .iter()
1440 .enumerate()
1441 .filter(|(_, instruction)| {
1442 matches!(
1443 instruction,
1444 SIRInstruction::Store(..) | SIRInstruction::Commit(..)
1445 )
1446 })
1447 .map(move |(index, _)| (block_id, index))
1448 })
1449 .collect::<Vec<_>>();
1450 if store_sites.is_empty() {
1451 return vec![unit.clone()];
1452 }
1453
1454 let instruction_at = |site: (BlockId, usize)| &unit.blocks[&site.0].instructions[site.1];
1455 let register_dependencies = store_sites
1456 .iter()
1457 .copied()
1458 .map(|store_site| {
1459 let mut dependencies = HashSet::default();
1460 let mut pending = instruction_registers(instruction_at(store_site));
1461 while let Some(register) = pending.pop() {
1462 if !dependencies.insert(register) {
1463 continue;
1464 }
1465 if let Some(&definition) = definitions.get(®ister) {
1466 pending.extend(instruction_registers(instruction_at(definition)));
1467 }
1468 }
1469 (store_site, dependencies)
1470 })
1471 .collect::<HashMap<_, _>>();
1472 let store_source = |site| match instruction_at(site) {
1473 SIRInstruction::Store(_, _, _, source, _, _) => Some(*source),
1474 _ => None,
1475 };
1476 let mut remaining = store_sites;
1477 let mut ordered_stores = Vec::with_capacity(remaining.len());
1478 while !remaining.is_empty() {
1479 let next = remaining
1480 .iter()
1481 .position(|candidate| {
1482 let candidate_source = store_source(*candidate);
1483 !remaining.iter().any(|predecessor| {
1484 if predecessor == candidate {
1485 return false;
1486 }
1487 store_source(*predecessor).is_some_and(|source| {
1488 Some(source) != candidate_source
1489 && register_dependencies[candidate].contains(&source)
1490 })
1491 })
1492 })
1493 .unwrap_or(0);
1494 ordered_stores.push(remaining.remove(next));
1495 }
1496
1497 let split = ordered_stores
1498 .iter()
1499 .enumerate()
1500 .map(|(order, &target)| {
1501 let reloads = ordered_stores[..order]
1502 .iter()
1503 .filter_map(|&prior_site| {
1504 let SIRInstruction::Store(address, offset, bits, source, _, _) =
1505 instruction_at(prior_site)
1506 else {
1507 return None;
1508 };
1509 (register_dependencies[&target].contains(source)
1510 && store_source(target) != Some(*source)
1511 && unit.register_map[source].width() == *bits)
1512 .then(|| (*source, (*address, offset.clone(), *bits)))
1513 })
1514 .collect::<HashMap<_, _>>();
1515 let mut extracted = unit.clone();
1516 for (block_id, block) in &mut extracted.blocks {
1517 block.instructions = std::mem::take(&mut block.instructions)
1518 .into_iter()
1519 .enumerate()
1520 .filter_map(|(index, instruction)| {
1521 let site = (*block_id, index);
1522 match instruction {
1523 SIRInstruction::Store(..) | SIRInstruction::Commit(..) => {
1524 (site == target).then_some(instruction)
1525 }
1526 SIRInstruction::RuntimeEvent { .. }
1527 | SIRInstruction::CombCaptureEvent { .. }
1528 | SIRInstruction::CombCaptureEnableIfChanged { .. } => None,
1529 _ => {
1530 if let Some(register) = instruction.defined_register()
1531 && let Some((address, offset, bits)) =
1532 reloads.get(®ister)
1533 {
1534 Some(SIRInstruction::Load(
1535 register,
1536 *address,
1537 offset.clone(),
1538 *bits,
1539 ))
1540 } else {
1541 Some(instruction)
1542 }
1543 }
1544 }
1545 })
1546 .collect();
1547 }
1548 extracted
1549 })
1550 .collect::<Vec<_>>();
1551
1552 return interleave_comb_runtime_effects(unit, &ordered_stores, split);
1553 }
1554 let block = &unit.blocks[&unit.entry_block_id];
1555 if !block.params.is_empty() || block.terminator != SIRTerminator::Return {
1556 return vec![unit.clone()];
1557 }
1558
1559 let definitions = block
1560 .instructions
1561 .iter()
1562 .enumerate()
1563 .filter_map(|(index, instruction)| {
1564 instruction
1565 .defined_register()
1566 .map(|register| (register, index))
1567 })
1568 .collect::<HashMap<_, _>>();
1569 let store_indices = block
1570 .instructions
1571 .iter()
1572 .enumerate()
1573 .filter_map(|(index, instruction)| {
1574 matches!(
1575 instruction,
1576 SIRInstruction::Store(..) | SIRInstruction::Commit(..)
1577 )
1578 .then_some(index)
1579 })
1580 .collect::<Vec<_>>();
1581 if store_indices.is_empty() {
1582 return vec![unit.clone()];
1583 }
1584
1585 let register_dependencies = store_indices
1586 .iter()
1587 .copied()
1588 .map(|store_index| {
1589 let mut dependencies = HashSet::default();
1590 let mut pending = instruction_registers(&block.instructions[store_index]);
1591 while let Some(register) = pending.pop() {
1592 if !dependencies.insert(register) {
1593 continue;
1594 }
1595 if let Some(&definition) = definitions.get(®ister) {
1596 pending.extend(instruction_registers(&block.instructions[definition]));
1597 }
1598 }
1599 (store_index, dependencies)
1600 })
1601 .collect::<HashMap<_, _>>();
1602 let store_source = |index| match &block.instructions[index] {
1603 SIRInstruction::Store(_, _, _, source, _, _) => Some(*source),
1604 _ => None,
1605 };
1606 let mut remaining = store_indices.clone();
1607 let mut ordered_stores = Vec::with_capacity(remaining.len());
1608 while !remaining.is_empty() {
1609 let next = remaining
1610 .iter()
1611 .position(|candidate| {
1612 let candidate_source = store_source(*candidate);
1613 !remaining.iter().any(|predecessor| {
1614 if predecessor == candidate {
1615 return false;
1616 }
1617 store_source(*predecessor).is_some_and(|source| {
1618 Some(source) != candidate_source
1619 && register_dependencies[candidate].contains(&source)
1620 })
1621 })
1622 })
1623 .unwrap_or(0);
1624 ordered_stores.push(remaining.remove(next));
1625 }
1626
1627 let split = ordered_stores
1628 .iter()
1629 .enumerate()
1630 .map(|(order, &store_index)| {
1631 let reloads = ordered_stores[..order]
1632 .iter()
1633 .filter_map(|&prior_index| {
1634 let SIRInstruction::Store(address, offset, bits, source, _, _) =
1635 &block.instructions[prior_index]
1636 else {
1637 return None;
1638 };
1639 (register_dependencies[&store_index].contains(source)
1640 && store_source(store_index) != Some(*source)
1641 && unit.register_map[source].width() == *bits)
1642 .then(|| (*source, (*address, offset.clone(), *bits)))
1643 })
1644 .collect::<HashMap<_, _>>();
1645 let mut prefix = HashSet::<usize>::default();
1646 let mut pending = instruction_registers(&block.instructions[store_index]);
1647 while let Some(register) = pending.pop() {
1648 let Some(&definition) = definitions.get(®ister) else {
1649 continue;
1650 };
1651 if !prefix.insert(definition) {
1652 continue;
1653 }
1654 if let Some((_, offset, _)) = reloads.get(®ister) {
1655 offset_registers(offset, &mut pending);
1656 continue;
1657 }
1658 pending.extend(instruction_registers(&block.instructions[definition]));
1659 }
1660 let mut prefix = prefix.into_iter().collect::<Vec<_>>();
1661 prefix.sort_unstable();
1662 let mut instructions = prefix
1663 .into_iter()
1664 .map(|index| {
1665 let instruction = &block.instructions[index];
1666 if let Some(register) = instruction.defined_register()
1667 && let Some((address, offset, bits)) = reloads.get(®ister)
1668 {
1669 return SIRInstruction::Load(register, *address, offset.clone(), *bits);
1670 }
1671 instruction.clone()
1672 })
1673 .collect::<Vec<_>>();
1674 instructions.push(block.instructions[store_index].clone());
1675 let split_block = celox_sir::BasicBlock {
1676 id: BlockId(0),
1677 params: Vec::new(),
1678 instructions,
1679 terminator: SIRTerminator::Return,
1680 };
1681 ExecutionUnit {
1682 entry_block_id: BlockId(0),
1683 blocks: [(BlockId(0), split_block)].into_iter().collect(),
1684 register_map: unit.register_map.clone(),
1685 }
1686 })
1687 .collect::<Vec<_>>();
1688 let ordered_store_sites = ordered_stores
1689 .iter()
1690 .map(|&index| (unit.entry_block_id, index))
1691 .collect::<Vec<_>>();
1692 interleave_comb_runtime_effects(unit, &ordered_store_sites, split)
1693}
1694
1695fn compile_program(
1696 laid_out: &LaidOutProgram,
1697 options: &SimulatorOptions,
1698 capture_trace: bool,
1699 cancel: Option<&CompileCancel>,
1700) -> Result<(NativeProgramImage, Option<NativeCodegenTrace>), SimulatorError> {
1701 let sir = laid_out;
1702 let layout = laid_out.layout();
1703 let (compile_tasks, task_bindings) = collect_ff_compile_tasks(sir);
1704 let comb_blocks = sir
1705 .sir
1706 .eval_comb
1707 .iter()
1708 .map(|unit| unit.blocks.len())
1709 .sum::<usize>();
1710 let max_task_blocks = compile_tasks
1711 .iter()
1712 .map(|task| {
1713 task.units
1714 .iter()
1715 .map(|unit| unit.blocks.len())
1716 .sum::<usize>()
1717 })
1718 .max()
1719 .unwrap_or(0);
1720 let max_instructions = std::iter::once(sir.sir.eval_comb.iter().collect::<Vec<_>>())
1721 .chain(compile_tasks.iter().map(|task| task.units.clone()))
1722 .map(|units| {
1723 units
1724 .iter()
1725 .flat_map(|unit| unit.blocks.values())
1726 .map(|block| block.instructions.len())
1727 .sum::<usize>()
1728 })
1729 .max()
1730 .unwrap_or(0);
1731 let release_pages = || {
1732 #[cfg(all(target_os = "linux", target_env = "gnu"))]
1735 if comb_blocks.max(max_task_blocks) >= 65_536 {
1736 unsafe {
1737 libc::malloc_trim(0);
1738 }
1739 }
1740 };
1741 release_pages();
1742 let workers = super::compile_resources::workers(
1743 comb_blocks.max(max_task_blocks),
1744 max_instructions,
1745 compile_tasks.len() + 1,
1746 );
1747 let serial = workers == 1;
1748 let mut task_order = (0..compile_tasks.len()).collect::<Vec<_>>();
1751 task_order.sort_by_key(|&id| {
1752 std::cmp::Reverse(
1753 compile_tasks[id]
1754 .units
1755 .iter()
1756 .map(|unit| unit.blocks.len())
1757 .sum::<usize>(),
1758 )
1759 });
1760 if options.x86_options.diagnostics.phase_timing {
1761 tracing::debug!(
1762 "[native-timing] compile_program serial={serial} workers={workers} comb_blocks={comb_blocks} max_task_blocks={max_task_blocks} max_instructions={max_instructions} ff_tasks={}",
1763 compile_tasks.len()
1764 );
1765 }
1766 let next_task = AtomicUsize::new(0);
1767 let (comb_jit, compiled_ff_codes) = if serial {
1768 let comb_jit = compile_units(
1769 &sir.sir.eval_comb,
1770 layout,
1771 options.four_state,
1772 "eval_comb",
1773 &options.x86_options,
1774 capture_trace,
1775 &options.optimize_options.diagnostics,
1776 cancel,
1777 )?;
1778 let mut compiled = HashMap::default();
1779 for (task_id, task) in compile_tasks.iter().enumerate() {
1780 release_pages();
1781 let code = compile_unit_refs(
1782 &task.units,
1783 layout,
1784 options.four_state,
1785 task.label,
1786 task.first_ff_unit,
1787 &options.x86_options,
1788 capture_trace,
1789 &options.optimize_options.diagnostics,
1790 cancel,
1791 )?;
1792 compiled.insert(task_id, code);
1793 }
1794 (comb_jit, compiled)
1795 } else {
1796 std::thread::scope(|scope| {
1797 let four_state = options.four_state;
1798 let x86_options = &options.x86_options;
1799 let comb_handle = scope.spawn(move || {
1800 if cancelled(cancel) {
1801 return Err(cancelled_error());
1802 }
1803 compile_units(
1804 &sir.sir.eval_comb,
1805 layout,
1806 four_state,
1807 "eval_comb",
1808 x86_options,
1809 capture_trace,
1810 &options.optimize_options.diagnostics,
1811 cancel,
1812 )
1813 });
1814 let task_worker_count = compile_tasks.len().min(workers.saturating_sub(1));
1815 let task_handles = (0..task_worker_count)
1816 .map(|_| {
1817 let next_task = &next_task;
1818 let compile_tasks = &compile_tasks;
1819 let task_order = &task_order;
1820 scope.spawn(move || {
1821 let mut compiled = Vec::new();
1822 loop {
1823 if cancelled(cancel) {
1824 return Err(cancelled_error());
1825 }
1826 let index = next_task.fetch_add(1, Ordering::Relaxed);
1827 let Some(&task_id) = task_order.get(index) else {
1828 break;
1829 };
1830 let task = &compile_tasks[task_id];
1831 let code = compile_unit_refs(
1832 &task.units,
1833 layout,
1834 four_state,
1835 task.label,
1836 task.first_ff_unit,
1837 x86_options,
1838 capture_trace,
1839 &options.optimize_options.diagnostics,
1840 cancel,
1841 )?;
1842 compiled.push((task_id, code));
1843 }
1844 Ok::<_, SimulatorError>(compiled)
1845 })
1846 })
1847 .collect::<Vec<_>>();
1848
1849 let comb_jit = comb_handle
1850 .join()
1851 .map_err(|_| codegen_message("native eval_comb compile thread panicked"))??;
1852 let mut compiled_ff_codes = HashMap::default();
1853 for handle in task_handles {
1854 let compiled = handle
1855 .join()
1856 .map_err(|_| codegen_message("native FF compile thread panicked"))??;
1857 compiled_ff_codes.extend(compiled);
1858 }
1859 Ok::<_, SimulatorError>((comb_jit, compiled_ff_codes))
1860 })?
1861 };
1862 release_pages();
1863 let force_store_boundaries = options.optimize_options.opt_level() == crate::OptLevel::O0;
1867 let comb_runtime_units = if options.native_force_support {
1868 sir.sir
1869 .eval_comb
1870 .iter()
1871 .flat_map(|unit| {
1872 if force_store_boundaries {
1873 split_comb_execution_unit(unit)
1874 } else {
1875 vec![unit.clone()]
1876 }
1877 })
1878 .collect::<Vec<_>>()
1879 } else {
1880 Vec::new()
1881 };
1882 let comb_unit_jits = comb_runtime_units
1883 .iter()
1884 .enumerate()
1885 .map(|(index, unit)| {
1886 if cancelled(cancel) {
1887 return Err(cancelled_error());
1888 }
1889 compile_unit_refs(
1890 &[unit],
1891 layout,
1892 options.four_state,
1893 &format!("eval_comb_unit[{index}]"),
1894 None,
1895 &options.x86_options,
1896 false,
1897 &options.optimize_options.diagnostics,
1898 cancel,
1899 )
1900 })
1901 .collect::<Result<Vec<_>, _>>()?;
1902 let codegen_trace = capture_trace
1903 .then(|| format_native_codegen_trace(&comb_jit, &compiled_ff_codes, &compile_tasks));
1904 let semantic_memory_size = layout
1905 .merged_total_size
1906 .checked_add(layout.triggered_bits_total_size)
1907 .expect("native semantic-memory size overflow");
1908 let native_memory_size = std::iter::once(comb_jit.required_state_size)
1909 .chain(
1910 compiled_ff_codes
1911 .values()
1912 .map(|compiled| compiled.required_state_size),
1913 )
1914 .chain(
1915 comb_unit_jits
1916 .iter()
1917 .map(|compiled| compiled.required_state_size),
1918 )
1919 .fold(semantic_memory_size, usize::max);
1920 let required_native_features = std::iter::once(comb_jit.required_native_features)
1921 .chain(
1922 compiled_ff_codes
1923 .values()
1924 .map(|compiled| compiled.required_native_features),
1925 )
1926 .chain(
1927 comb_unit_jits
1928 .iter()
1929 .map(|compiled| compiled.required_native_features),
1930 )
1931 .fold(0, |features, required| features | required);
1932 let mut packed_image = Vec::new();
1933 let mut code_entries = Vec::with_capacity(1 + compiled_ff_codes.len());
1934 let mut image_symbols = Vec::new();
1935 let comb_offset = append_native_code(
1936 &mut packed_image,
1937 &mut code_entries,
1938 &mut image_symbols,
1939 "eval_comb".into(),
1940 &comb_jit,
1941 )?;
1942 let mut comb_unit_offsets = Vec::with_capacity(comb_unit_jits.len());
1943 for (index, compiled) in comb_unit_jits.iter().enumerate() {
1944 comb_unit_offsets.push(append_native_code(
1945 &mut packed_image,
1946 &mut code_entries,
1947 &mut image_symbols,
1948 format!("eval_comb_unit[{index}]"),
1949 compiled,
1950 )?);
1951 }
1952 let mut compiled_ff_keys = compiled_ff_codes.keys().copied().collect::<Vec<_>>();
1953 compiled_ff_keys.sort_unstable();
1954 let mut task_offsets = HashMap::default();
1955 let mut label_indices = HashMap::<&str, usize>::default();
1956 for &task_id in &compiled_ff_keys {
1957 let task = &compile_tasks[task_id];
1958 let index = label_indices.entry(task.label).or_default();
1959 let name = format!("{}[{index}]", task.label);
1960 *index += 1;
1961 let offset = append_native_code(
1962 &mut packed_image,
1963 &mut code_entries,
1964 &mut image_symbols,
1965 name,
1966 &compiled_ff_codes[&task_id],
1967 )?;
1968 task_offsets.insert(task_id, offset);
1969 }
1970 let mut next_id = 0usize;
1974 let mut id_to_addr = Vec::new();
1975 let mut id_to_event = Vec::new();
1976 let mut event_map = HashMap::default();
1977 let mut eval_only_event_map = HashMap::default();
1978 let mut apply_event_map = HashMap::default();
1979 let mut addr_to_id = HashMap::default();
1980 let compile_ff_group = |ff_map: &HashMap<
1981 AbsoluteAddr,
1982 Vec<crate::ir::ExecutionUnit<crate::ir::RegionedAbsoluteAddr>>,
1983 >,
1984 label: &'static str,
1985 event_map_out: &mut HashMap<AbsoluteAddr, NativeEventImageRef>,
1986 addr_to_id: &mut HashMap<AbsoluteAddr, usize>,
1987 compiled_ff_cache: &HashMap<usize, usize>,
1988 comb_apply_label: Option<&'static str>,
1989 next_id: &mut usize,
1990 id_to_addr: &mut Vec<AbsoluteAddr>,
1991 id_to_event: &mut Vec<NativeEventImageRef>|
1992 -> Result<(), SimulatorError> {
1993 for addr in ff_map.keys() {
1994 let canonical = sir.design.events.canonical(*addr);
1995 if let Some(&event) = event_map_out.get(&canonical) {
1996 event_map_out.insert(*addr, event);
1997 continue;
1998 }
1999
2000 let task_id = task_bindings[&(label, *addr)];
2001 let func_offset = compiled_ff_cache[&task_id];
2002 let comb_apply_offset = comb_apply_label
2003 .map(|label| {
2004 let task_id = task_bindings[&(label, *addr)];
2005 compiled_ff_cache[&task_id]
2006 })
2007 .unwrap_or(func_offset);
2008
2009 let (id, is_new_id) = if let Some(&id) = addr_to_id.get(&canonical) {
2010 (id, false)
2011 } else {
2012 let id = *next_id;
2013 *next_id += 1;
2014 addr_to_id.insert(canonical, id);
2015 id_to_addr.push(canonical);
2016 (id, true)
2017 };
2018
2019 let event = NativeEventImageRef {
2020 func_offset,
2021 comb_apply_offset,
2022 addr: canonical,
2023 id,
2024 };
2025 event_map_out.insert(canonical, event);
2026 if *addr != canonical {
2027 event_map_out.insert(*addr, event);
2028 }
2029 if is_new_id {
2030 id_to_event.push(event);
2031 }
2032 }
2033 Ok(())
2034 };
2035
2036 compile_ff_group(
2037 &sir.sir.eval_apply_ffs,
2038 "eval_apply_ff",
2039 &mut event_map,
2040 &mut addr_to_id,
2041 &task_offsets,
2042 Some("eval_comb_apply_ff"),
2043 &mut next_id,
2044 &mut id_to_addr,
2045 &mut id_to_event,
2046 )?;
2047 compile_ff_group(
2048 &sir.sir.eval_only_ffs,
2049 "eval_only_ff",
2050 &mut eval_only_event_map,
2051 &mut addr_to_id,
2052 &task_offsets,
2053 None,
2054 &mut next_id,
2055 &mut id_to_addr,
2056 &mut id_to_event,
2057 )?;
2058 compile_ff_group(
2059 &sir.sir.apply_ffs,
2060 "apply_ff",
2061 &mut apply_event_map,
2062 &mut addr_to_id,
2063 &task_offsets,
2064 None,
2065 &mut next_id,
2066 &mut id_to_addr,
2067 &mut id_to_event,
2068 )?;
2069 let mut four_state_inits = Vec::new();
2071 if options.four_state {
2072 for (addr, &offset) in &layout.offsets {
2073 let is_4state = layout.is_4states.get(addr).copied().unwrap_or(false);
2074 if is_4state {
2075 let allocated_size = layout.plane_size(addr);
2076 four_state_inits.push((offset, allocated_size));
2077 }
2078 }
2079 for (addr, &rel_offset) in &layout.working_offsets {
2080 let offset = layout.working_base_offset + rel_offset;
2081 let is_4state = layout.is_4states.get(addr).copied().unwrap_or(false);
2082 if is_4state {
2083 let allocated_size = layout.plane_size(addr);
2084 four_state_inits.push((offset, allocated_size));
2085 }
2086 }
2087 }
2088
2089 Ok((
2090 NativeProgramImage {
2091 code: packed_image,
2092 code_entries,
2093 symbols: image_symbols,
2094 comb_offset,
2095 comb_unit_offsets,
2096 required_native_features,
2097 event_map,
2098 eval_only_event_map,
2099 apply_event_map,
2100 id_to_addr,
2101 id_to_event,
2102 reflection: sir.runtime().build_design_reflection(layout),
2103 design: sir.runtime().design.clone(),
2104 testbench: sir.runtime().testbench.clone(),
2105 runtime_schema: NativeRuntimeSchema {
2106 runtime_errors: sir.runtime().runtime_schema.runtime_errors.clone(),
2107 runtime_event_sites: sir.runtime().runtime_schema.runtime_event_sites.clone(),
2108 comb_observers: sir.runtime().runtime_schema.comb_observers.clone(),
2109 testbench_read_roots: sir.runtime().runtime_schema.testbench_read_roots.clone(),
2110 rtl_writes: sir.runtime().runtime_schema.rtl_writes.clone(),
2111 },
2112 layout: layout.clone(),
2113 native_memory_size,
2114 options: NativeRuntimeOptions {
2115 four_state: options.four_state,
2116 native_tick_loop: options.x86_options.native_tick_loop,
2117 native_force_support: options.native_force_support,
2118 perf_map: options.x86_options.diagnostics.perf_map,
2119 },
2120 four_state_inits,
2121 },
2122 codegen_trace,
2123 ))
2124}
2125
2126pub struct NativeBackend {
2131 compiled: Arc<SharedNativeCode>,
2132 memory: MemoryImage,
2133 runtime_event_buffer: Arc<RuntimeEventBuffer>,
2134 comb_capture_enabled: Vec<u8>,
2135 execution_timing: Option<NativeExecutionTiming>,
2136}
2137
2138fn write_bits_to_memory_from(
2139 memory: &mut [u8],
2140 destination_bit_offset: usize,
2141 bit_width: usize,
2142 source: &[u8],
2143 source_bit_offset: usize,
2144) {
2145 for bit in 0..bit_width {
2146 let source_bit = source_bit_offset + bit;
2147 let value = (source[source_bit / 8] >> (source_bit % 8)) & 1;
2148 let destination_bit = destination_bit_offset + bit;
2149 let destination = &mut memory[destination_bit / 8];
2150 let mask = 1u8 << (destination_bit % 8);
2151 if value == 0 {
2152 *destination &= !mask;
2153 } else {
2154 *destination |= mask;
2155 }
2156 }
2157}
2158
2159fn write_bits_to_memory(
2160 memory: &mut [u8],
2161 destination_bit_offset: usize,
2162 bit_width: usize,
2163 source: &[u8],
2164) {
2165 write_bits_to_memory_from(memory, destination_bit_offset, bit_width, source, 0);
2166}
2167
2168fn write_initial_run_to_plane(
2169 memory: &mut [u8],
2170 signal: SignalRef,
2171 mask_plane: bool,
2172 run: &InitialStateWriteRun,
2173 source: &[u8],
2174) {
2175 let Some(array) = signal.array_layout else {
2176 let plane_size = signal.width.div_ceil(8);
2177 let destination_bit_offset =
2178 (signal.offset + usize::from(mask_plane) * plane_size) * 8 + run.bit_offset;
2179 write_bits_to_memory(memory, destination_bit_offset, run.bit_width, source);
2180 return;
2181 };
2182
2183 let plane_offset = signal.offset + usize::from(mask_plane) * array.plane_size;
2184 let mut consumed = 0usize;
2185 while consumed < run.bit_width {
2186 let logical_offset = run.bit_offset + consumed;
2187 let element = logical_offset / array.element_width;
2188 let intra_element = logical_offset % array.element_width;
2189 let part_width = (run.bit_width - consumed).min(array.element_width - intra_element);
2190 let destination_bit_offset =
2191 (plane_offset + element * array.element_stride) * 8 + intra_element;
2192
2193 if consumed.is_multiple_of(8)
2194 && destination_bit_offset.is_multiple_of(8)
2195 && part_width.is_multiple_of(8)
2196 {
2197 let source_byte = consumed / 8;
2198 let destination_byte = destination_bit_offset / 8;
2199 let byte_width = part_width / 8;
2200 memory[destination_byte..destination_byte + byte_width]
2201 .copy_from_slice(&source[source_byte..source_byte + byte_width]);
2202 } else {
2203 write_bits_to_memory_from(memory, destination_bit_offset, part_width, source, consumed);
2204 }
2205 consumed += part_width;
2206 }
2207}
2208
2209impl NativeBackend {
2210 pub(crate) fn eval_comb_units_with(
2211 &mut self,
2212 mut after_unit: impl FnMut(&mut Self),
2213 ) -> Result<(), SimulatorErrorCode> {
2214 let funcs = self.compiled.comb_unit_funcs.clone();
2215 if funcs.is_empty() {
2216 return self.eval_comb();
2217 }
2218 for func in funcs {
2219 self.call_func_timed(func)?;
2220 after_unit(self);
2221 }
2222 Ok(())
2223 }
2224
2225 pub fn compile_image(
2229 laid_out: &LaidOutProgram,
2230 options: &SimulatorOptions,
2231 ) -> Result<NativeProgramImage, SimulatorError> {
2232 let (image, trace) = compile_program(laid_out, options, false, None)?;
2233 debug_assert!(trace.is_none());
2234 Ok(image)
2235 }
2236
2237 pub(crate) fn compile_image_with_cancel(
2245 laid_out: &LaidOutProgram,
2246 options: &SimulatorOptions,
2247 cancel: &CompileCancel,
2248 ) -> Result<NativeProgramImage, SimulatorError> {
2249 let (image, trace) = compile_program(laid_out, options, false, Some(cancel))?;
2250 debug_assert!(trace.is_none());
2251 Ok(image)
2252 }
2253
2254 pub(crate) fn compile_image_with_cancel_and_trace(
2256 laid_out: &LaidOutProgram,
2257 options: &SimulatorOptions,
2258 cancel: &CompileCancel,
2259 ) -> Result<(NativeProgramImage, NativeCodegenTrace), SimulatorError> {
2260 let (image, trace) = compile_program(laid_out, options, true, Some(cancel))?;
2261 Ok((
2262 image,
2263 trace.expect("trace-enabled native compilation must return a trace"),
2264 ))
2265 }
2266
2267 pub(crate) fn compile_image_with_codegen_trace(
2268 laid_out: &LaidOutProgram,
2269 options: &SimulatorOptions,
2270 ) -> Result<(NativeProgramImage, NativeCodegenTrace), SimulatorError> {
2271 let (image, trace) = compile_program(laid_out, options, true, None)?;
2272 Ok((
2273 image,
2274 trace.expect("trace-enabled native compilation must return a trace"),
2275 ))
2276 }
2277
2278 pub unsafe fn from_image(image: NativeProgramImage) -> Result<Self, SimulatorError> {
2287 let shared = Arc::new(unsafe { SharedNativeCode::from_image(image)? });
2289 Ok(Self::from_shared(shared))
2290 }
2291
2292 pub fn new(
2293 laid_out: &LaidOutProgram,
2294 options: &SimulatorOptions,
2295 ) -> Result<Self, SimulatorError> {
2296 let image = Self::compile_image(laid_out, options)?;
2297 unsafe { Self::from_image(image) }
2299 }
2300
2301 #[cfg(any(
2302 all(target_arch = "x86_64", not(feature = "arm64-codegen")),
2303 all(target_arch = "aarch64", not(feature = "x86_64-codegen"))
2304 ))]
2305 pub(crate) fn new_with_codegen_trace(
2306 laid_out: &LaidOutProgram,
2307 options: &SimulatorOptions,
2308 ) -> Result<(Self, NativeCodegenTrace), SimulatorError> {
2309 let (image, trace) = Self::compile_image_with_codegen_trace(laid_out, options)?;
2310 let shared = unsafe { SharedNativeCode::from_image(image)? };
2312 let backend = Self::from_shared(Arc::new(shared));
2313 Ok((backend, trace))
2314 }
2315
2316 pub fn from_shared(shared: Arc<SharedNativeCode>) -> Self {
2319 let mem_size_words = shared.native_memory_size.div_ceil(8);
2320 let mut memory = MemoryImage::zeroed(mem_size_words + 1); let runtime_event_buffer = Arc::new(RuntimeEventBuffer::new(
2322 shared.layout.runtime_event_buffer_size,
2323 ));
2324 let comb_capture_enabled = vec![0; shared.layout.runtime_event_site_layouts.len().max(1)];
2325
2326 for &(offset, allocated_size) in &shared.four_state_inits {
2328 unsafe {
2329 let base_ptr = (memory.as_mut_ptr() as *mut u8).add(offset);
2330 std::ptr::write_bytes(base_ptr, 0xFF, allocated_size);
2331 let mask_ptr = base_ptr.add(allocated_size);
2332 std::ptr::write_bytes(mask_ptr, 0xFF, allocated_size);
2333 }
2334 }
2335
2336 let mut backend = Self {
2337 compiled: shared,
2338 memory,
2339 runtime_event_buffer,
2340 comb_capture_enabled,
2341 execution_timing: None,
2342 };
2343 backend.install_event_buffers();
2344 let compiled = Arc::clone(&backend.compiled);
2345 backend.apply_initial_values(&compiled.program_image.design.initial_state);
2346 backend
2347 }
2348
2349 pub(crate) fn adopt_shared_with_state(
2356 shared: Arc<SharedNativeCode>,
2357 memory: MemoryImage,
2358 runtime_event_buffer: Arc<RuntimeEventBuffer>,
2359 comb_capture_enabled: Vec<u8>,
2360 ) -> Self {
2361 Self {
2362 compiled: shared,
2363 memory,
2364 runtime_event_buffer,
2365 comb_capture_enabled,
2366 execution_timing: None,
2367 }
2368 }
2369
2370 fn apply_initial_values(&mut self, initial_state: &[InitialStateValue<AbsoluteAddr>]) {
2371 for init in initial_state {
2372 let signal = self.resolve_signal(&init.address);
2373 match &init.data {
2374 InitialStateData::Packed {
2375 value,
2376 mask,
2377 written_mask,
2378 } => {
2379 let width_mask = if signal.width == 0 {
2380 BigUint::default()
2381 } else {
2382 (BigUint::from(1u8) << signal.width) - BigUint::from(1u8)
2383 };
2384 let preserve_mask = &width_mask ^ (written_mask & &width_mask);
2385 let (current_value, current_mask) = self.get_four_state(signal);
2386 let value = (current_value & &preserve_mask) | (value & written_mask);
2387 let mask = (current_mask & &preserve_mask) | (mask & written_mask);
2388 if self.compiled.options.four_state && signal.is_4state {
2389 self.set_four_state(signal, value, mask);
2390 } else {
2391 let known_mask = &width_mask ^ (&mask & &width_mask);
2392 self.set_wide(signal, value & known_mask);
2393 }
2394 }
2395 InitialStateData::Writes(runs) => self.apply_initial_memory_writes(signal, runs),
2396 }
2397 }
2398 }
2399
2400 fn apply_initial_memory_writes(&mut self, signal: SignalRef, runs: &[InitialStateWriteRun]) {
2401 let value_byte_size = signal.width.div_ceil(8);
2402 let write_mask = self.compiled.options.four_state && signal.is_4state;
2403 let mem = self.mem_bytes_mut();
2404
2405 for run in runs {
2406 if run.bit_width == 0 {
2407 continue;
2408 }
2409 if signal.array_layout.is_some() {
2410 write_initial_run_to_plane(mem, signal, false, run, &run.value_bytes);
2411 if write_mask {
2412 write_initial_run_to_plane(mem, signal, true, run, &run.mask_bytes);
2413 }
2414 continue;
2415 }
2416 if run.bit_offset.is_multiple_of(8) && run.bit_width.is_multiple_of(8) {
2417 let byte_offset = run.bit_offset / 8;
2418 let byte_width = run.bit_width / 8;
2419 let value_offset = signal.offset + byte_offset;
2420 mem[value_offset..value_offset + byte_width]
2421 .copy_from_slice(&run.value_bytes[..byte_width]);
2422 if write_mask {
2423 let mask_offset = signal.offset + value_byte_size + byte_offset;
2424 mem[mask_offset..mask_offset + byte_width]
2425 .copy_from_slice(&run.mask_bytes[..byte_width]);
2426 }
2427 continue;
2428 }
2429
2430 write_bits_to_memory(
2431 mem,
2432 signal.offset * 8 + run.bit_offset,
2433 run.bit_width,
2434 &run.value_bytes,
2435 );
2436 if write_mask {
2437 write_bits_to_memory(
2438 mem,
2439 (signal.offset + value_byte_size) * 8 + run.bit_offset,
2440 run.bit_width,
2441 &run.mask_bytes,
2442 );
2443 }
2444 }
2445 }
2446
2447 pub fn start_execution_timing(&mut self) {
2449 self.execution_timing = Some(NativeExecutionTiming::default());
2450 }
2451
2452 pub(crate) fn memory_word_capacity(&self) -> usize {
2454 self.memory.capacity_words()
2455 }
2456
2457 pub(crate) fn replace_shared_code(&mut self, shared: Arc<SharedNativeCode>) {
2464 let mem_size_words = shared.native_memory_size.div_ceil(8) + 1;
2465 if self.memory.len_words() < mem_size_words {
2466 self.memory.resize_zeroed_within_capacity(mem_size_words);
2467 }
2468 self.compiled = shared;
2469 }
2470
2471 pub fn finish_execution_timing(&mut self) -> Option<NativeExecutionTiming> {
2473 self.execution_timing.take()
2474 }
2475
2476 fn install_event_buffers(&mut self) {
2477 use crate::backend::memory_layout::{
2478 STATE_HEADER_COMB_CAPTURE_ENABLED_ADDR_OFFSET, STATE_HEADER_RUNTIME_EVENT_ADDR_OFFSET,
2479 };
2480
2481 let addr = self.runtime_event_buffer.as_mut_ptr() as u64;
2482 let ptr = unsafe {
2483 (self.memory.as_mut_ptr() as *mut u8).add(STATE_HEADER_RUNTIME_EVENT_ADDR_OFFSET)
2484 as *mut u64
2485 };
2486 unsafe {
2487 std::ptr::write_unaligned(ptr, addr);
2488 }
2489 let addr = self.comb_capture_enabled.as_ptr() as u64;
2490 let ptr = unsafe {
2491 (self.memory.as_mut_ptr() as *mut u8).add(STATE_HEADER_COMB_CAPTURE_ENABLED_ADDR_OFFSET)
2492 as *mut u64
2493 };
2494 unsafe {
2495 std::ptr::write_unaligned(ptr, addr);
2496 }
2497 }
2498
2499 pub fn shared_code(&self) -> Arc<SharedNativeCode> {
2501 Arc::clone(&self.compiled)
2502 }
2503
2504 fn mem_ptr(&self) -> *const u8 {
2505 self.memory.as_ptr() as *const u8
2506 }
2507
2508 fn mem_mut_ptr(&mut self) -> *mut u8 {
2509 self.memory.as_mut_ptr() as *mut u8
2510 }
2511
2512 fn mem_bytes(&self) -> &[u8] {
2513 let ptr = self.mem_ptr();
2514 let len = self.memory.len_words() * 8;
2515 unsafe { std::slice::from_raw_parts(ptr, len) }
2516 }
2517
2518 fn mem_bytes_mut(&mut self) -> &mut [u8] {
2519 let ptr = self.mem_mut_ptr();
2520 let len = self.memory.len_words() * 8;
2521 unsafe { std::slice::from_raw_parts_mut(ptr, len) }
2522 }
2523
2524 fn read_signal_plane(&self, signal: SignalRef, mask_plane: bool) -> BigUint {
2525 let bytes = self.mem_bytes();
2526 let Some(array) = signal.array_layout else {
2527 let byte_size = get_byte_size(signal.width);
2528 let plane_offset = signal.offset + usize::from(mask_plane) * byte_size;
2529 let mut value = BigUint::from_bytes_le(&bytes[plane_offset..plane_offset + byte_size]);
2530 if !signal.width.is_multiple_of(8) {
2531 value &= (BigUint::from(1u8) << signal.width) - BigUint::from(1u8);
2532 }
2533 return value;
2534 };
2535
2536 let plane_offset = signal.offset + usize::from(mask_plane) * array.plane_size;
2537 let element_bytes = get_byte_size(array.element_width);
2538 let element_mask = (BigUint::from(1u8) << array.element_width) - BigUint::from(1u8);
2539 let mut value = BigUint::from(0u8);
2540 for element in 0..array.element_count {
2541 let start = plane_offset + element * array.element_stride;
2542 let element_value =
2543 BigUint::from_bytes_le(&bytes[start..start + element_bytes]) & &element_mask;
2544 value |= element_value << (element * array.element_width);
2545 }
2546 value
2547 }
2548
2549 fn write_signal_plane(&mut self, signal: SignalRef, mask_plane: bool, value: &BigUint) {
2550 let Some(array) = signal.array_layout else {
2551 let byte_size = get_byte_size(signal.width);
2552 let plane_offset = signal.offset + usize::from(mask_plane) * byte_size;
2553 let bytes = self.mem_bytes_mut();
2554 bytes[plane_offset..plane_offset + byte_size].fill(0);
2555 let value_bytes = value.to_bytes_le();
2556 let copy_len = value_bytes.len().min(byte_size);
2557 bytes[plane_offset..plane_offset + copy_len].copy_from_slice(&value_bytes[..copy_len]);
2558 if !signal.width.is_multiple_of(8) && byte_size != 0 {
2559 bytes[plane_offset + byte_size - 1] &= (1u8 << (signal.width % 8)) - 1;
2560 }
2561 return;
2562 };
2563
2564 let plane_offset = signal.offset + usize::from(mask_plane) * array.plane_size;
2565 let element_bytes = get_byte_size(array.element_width);
2566 let element_mask = (BigUint::from(1u8) << array.element_width) - BigUint::from(1u8);
2567 let bytes = self.mem_bytes_mut();
2568 bytes[plane_offset..plane_offset + array.plane_size].fill(0);
2569 for element in 0..array.element_count {
2570 let element_value = (value >> (element * array.element_width)) & &element_mask;
2571 let value_bytes = element_value.to_bytes_le();
2572 let copy_len = value_bytes.len().min(element_bytes);
2573 let start = plane_offset + element * array.element_stride;
2574 bytes[start..start + copy_len].copy_from_slice(&value_bytes[..copy_len]);
2575 }
2576 }
2577
2578 fn call_func(memory: &mut [u64], func: NativeSimFunc) -> Result<(), SimulatorErrorCode> {
2579 let ptr = memory.as_mut_ptr() as *mut u8;
2580 let ret = unsafe { func(ptr) };
2581 match ret {
2582 0 => Ok(()),
2583 code if code > 0 => Err(SimulatorErrorCode::DetectedTrueLoopCode(code)),
2584 _ => Err(SimulatorErrorCode::InternalError),
2585 }
2586 }
2587
2588 fn call_func_timed(&mut self, func: NativeSimFunc) -> Result<(), SimulatorErrorCode> {
2589 let Some(_) = self.execution_timing else {
2590 return Self::call_func(self.memory.as_mut_slice(), func);
2591 };
2592 let start = Instant::now();
2593 let result = Self::call_func(self.memory.as_mut_slice(), func);
2594 let elapsed = start.elapsed();
2595 let timing = self
2596 .execution_timing
2597 .as_mut()
2598 .expect("native execution timing was enabled before the call");
2599 timing.elapsed = timing.elapsed.saturating_add(elapsed);
2600 timing.calls = timing.calls.saturating_add(1);
2601 result
2602 }
2603
2604 fn call_func_many(
2605 memory: &mut [u64],
2606 func: NativeSimFunc,
2607 count: u64,
2608 ) -> (u64, Result<(), SimulatorErrorCode>) {
2609 use crate::backend::memory_layout::STATE_HEADER_NATIVE_LOOP_REMAINING_OFFSET;
2610
2611 if count == 0 {
2612 return (0, Ok(()));
2613 }
2614 let remaining_word = STATE_HEADER_NATIVE_LOOP_REMAINING_OFFSET / 8;
2615 memory[remaining_word] = count;
2616 let ptr = memory.as_mut_ptr() as *mut u8;
2617 let ret = unsafe { func(ptr) };
2618 let completed = count.saturating_sub(memory[remaining_word]);
2619 let result = match ret {
2620 0 => Ok(()),
2621 code if code > 0 => Err(SimulatorErrorCode::DetectedTrueLoopCode(code)),
2622 _ => Err(SimulatorErrorCode::InternalError),
2623 };
2624 (completed, result)
2625 }
2626
2627 fn call_func_many_timed(
2628 &mut self,
2629 func: NativeSimFunc,
2630 count: u64,
2631 ) -> (u64, Result<(), SimulatorErrorCode>) {
2632 if self.execution_timing.is_none() || count == 0 {
2633 return Self::call_func_many(self.memory.as_mut_slice(), func, count);
2634 }
2635 let start = Instant::now();
2636 let result = Self::call_func_many(self.memory.as_mut_slice(), func, count);
2637 let elapsed = start.elapsed();
2638 let timing = self
2639 .execution_timing
2640 .as_mut()
2641 .expect("native execution timing was enabled before the call");
2642 timing.elapsed = timing.elapsed.saturating_add(elapsed);
2643 timing.calls = timing.calls.saturating_add(1);
2644 result
2645 }
2646}
2647
2648impl super::super::SimBackend for NativeBackend {
2649 type Event = NativeEventRef;
2650
2651 fn eval_comb(&mut self) -> Result<(), SimulatorErrorCode> {
2652 let func = self.compiled.comb_func;
2653 self.call_func_timed(func)
2654 }
2655
2656 fn eval_apply_ff_at(&mut self, event: NativeEventRef) -> Result<(), SimulatorErrorCode> {
2657 self.call_func_timed(event.func)
2658 }
2659
2660 fn eval_comb_apply_ff_at(&mut self, event: NativeEventRef) -> Result<(), SimulatorErrorCode> {
2661 self.call_func_timed(event.comb_apply_func)
2662 }
2663
2664 fn eval_comb_apply_ff_many_at(
2665 &mut self,
2666 event: NativeEventRef,
2667 count: u64,
2668 ) -> (u64, Result<(), SimulatorErrorCode>) {
2669 if self.compiled.options.native_tick_loop {
2670 self.call_func_many_timed(event.comb_apply_func, count)
2671 } else if count == 0 {
2672 (0, Ok(()))
2673 } else {
2674 (1, self.call_func_timed(event.comb_apply_func))
2675 }
2676 }
2677
2678 fn eval_only_ff_at(&mut self, event: NativeEventRef) -> Result<(), SimulatorErrorCode> {
2679 self.call_func_timed(event.func)
2680 }
2681
2682 fn apply_ff_at(&mut self, event: NativeEventRef) -> Result<(), SimulatorErrorCode> {
2683 self.call_func_timed(event.func)
2684 }
2685
2686 fn resolve_signal(&self, addr: &AbsoluteAddr) -> SignalRef {
2687 let layout = &self.compiled.layout;
2688 let offset = layout.offsets.get(addr).copied().unwrap_or(0);
2689 let width = layout.widths.get(addr).copied().unwrap_or(0);
2690 let is_4state = layout.is_4states.get(addr).copied().unwrap_or(false);
2691 let array_layout = layout
2692 .unpacked_arrays
2693 .get(addr)
2694 .map(|array| SignalArrayLayout {
2695 element_width: array.element_width,
2696 element_count: array.element_count,
2697 element_stride: array.element_stride,
2698 plane_size: array.plane_size,
2699 });
2700 SignalRef {
2701 offset,
2702 width,
2703 is_4state,
2704 array_layout,
2705 }
2706 }
2707
2708 fn resolve_event(&self, addr: &AbsoluteAddr) -> NativeEventRef {
2709 *self
2710 .compiled
2711 .event_map
2712 .get(addr)
2713 .unwrap_or_else(|| panic!("event not found for {:?}", addr))
2714 }
2715
2716 fn resolve_event_opt(&self, addr: &AbsoluteAddr) -> Option<NativeEventRef> {
2717 self.compiled.event_map.get(addr).copied()
2718 }
2719
2720 fn resolve_eval_only_event(&self, addr: &AbsoluteAddr) -> Option<NativeEventRef> {
2721 self.compiled.eval_only_event_map.get(addr).copied()
2722 }
2723
2724 fn resolve_apply_event(&self, addr: &AbsoluteAddr) -> Option<NativeEventRef> {
2725 self.compiled.apply_event_map.get(addr).copied()
2726 }
2727
2728 fn set<T: Copy>(&mut self, signal: SignalRef, val: T) {
2729 celox_runtime::backend::SimBackend::mark_vcd_signal(self, signal);
2730 let allocated_size = get_byte_size(signal.width);
2731 let provided_size = std::mem::size_of::<T>();
2732 let clear_mask = self.compiled.options.four_state && signal.is_4state;
2733
2734 assert!(provided_size <= allocated_size);
2735
2736 if signal.array_layout.is_some() {
2737 let value_bytes =
2738 unsafe { std::slice::from_raw_parts(&val as *const T as *const u8, provided_size) };
2739 self.write_signal_plane(signal, false, &BigUint::from_bytes_le(value_bytes));
2740 if clear_mask {
2741 self.write_signal_plane(signal, true, &BigUint::from(0u8));
2742 }
2743 return;
2744 }
2745
2746 unsafe {
2747 let base_ptr = (self.memory.as_mut_ptr() as *mut u8).add(signal.offset);
2748 if !clear_mask && allocated_size == 1 {
2749 let raw = *(&val as *const T as *const u8);
2750 let byte = if signal.width < 8 {
2751 raw & ((1u8 << signal.width) - 1)
2752 } else {
2753 raw
2754 };
2755 *base_ptr = byte;
2756 return;
2757 }
2758
2759 if provided_size < allocated_size {
2760 std::ptr::write_bytes(base_ptr, 0, allocated_size);
2761 }
2762 std::ptr::write_unaligned(base_ptr as *mut T, val);
2763
2764 if clear_mask {
2765 let mask_ptr = base_ptr.add(allocated_size);
2766 std::ptr::write_bytes(mask_ptr, 0, allocated_size);
2767 }
2768 }
2769 }
2770
2771 fn set_wide(&mut self, signal: SignalRef, val: BigUint) {
2772 celox_runtime::backend::SimBackend::mark_vcd_signal(self, signal);
2773 let clear_mask = self.compiled.options.four_state && signal.is_4state;
2774 self.write_signal_plane(signal, false, &val);
2775 if clear_mask {
2776 self.write_signal_plane(signal, true, &BigUint::from(0u8));
2777 }
2778 }
2779
2780 fn set_four_state(&mut self, signal: SignalRef, val: BigUint, mask: BigUint) {
2781 celox_runtime::backend::SimBackend::mark_vcd_signal(self, signal);
2782 let write_mask = self.compiled.options.four_state && signal.is_4state;
2783 self.write_signal_plane(signal, false, &val);
2784 if write_mask {
2785 self.write_signal_plane(signal, true, &mask);
2786 }
2787 }
2788
2789 fn get(&self, signal: SignalRef) -> BigUint {
2790 self.read_signal_plane(signal, false)
2791 }
2792
2793 fn get_as<T: Default + Copy>(&self, signal: SignalRef) -> T {
2794 let bs = get_byte_size(signal.width);
2795 let provided_size = std::mem::size_of::<T>();
2796 if signal.array_layout.is_some() {
2797 let mut val = T::default();
2798 let value = self.read_signal_plane(signal, false).to_bytes_le();
2799 let val_bytes = unsafe {
2800 std::slice::from_raw_parts_mut(&mut val as *mut T as *mut u8, provided_size)
2801 };
2802 let copy_len = value.len().min(val_bytes.len());
2803 val_bytes[..copy_len].copy_from_slice(&value[..copy_len]);
2804 return val;
2805 }
2806 let ptr = unsafe { (self.memory.as_ptr() as *const u8).add(signal.offset) };
2807 if provided_size <= bs {
2808 return unsafe { std::ptr::read_unaligned(ptr as *const T) };
2809 }
2810
2811 let bytes = self.mem_bytes();
2812 let mut val = T::default();
2813 let val_bytes =
2814 unsafe { std::slice::from_raw_parts_mut(&mut val as *mut T as *mut u8, provided_size) };
2815 let copy_len = val_bytes.len().min(bs);
2816 val_bytes[..copy_len].copy_from_slice(&bytes[signal.offset..signal.offset + copy_len]);
2817 val
2818 }
2819
2820 fn get_four_state(&self, signal: SignalRef) -> (BigUint, BigUint) {
2821 let val = self.read_signal_plane(signal, false);
2822 let mask = if self.compiled.options.four_state && signal.is_4state {
2823 self.read_signal_plane(signal, true)
2824 } else {
2825 BigUint::from(0u32)
2826 };
2827 (val, mask)
2828 }
2829
2830 fn memory_as_ptr(&self) -> (*const u8, usize) {
2831 (self.mem_ptr(), self.memory.len_words() * 8)
2832 }
2833
2834 fn memory_as_mut_ptr(&mut self) -> (*mut u8, usize) {
2835 (
2836 self.memory.expose_mut_ptr().cast(),
2837 self.memory.len_words() * 8,
2838 )
2839 }
2840
2841 fn memory_owner(&self) -> Option<Arc<dyn std::any::Any + Send + Sync>> {
2842 Some(self.memory.owner())
2843 }
2844
2845 fn vcd_tracking_enabled(&self) -> bool {
2846 self.memory.vcd_tracking_enabled()
2847 }
2848
2849 fn runtime_event_buffer_as_ptr(&self) -> (*const u8, usize) {
2850 (
2851 self.runtime_event_buffer.as_ptr(),
2852 self.runtime_event_buffer.byte_size(),
2853 )
2854 }
2855
2856 fn runtime_event_buffer(&self) -> Option<Arc<RuntimeEventBuffer>> {
2857 Some(Arc::clone(&self.runtime_event_buffer))
2858 }
2859
2860 fn set_comb_capture_event_enabled(&mut self, active_sites: &[bool]) {
2861 self.comb_capture_enabled.fill(0);
2862 for (idx, active) in active_sites.iter().copied().enumerate() {
2863 if active && idx < self.comb_capture_enabled.len() {
2864 self.comb_capture_enabled[idx] = 1;
2865 }
2866 }
2867 }
2868
2869 fn stable_region_size(&self) -> usize {
2870 self.compiled.layout.total_size
2871 }
2872
2873 fn layout(&self) -> &MemoryLayout {
2874 &self.compiled.layout
2875 }
2876
2877 fn id_to_addr_slice(&self) -> &[AbsoluteAddr] {
2878 &self.compiled.id_to_addr
2879 }
2880
2881 fn id_to_event_slice(&self) -> &[NativeEventRef] {
2882 &self.compiled.id_to_event
2883 }
2884
2885 fn num_events(&self) -> usize {
2886 self.compiled.id_to_event.len()
2887 }
2888
2889 fn clear_triggered_bits(&mut self) {
2890 let offset = self.compiled.layout.triggered_bits_offset;
2891 let size = self.compiled.layout.triggered_bits_total_size;
2892 let bytes = self.mem_bytes_mut();
2893 bytes[offset..offset + size].fill(0);
2894 }
2895
2896 fn mark_triggered_bit(&mut self, id: usize) {
2897 let offset = self.compiled.layout.triggered_bits_offset;
2898 let byte_idx = offset + id / 8;
2899 let bit_idx = id % 8;
2900 self.mem_bytes_mut()[byte_idx] |= 1 << bit_idx;
2901 }
2902
2903 fn get_triggered_bits(&self) -> BitSet {
2904 let offset = self.compiled.layout.triggered_bits_offset;
2905 let size = self.compiled.layout.triggered_bits_total_size;
2906 let bytes = self.mem_bytes();
2907 let mut bs = BitSet::with_capacity(size * 8);
2908 for i in 0..size * 8 {
2909 let byte_idx = offset + i / 8;
2910 let bit_idx = i % 8;
2911 if bytes[byte_idx] & (1 << bit_idx) != 0 {
2912 bs.insert(i);
2913 }
2914 }
2915 bs
2916 }
2917}