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