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