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