1#![cfg(feature = "host-runtime")]
37
38use std::sync::Arc;
39use std::sync::mpsc;
40use std::thread::JoinHandle;
41use std::time::{Duration, Instant};
42
43use num_bigint::BigUint;
44
45use super::compile_cancel::CompileCancel;
46use super::{
47 EventHandle, MemoryLayout, RuntimeEventBuffer, SharedJitCode, SimBackend, SimulatorErrorCode,
48 memory_image::MemoryImage,
49};
50#[cfg(any(
51 target_arch = "x86_64",
52 feature = "arm64-codegen",
53 target_arch = "aarch64"
54))]
55use crate::backend::native::SharedNativeCode;
56use crate::backend::{InterpBackend, JitBackend};
57use crate::{
58 SimulatorError, SimulatorOptions,
59 ir::{AbsoluteAddr, LaidOutProgram, SignalRef},
60};
61
62pub(crate) fn native_is_default_target() -> bool {
65 #[cfg(any(
66 all(target_arch = "x86_64", not(feature = "arm64-codegen")),
67 all(target_arch = "aarch64", not(feature = "x86_64-codegen"))
68 ))]
69 {
70 true
71 }
72 #[cfg(not(any(
73 all(target_arch = "x86_64", not(feature = "arm64-codegen")),
74 all(target_arch = "aarch64", not(feature = "x86_64-codegen"))
75 )))]
76 {
77 false
78 }
79}
80
81pub(crate) fn default_target_layout_mode() -> crate::backend::memory_layout::MemoryLayoutMode {
87 if native_is_default_target() {
88 crate::backend::memory_layout::MemoryLayoutMode::ElementStrided
89 } else {
90 crate::backend::memory_layout::MemoryLayoutMode::Packed
91 }
92}
93
94pub(crate) fn tiered_layout_mode(
100 vcd_recording: bool,
101) -> crate::backend::memory_layout::MemoryLayoutMode {
102 if vcd_recording {
103 crate::backend::memory_layout::MemoryLayoutMode::Packed
104 } else {
105 default_target_layout_mode()
106 }
107}
108
109enum CompiledTier {
111 Jit(Box<JitBackend>),
112 #[cfg(any(
113 target_arch = "x86_64",
114 feature = "arm64-codegen",
115 target_arch = "aarch64"
116 ))]
117 Native(Box<crate::backend::native::NativeBackend>),
118}
119
120impl CompiledCode {
121 fn required_image_words(&self) -> usize {
124 match self {
125 CompiledCode::Cranelift(shared) => shared.layout.merged_total_size.div_ceil(8),
130 #[cfg(any(
131 target_arch = "x86_64",
132 feature = "arm64-codegen",
133 target_arch = "aarch64"
134 ))]
135 CompiledCode::Native(shared) => shared.native_memory_size.div_ceil(8) + 1,
136 }
137 }
138}
139
140impl CompiledTier {
141 fn adopt(
149 code: CompiledCode,
150 mut memory: MemoryImage,
151 runtime_event_buffer: Arc<RuntimeEventBuffer>,
152 comb_capture_enabled: Vec<u8>,
153 ) -> Self {
154 debug_assert!(memory.capacity_words() >= code.required_image_words());
155 if memory.len_words() < code.required_image_words() {
156 memory.resize_zeroed_within_capacity(code.required_image_words());
157 }
158 match code {
159 CompiledCode::Cranelift(shared) => {
160 Self::Jit(Box::new(JitBackend::adopt_shared_with_state(
161 shared,
162 memory,
163 runtime_event_buffer,
164 comb_capture_enabled,
165 )))
166 }
167 #[cfg(any(
168 target_arch = "x86_64",
169 feature = "arm64-codegen",
170 target_arch = "aarch64"
171 ))]
172 CompiledCode::Native(shared) => Self::Native(Box::new(
173 crate::backend::native::NativeBackend::adopt_shared_with_state(
174 shared,
175 memory,
176 runtime_event_buffer,
177 comb_capture_enabled,
178 ),
179 )),
180 }
181 }
182
183 fn layout(&self) -> &MemoryLayout {
184 match self {
185 Self::Jit(jit) => jit.layout(),
186 #[cfg(any(
187 target_arch = "x86_64",
188 feature = "arm64-codegen",
189 target_arch = "aarch64"
190 ))]
191 Self::Native(native) => native.layout(),
192 }
193 }
194
195 fn memory_base_mut(&mut self) -> *mut u8 {
196 match self {
197 Self::Jit(jit) => jit.memory_as_ptr().0.cast_mut(),
198 #[cfg(any(
199 target_arch = "x86_64",
200 feature = "arm64-codegen",
201 target_arch = "aarch64"
202 ))]
203 Self::Native(native) => native.memory_as_ptr().0.cast_mut(),
204 }
205 }
206
207 fn eval_comb(&mut self) -> Result<(), SimulatorErrorCode> {
208 match self {
209 Self::Jit(jit) => jit.eval_comb(),
210 #[cfg(any(
211 target_arch = "x86_64",
212 feature = "arm64-codegen",
213 target_arch = "aarch64"
214 ))]
215 Self::Native(native) => native.eval_comb(),
216 }
217 }
218
219 fn eval_apply_ff_at(&mut self, id: usize) -> Result<(), SimulatorErrorCode> {
223 match self {
224 Self::Jit(jit) => {
225 let event = jit.id_to_event_slice()[id];
226 jit.eval_apply_ff_at(event)
227 }
228 #[cfg(any(
229 target_arch = "x86_64",
230 feature = "arm64-codegen",
231 target_arch = "aarch64"
232 ))]
233 Self::Native(native) => {
234 let event = native.id_to_event_slice()[id];
235 native.eval_apply_ff_at(event)
236 }
237 }
238 }
239
240 fn eval_comb_apply_ff_at(&mut self, id: usize) -> Result<(), SimulatorErrorCode> {
241 match self {
242 Self::Jit(jit) => {
243 let event = jit.id_to_event_slice()[id];
244 jit.eval_comb_apply_ff_at(event)
245 }
246 #[cfg(any(
247 target_arch = "x86_64",
248 feature = "arm64-codegen",
249 target_arch = "aarch64"
250 ))]
251 Self::Native(native) => {
252 let event = native.id_to_event_slice()[id];
253 native.eval_comb_apply_ff_at(event)
254 }
255 }
256 }
257
258 fn eval_comb_apply_ff_many_at(
263 &mut self,
264 id: usize,
265 count: u64,
266 ) -> (u64, Result<(), SimulatorErrorCode>) {
267 match self {
268 Self::Jit(jit) => (1, {
269 let event = jit.id_to_event_slice()[id];
270 jit.eval_comb_apply_ff_at(event)
271 }),
272 #[cfg(any(
273 target_arch = "x86_64",
274 feature = "arm64-codegen",
275 target_arch = "aarch64"
276 ))]
277 Self::Native(native) => {
278 native.eval_comb_apply_ff_many_at(native.id_to_event_slice()[id], count)
279 }
280 }
281 }
282
283 fn eval_only_ff_at_addr(&mut self, addr: &AbsoluteAddr) -> Result<(), SimulatorErrorCode> {
290 match self {
291 Self::Jit(jit) => {
292 let event = jit
293 .resolve_eval_only_event(addr)
294 .unwrap_or_else(|| panic!("eval-only event not found for {addr:?}"));
295 jit.eval_only_ff_at(event)
296 }
297 #[cfg(any(
298 target_arch = "x86_64",
299 feature = "arm64-codegen",
300 target_arch = "aarch64"
301 ))]
302 Self::Native(native) => {
303 let event = native
304 .resolve_eval_only_event(addr)
305 .unwrap_or_else(|| panic!("eval-only event not found for {addr:?}"));
306 native.eval_only_ff_at(event)
307 }
308 }
309 }
310
311 fn apply_ff_at_addr(&mut self, addr: &AbsoluteAddr) -> Result<(), SimulatorErrorCode> {
314 match self {
315 Self::Jit(jit) => {
316 let event = jit
317 .resolve_apply_event(addr)
318 .unwrap_or_else(|| panic!("apply event not found for {addr:?}"));
319 jit.apply_ff_at(event)
320 }
321 #[cfg(any(
322 target_arch = "x86_64",
323 feature = "arm64-codegen",
324 target_arch = "aarch64"
325 ))]
326 Self::Native(native) => {
327 let event = native
328 .resolve_apply_event(addr)
329 .unwrap_or_else(|| panic!("apply event not found for {addr:?}"));
330 native.apply_ff_at(event)
331 }
332 }
333 }
334}
335
336pub(crate) enum CompiledCode {
339 Cranelift(Arc<SharedJitCode>),
340 #[cfg(any(
341 target_arch = "x86_64",
342 feature = "arm64-codegen",
343 target_arch = "aarch64"
344 ))]
345 Native(Arc<SharedNativeCode>),
346}
347
348#[derive(Clone, Copy, Debug)]
355pub struct TieredEventRef {
356 addr: AbsoluteAddr,
357 id: usize,
358}
359
360impl EventHandle for TieredEventRef {
361 fn id(&self) -> usize {
362 self.id
363 }
364
365 fn addr(&self) -> AbsoluteAddr {
366 self.addr
367 }
368}
369
370enum Phase {
371 Interpreting(Option<Box<InterpBackend>>),
372 Compiled(CompiledTier),
373}
374
375enum Promotion {
376 Pending(mpsc::Receiver<Result<CompiledCode, SimulatorError>>),
379 Failed(SimulatorError),
381 Disabled,
384 Promoted,
385}
386
387#[derive(Clone, Copy, Debug, PartialEq, Eq)]
389#[non_exhaustive]
390pub enum TieredExecutionTier {
391 Interpreter,
393 Compiled,
395}
396
397#[derive(Clone, Copy, Debug, PartialEq, Eq)]
399#[non_exhaustive]
400pub enum TieredPromotionStatus {
401 Pending,
403 Failed,
405 Disabled,
407 Promoted,
409}
410
411#[derive(Clone, Copy, Debug, PartialEq, Eq)]
420#[non_exhaustive]
421pub struct TieredExecutionStats {
422 pub tier: TieredExecutionTier,
424 pub promotion: TieredPromotionStatus,
426 pub interpreted_evaluations: u64,
428 pub compiled_evaluations: u64,
430 pub promoted_after_interpreted_evaluations: Option<u64>,
432 pub safe_point_polls: u64,
434 pub split_apply_deferrals: u64,
436 pub threshold_deferrals: u64,
438}
439
440#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
447pub struct TieredExecutionTiming {
448 promotion_elapsed: Option<Duration>,
449}
450
451impl TieredExecutionTiming {
452 pub fn promotion_elapsed(self) -> Option<Duration> {
454 self.promotion_elapsed
455 }
456}
457
458struct ActiveExecutionTiming {
459 started_at: Instant,
460 promotion_elapsed: Option<Duration>,
461}
462
463pub struct TieredBackend {
466 phase: Phase,
467 promotion: Promotion,
468 events: Vec<TieredEventRef>,
471 cancel: CompileCancel,
474 compiler_worker: Option<JoinHandle<()>>,
478 pending_split_applies: usize,
483 promotion_threshold: u64,
487 interpreted_steps: u64,
489 compiled_steps: u64,
491 promoted_after_interpreted_steps: Option<u64>,
493 safe_point_polls: u64,
495 split_apply_deferrals: u64,
497 threshold_deferrals: u64,
499 execution_timing: Option<ActiveExecutionTiming>,
502}
503
504impl TieredBackend {
505 pub fn new(laid_out: &LaidOutProgram, options: &SimulatorOptions) -> Self {
513 if native_is_default_target() {
514 #[cfg(any(
515 target_arch = "x86_64",
516 feature = "arm64-codegen",
517 target_arch = "aarch64"
518 ))]
519 {
520 Self::with_compiler(laid_out, options, |laid_out, options, cancel| {
521 use crate::backend::native::{NativeBackend, SharedNativeCode};
522 let wants_native_trace = options.trace.native || options.trace.mir;
526 let image = if wants_native_trace {
527 let (image, native_trace) =
528 NativeBackend::compile_image_with_cancel_and_trace(
529 laid_out, options, cancel,
530 )?;
531 let compilation = crate::debug::CompilationTrace {
532 native_optimized_sir: Some(native_trace.optimized_sir),
533 mir: Some(native_trace.mir),
534 reactive_event_graph: Some(native_trace.reactive_graph),
535 native_state_layout: Some(native_trace.state_layout),
536 ..crate::debug::CompilationTrace::default()
537 };
538 if options.trace.output_to_stdout {
539 compilation.print();
540 }
541 image
542 } else {
543 NativeBackend::compile_image_with_cancel(laid_out, options, cancel)?
544 };
545 let shared = Arc::new(unsafe { SharedNativeCode::from_image(image)? });
548 Ok(CompiledCode::Native(shared))
549 })
550 }
551 #[cfg(not(any(
552 target_arch = "x86_64",
553 feature = "arm64-codegen",
554 target_arch = "aarch64"
555 )))]
556 {
557 unreachable!("native tier selected on a host without native support")
558 }
559 } else {
560 Self::with_compiler(laid_out, options, |laid_out, options, _cancel| {
561 let mut trace = crate::debug::CompilationTrace::default();
562 let wants_codegen_trace = options.trace.pre_optimized_clif
563 || options.trace.post_optimized_clif
564 || options.trace.native;
565 let shared = Arc::new(JitBackend::compile(
566 laid_out,
567 options,
568 wants_codegen_trace.then_some(&mut trace),
569 )?);
570 if options.trace.output_to_stdout {
571 trace.print();
572 }
573 Ok(CompiledCode::Cranelift(shared))
574 })
575 }
576 }
577
578 pub(crate) fn with_compiler<F>(
585 laid_out: &LaidOutProgram,
586 options: &SimulatorOptions,
587 compile: F,
588 ) -> Self
589 where
590 F: FnOnce(
591 &LaidOutProgram,
592 &SimulatorOptions,
593 &CompileCancel,
594 ) -> Result<CompiledCode, SimulatorError>
595 + Send
596 + 'static,
597 {
598 let mut interp = Box::new(
599 InterpBackend::new(laid_out, options)
600 .expect("interpreter construction cannot fail for a laid-out program"),
601 );
602 if !matches!(
610 options.tier_promotion,
611 crate::simulator::TierPromotion::Never
612 ) {
613 let len = interp.image_word_len();
614 let slack = len.max(1024) / 8;
615 interp.reserve_image_capacity(len + slack.max(1024));
616 }
617 let events = interp
618 .id_to_event_slice()
619 .iter()
620 .map(|ev| TieredEventRef {
621 addr: ev.addr(),
622 id: ev.id(),
623 })
624 .collect();
625
626 let cancel = CompileCancel::new();
627 let worker_cancel = cancel.clone();
628 let (promotion, compiler_worker) = if matches!(
632 options.tier_promotion,
633 crate::simulator::TierPromotion::Never
634 ) {
635 (Promotion::Disabled, None)
638 } else {
639 let (sender, receiver) = mpsc::channel();
640 let background_laid_out = laid_out.clone();
641 let background_options = options.clone();
642 match std::thread::Builder::new()
643 .name("celox-jit-compile".to_string())
644 .spawn(move || {
645 let span = tracing::info_span!(
650 "celox.tiered.compile",
651 target = if native_is_default_target() {
652 "native"
653 } else {
654 "cranelift"
655 }
656 );
657 let result = span.in_scope(|| {
658 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
659 compile(&background_laid_out, &background_options, &worker_cancel)
660 }))
661 .unwrap_or_else(|_| {
662 Err(SimulatorError::from(crate::RuntimeErrorCode::InternalError))
663 });
664 match &result {
665 Ok(_) => tracing::info!("tiered background compilation completed"),
666 Err(error) => tracing::warn!(
667 error = %error,
668 "tiered background compilation failed"
669 ),
670 }
671 result
672 });
673 let _ = sender.send(result);
674 }) {
675 Ok(handle) => (Promotion::Pending(receiver), Some(handle)),
676 Err(error) => (
677 Promotion::Failed(SimulatorError::new(crate::SimulatorErrorKind::Codegen(
678 crate::CodegenError::message(format!(
679 "failed to spawn the background compiler thread: {error}"
680 )),
681 ))),
682 None,
683 ),
684 }
685 };
686
687 let promotion_threshold = match options.tier_promotion {
688 crate::simulator::TierPromotion::AfterSteps(steps) => steps,
689 _ => 0,
690 };
691
692 Self {
693 phase: Phase::Interpreting(Some(interp)),
694 promotion,
695 events,
696 cancel,
697 compiler_worker,
698 pending_split_applies: 0,
699 promotion_threshold,
700 interpreted_steps: 0,
701 compiled_steps: 0,
702 promoted_after_interpreted_steps: None,
703 safe_point_polls: 0,
704 split_apply_deferrals: 0,
705 threshold_deferrals: 0,
706 execution_timing: None,
707 }
708 }
709
710 pub fn is_compiled(&self) -> bool {
712 matches!(self.phase, Phase::Compiled(_))
713 }
714
715 pub fn execution_stats(&self) -> TieredExecutionStats {
717 TieredExecutionStats {
718 tier: if self.is_compiled() {
719 TieredExecutionTier::Compiled
720 } else {
721 TieredExecutionTier::Interpreter
722 },
723 promotion: match &self.promotion {
724 Promotion::Pending(_) => TieredPromotionStatus::Pending,
725 Promotion::Failed(_) => TieredPromotionStatus::Failed,
726 Promotion::Disabled => TieredPromotionStatus::Disabled,
727 Promotion::Promoted => TieredPromotionStatus::Promoted,
728 },
729 interpreted_evaluations: self.interpreted_steps,
730 compiled_evaluations: self.compiled_steps,
731 promoted_after_interpreted_evaluations: self.promoted_after_interpreted_steps,
732 safe_point_polls: self.safe_point_polls,
733 split_apply_deferrals: self.split_apply_deferrals,
734 threshold_deferrals: self.threshold_deferrals,
735 }
736 }
737
738 pub fn start_execution_timing(&mut self) {
740 self.execution_timing = Some(ActiveExecutionTiming {
741 started_at: Instant::now(),
742 promotion_elapsed: self.is_compiled().then_some(Duration::ZERO),
743 });
744 }
745
746 pub fn finish_execution_timing(&mut self) -> Option<TieredExecutionTiming> {
748 self.execution_timing
749 .take()
750 .map(|timing| TieredExecutionTiming {
751 promotion_elapsed: timing.promotion_elapsed,
752 })
753 }
754
755 pub fn promotion_error(&self) -> Option<&SimulatorError> {
760 match &self.promotion {
761 Promotion::Failed(error) => Some(error),
762 _ => None,
763 }
764 }
765
766 pub fn cancel_background_compilation(&mut self) -> bool {
777 let pending = matches!(self.promotion, Promotion::Pending(_));
778 self.cancel.cancel();
779 if pending {
780 self.promotion = Promotion::Failed(super::compile_cancel::cancelled_error());
781 }
782 pending
783 }
784
785 fn count_evaluation_for_current_tier(&mut self) {
789 if matches!(self.phase, Phase::Interpreting(Some(_))) {
790 self.interpreted_steps = self.interpreted_steps.saturating_add(1);
791 } else if matches!(self.phase, Phase::Compiled(_)) {
792 self.compiled_steps = self.compiled_steps.saturating_add(1);
793 }
794 }
795
796 fn maybe_promote(&mut self) {
801 if !matches!(self.phase, Phase::Interpreting(Some(_))) {
802 return;
803 }
804 self.safe_point_polls = self.safe_point_polls.saturating_add(1);
805 if self.pending_split_applies > 0 {
809 self.split_apply_deferrals = self.split_apply_deferrals.saturating_add(1);
810 return;
811 }
812 if self.interpreted_steps < self.promotion_threshold {
815 self.threshold_deferrals = self.threshold_deferrals.saturating_add(1);
816 return;
817 }
818 let Promotion::Pending(receiver) = &self.promotion else {
819 return;
820 };
821 if self.cancel.is_cancelled() {
824 self.promotion = Promotion::Failed(super::compile_cancel::cancelled_error());
825 return;
826 }
827 let Ok(result) = receiver.try_recv() else {
828 return;
829 };
830
831 let Ok(code) = result else {
832 self.promotion = match result {
835 Err(error) => Promotion::Failed(error),
836 Ok(_) => unreachable!("checked above"),
837 };
838 return;
839 };
840 if let Phase::Interpreting(Some(interp)) = &mut self.phase {
846 let required = code.required_image_words();
847 if interp.image_word_capacity() < required {
848 self.promotion = Promotion::Failed(SimulatorError::new(
849 crate::SimulatorErrorKind::Codegen(crate::CodegenError::message(format!(
850 "native image needs {required} words but the reserved interpreter \
851 image only has {} words of capacity; refusing to move the live \
852 memory image and staying interpreted",
853 interp.image_word_capacity()
854 ))),
855 ));
856 return;
857 }
858 }
859 let mut adopted = None;
860 if let Phase::Interpreting(slot) = &mut self.phase {
861 if let Some(mut interp) = slot.take() {
862 let (memory, runtime_event_buffer, comb_capture_enabled) = interp.tier_transfer();
863 drop(interp);
864 adopted = Some(CompiledTier::adopt(
865 code,
866 memory,
867 runtime_event_buffer,
868 comb_capture_enabled,
869 ));
870 }
871 }
872 if let Some(mut compiled) = adopted {
873 let promotion_span = tracing::info_span!(
874 "celox.tiered.promote",
875 interpreted_evaluations = self.interpreted_steps
876 );
877 let _entered = promotion_span.enter();
878 let sparse_metas: Vec<_> = {
882 let layout = compiled.layout();
883 layout
884 .sparse_layouts
885 .values()
886 .map(|s| {
887 (
888 s.dirty_words_offset,
889 s.dirty_word_count * 8,
890 s.summary_words_offset,
891 s.summary_word_count * 8,
892 )
893 })
894 .collect()
895 };
896 unsafe {
897 let base = compiled.memory_base_mut();
898 for &(dwo, dwc, swo, swc) in &sparse_metas {
899 std::ptr::write_bytes(base.add(dwo), 0, dwc);
900 std::ptr::write_bytes(base.add(swo), 0, swc);
901 }
902 }
903 self.phase = Phase::Compiled(compiled);
904 self.promotion = Promotion::Promoted;
905 self.promoted_after_interpreted_steps = Some(self.interpreted_steps);
906 if let Some(timing) = &mut self.execution_timing
907 && timing.promotion_elapsed.is_none()
908 {
909 timing.promotion_elapsed = Some(timing.started_at.elapsed());
910 }
911 tracing::info!("tiered backend adopted generated code");
912 }
913 }
914}
915
916impl Drop for TieredBackend {
917 fn drop(&mut self) {
918 self.cancel.cancel();
919 if let Some(worker) = self.compiler_worker.take() {
920 let _ = worker.join();
921 }
922 }
923}
924
925impl SimBackend for TieredBackend {
926 type Event = TieredEventRef;
927
928 fn eval_comb(&mut self) -> Result<(), SimulatorErrorCode> {
929 self.maybe_promote();
930 self.count_evaluation_for_current_tier();
931 match &mut self.phase {
932 Phase::Interpreting(Some(interp)) => interp.eval_comb(),
933 Phase::Compiled(compiled) => compiled.eval_comb(),
934 Phase::Interpreting(None) => unreachable!("promoted backend left no interpreter"),
935 }
936 }
937
938 fn eval_apply_ff_at(&mut self, event: TieredEventRef) -> Result<(), SimulatorErrorCode> {
939 self.maybe_promote();
940 self.count_evaluation_for_current_tier();
941 match &mut self.phase {
942 Phase::Interpreting(Some(interp)) => interp.eval_apply_ff_at(
943 super::interp::InterpEventRef::from_parts(event.addr(), event.id()),
944 ),
945 Phase::Compiled(compiled) => compiled.eval_apply_ff_at(event.id()),
946 Phase::Interpreting(None) => unreachable!("promoted backend left no interpreter"),
947 }
948 }
949
950 fn eval_comb_apply_ff_at(&mut self, event: TieredEventRef) -> Result<(), SimulatorErrorCode> {
951 self.maybe_promote();
952 self.count_evaluation_for_current_tier();
953 match &mut self.phase {
954 Phase::Interpreting(Some(interp)) => interp.eval_comb_apply_ff_at(
955 super::interp::InterpEventRef::from_parts(event.addr(), event.id()),
956 ),
957 Phase::Compiled(compiled) => compiled.eval_comb_apply_ff_at(event.id()),
958 Phase::Interpreting(None) => unreachable!("promoted backend left no interpreter"),
959 }
960 }
961
962 fn eval_comb_apply_ff_many_at(
963 &mut self,
964 event: TieredEventRef,
965 count: u64,
966 ) -> (u64, Result<(), SimulatorErrorCode>) {
967 if count == 0 {
970 return (0, Ok(()));
971 }
972 self.maybe_promote();
973 match &mut self.phase {
974 Phase::Interpreting(Some(interp)) => {
975 self.interpreted_steps = self.interpreted_steps.saturating_add(1);
976 (
977 1,
978 interp.eval_comb_apply_ff_at(super::interp::InterpEventRef::from_parts(
979 event.addr(),
980 event.id(),
981 )),
982 )
983 }
984 Phase::Compiled(compiled) => {
985 let (completed, result) = compiled.eval_comb_apply_ff_many_at(event.id(), count);
986 self.compiled_steps = self.compiled_steps.saturating_add(completed);
987 (completed, result)
988 }
989 Phase::Interpreting(None) => unreachable!("promoted backend left no interpreter"),
990 }
991 }
992
993 fn eval_only_ff_at(&mut self, event: TieredEventRef) -> Result<(), SimulatorErrorCode> {
994 self.maybe_promote();
995 self.count_evaluation_for_current_tier();
996 let result = match &mut self.phase {
997 Phase::Interpreting(Some(interp)) => interp.eval_only_ff_at(
998 super::interp::InterpEventRef::from_parts(event.addr(), event.id()),
999 ),
1000 Phase::Compiled(compiled) => compiled.eval_only_ff_at_addr(&event.addr()),
1001 Phase::Interpreting(None) => unreachable!("promoted backend left no interpreter"),
1002 };
1003 self.pending_split_applies = self.pending_split_applies.saturating_add(1);
1006 result
1007 }
1008
1009 fn apply_ff_at(&mut self, event: TieredEventRef) -> Result<(), SimulatorErrorCode> {
1010 self.maybe_promote();
1012 self.count_evaluation_for_current_tier();
1013 let result = match &mut self.phase {
1014 Phase::Interpreting(Some(interp)) => interp.apply_ff_at(
1015 super::interp::InterpEventRef::from_parts(event.addr(), event.id()),
1016 ),
1017 Phase::Compiled(compiled) => compiled.apply_ff_at_addr(&event.addr()),
1018 Phase::Interpreting(None) => unreachable!("promoted backend left no interpreter"),
1019 };
1020 self.pending_split_applies = self.pending_split_applies.saturating_sub(1);
1021 result
1022 }
1023
1024 fn resolve_signal(&self, addr: &AbsoluteAddr) -> SignalRef {
1025 match &self.phase {
1026 Phase::Interpreting(Some(interp)) => interp.resolve_signal(addr),
1027 Phase::Compiled(CompiledTier::Jit(jit)) => jit.resolve_signal(addr),
1028 #[cfg(any(
1029 target_arch = "x86_64",
1030 feature = "arm64-codegen",
1031 target_arch = "aarch64"
1032 ))]
1033 Phase::Compiled(CompiledTier::Native(native)) => native.resolve_signal(addr),
1034 Phase::Interpreting(None) => unreachable!("promoted backend left no interpreter"),
1035 }
1036 }
1037
1038 fn resolve_event(&self, addr: &AbsoluteAddr) -> TieredEventRef {
1039 match &self.phase {
1040 Phase::Interpreting(Some(interp)) => {
1041 let ev = interp.resolve_event(addr);
1042 TieredEventRef {
1043 addr: ev.addr(),
1044 id: ev.id(),
1045 }
1046 }
1047 Phase::Compiled(CompiledTier::Jit(jit)) => {
1048 let ev = jit.resolve_event(addr);
1049 TieredEventRef {
1050 addr: ev.addr(),
1051 id: ev.id(),
1052 }
1053 }
1054 #[cfg(any(
1055 target_arch = "x86_64",
1056 feature = "arm64-codegen",
1057 target_arch = "aarch64"
1058 ))]
1059 Phase::Compiled(CompiledTier::Native(native)) => {
1060 let ev = native.resolve_event(addr);
1061 TieredEventRef {
1062 addr: ev.addr(),
1063 id: ev.id(),
1064 }
1065 }
1066 Phase::Interpreting(None) => unreachable!("promoted backend left no interpreter"),
1067 }
1068 }
1069
1070 fn resolve_event_opt(&self, addr: &AbsoluteAddr) -> Option<TieredEventRef> {
1071 match &self.phase {
1072 Phase::Interpreting(Some(interp)) => {
1073 interp.resolve_event_opt(addr).map(|ev| TieredEventRef {
1074 addr: ev.addr(),
1075 id: ev.id(),
1076 })
1077 }
1078 Phase::Compiled(CompiledTier::Jit(jit)) => {
1079 jit.resolve_event_opt(addr).map(|ev| TieredEventRef {
1080 addr: ev.addr(),
1081 id: ev.id(),
1082 })
1083 }
1084 #[cfg(any(
1085 target_arch = "x86_64",
1086 feature = "arm64-codegen",
1087 target_arch = "aarch64"
1088 ))]
1089 Phase::Compiled(CompiledTier::Native(native)) => {
1090 native.resolve_event_opt(addr).map(|ev| TieredEventRef {
1091 addr: ev.addr(),
1092 id: ev.id(),
1093 })
1094 }
1095 Phase::Interpreting(None) => unreachable!("promoted backend left no interpreter"),
1096 }
1097 }
1098
1099 fn resolve_eval_only_event(&self, addr: &AbsoluteAddr) -> Option<TieredEventRef> {
1100 match &self.phase {
1101 Phase::Interpreting(Some(interp)) => {
1102 interp
1103 .resolve_eval_only_event(addr)
1104 .map(|ev| TieredEventRef {
1105 addr: ev.addr(),
1106 id: ev.id(),
1107 })
1108 }
1109 Phase::Compiled(CompiledTier::Jit(jit)) => {
1110 jit.resolve_eval_only_event(addr).map(|ev| TieredEventRef {
1111 addr: ev.addr(),
1112 id: ev.id(),
1113 })
1114 }
1115 #[cfg(any(
1116 target_arch = "x86_64",
1117 feature = "arm64-codegen",
1118 target_arch = "aarch64"
1119 ))]
1120 Phase::Compiled(CompiledTier::Native(native)) => native
1121 .resolve_eval_only_event(addr)
1122 .map(|ev| TieredEventRef {
1123 addr: ev.addr(),
1124 id: ev.id(),
1125 }),
1126 Phase::Interpreting(None) => unreachable!("promoted backend left no interpreter"),
1127 }
1128 }
1129
1130 fn resolve_apply_event(&self, addr: &AbsoluteAddr) -> Option<TieredEventRef> {
1131 match &self.phase {
1132 Phase::Interpreting(Some(interp)) => {
1133 interp.resolve_apply_event(addr).map(|ev| TieredEventRef {
1134 addr: ev.addr(),
1135 id: ev.id(),
1136 })
1137 }
1138 Phase::Compiled(CompiledTier::Jit(jit)) => {
1139 jit.resolve_apply_event(addr).map(|ev| TieredEventRef {
1140 addr: ev.addr(),
1141 id: ev.id(),
1142 })
1143 }
1144 #[cfg(any(
1145 target_arch = "x86_64",
1146 feature = "arm64-codegen",
1147 target_arch = "aarch64"
1148 ))]
1149 Phase::Compiled(CompiledTier::Native(native)) => {
1150 native.resolve_apply_event(addr).map(|ev| TieredEventRef {
1151 addr: ev.addr(),
1152 id: ev.id(),
1153 })
1154 }
1155 Phase::Interpreting(None) => unreachable!("promoted backend left no interpreter"),
1156 }
1157 }
1158
1159 fn set<T: Copy>(&mut self, signal: SignalRef, value: T) {
1160 match &mut self.phase {
1161 Phase::Interpreting(Some(interp)) => interp.set(signal, value),
1162 Phase::Compiled(CompiledTier::Jit(jit)) => jit.set(signal, value),
1163 #[cfg(any(
1164 target_arch = "x86_64",
1165 feature = "arm64-codegen",
1166 target_arch = "aarch64"
1167 ))]
1168 Phase::Compiled(CompiledTier::Native(native)) => native.set(signal, value),
1169 Phase::Interpreting(None) => unreachable!("promoted backend left no interpreter"),
1170 }
1171 }
1172
1173 fn set_wide(&mut self, signal: SignalRef, value: BigUint) {
1174 match &mut self.phase {
1175 Phase::Interpreting(Some(interp)) => interp.set_wide(signal, value),
1176 Phase::Compiled(CompiledTier::Jit(jit)) => jit.set_wide(signal, value),
1177 #[cfg(any(
1178 target_arch = "x86_64",
1179 feature = "arm64-codegen",
1180 target_arch = "aarch64"
1181 ))]
1182 Phase::Compiled(CompiledTier::Native(native)) => native.set_wide(signal, value),
1183 Phase::Interpreting(None) => unreachable!("promoted backend left no interpreter"),
1184 }
1185 }
1186
1187 fn set_four_state(&mut self, signal: SignalRef, value: BigUint, mask: BigUint) {
1188 match &mut self.phase {
1189 Phase::Interpreting(Some(interp)) => interp.set_four_state(signal, value, mask),
1190 Phase::Compiled(CompiledTier::Jit(jit)) => jit.set_four_state(signal, value, mask),
1191 #[cfg(any(
1192 target_arch = "x86_64",
1193 feature = "arm64-codegen",
1194 target_arch = "aarch64"
1195 ))]
1196 Phase::Compiled(CompiledTier::Native(native)) => {
1197 native.set_four_state(signal, value, mask)
1198 }
1199 Phase::Interpreting(None) => unreachable!("promoted backend left no interpreter"),
1200 }
1201 }
1202
1203 fn get(&self, signal: SignalRef) -> BigUint {
1204 match &self.phase {
1205 Phase::Interpreting(Some(interp)) => interp.get(signal),
1206 Phase::Compiled(CompiledTier::Jit(jit)) => jit.get(signal),
1207 #[cfg(any(
1208 target_arch = "x86_64",
1209 feature = "arm64-codegen",
1210 target_arch = "aarch64"
1211 ))]
1212 Phase::Compiled(CompiledTier::Native(native)) => native.get(signal),
1213 Phase::Interpreting(None) => unreachable!("promoted backend left no interpreter"),
1214 }
1215 }
1216
1217 fn get_as<T: Default + Copy>(&self, signal: SignalRef) -> T {
1218 match &self.phase {
1219 Phase::Interpreting(Some(interp)) => interp.get_as(signal),
1220 Phase::Compiled(CompiledTier::Jit(jit)) => jit.get_as(signal),
1221 #[cfg(any(
1222 target_arch = "x86_64",
1223 feature = "arm64-codegen",
1224 target_arch = "aarch64"
1225 ))]
1226 Phase::Compiled(CompiledTier::Native(native)) => native.get_as(signal),
1227 Phase::Interpreting(None) => unreachable!("promoted backend left no interpreter"),
1228 }
1229 }
1230
1231 fn get_four_state(&self, signal: SignalRef) -> (BigUint, BigUint) {
1232 match &self.phase {
1233 Phase::Interpreting(Some(interp)) => interp.get_four_state(signal),
1234 Phase::Compiled(CompiledTier::Jit(jit)) => jit.get_four_state(signal),
1235 #[cfg(any(
1236 target_arch = "x86_64",
1237 feature = "arm64-codegen",
1238 target_arch = "aarch64"
1239 ))]
1240 Phase::Compiled(CompiledTier::Native(native)) => native.get_four_state(signal),
1241 Phase::Interpreting(None) => unreachable!("promoted backend left no interpreter"),
1242 }
1243 }
1244
1245 fn memory_as_ptr(&self) -> (*const u8, usize) {
1246 match &self.phase {
1247 Phase::Interpreting(Some(interp)) => interp.memory_as_ptr(),
1248 Phase::Compiled(CompiledTier::Jit(jit)) => jit.memory_as_ptr(),
1249 #[cfg(any(
1250 target_arch = "x86_64",
1251 feature = "arm64-codegen",
1252 target_arch = "aarch64"
1253 ))]
1254 Phase::Compiled(CompiledTier::Native(native)) => native.memory_as_ptr(),
1255 Phase::Interpreting(None) => unreachable!("promoted backend left no interpreter"),
1256 }
1257 }
1258
1259 fn memory_as_mut_ptr(&mut self) -> (*mut u8, usize) {
1260 match &mut self.phase {
1261 Phase::Interpreting(Some(interp)) => interp.memory_as_mut_ptr(),
1262 Phase::Compiled(CompiledTier::Jit(jit)) => jit.memory_as_mut_ptr(),
1263 #[cfg(any(
1264 target_arch = "x86_64",
1265 feature = "arm64-codegen",
1266 target_arch = "aarch64"
1267 ))]
1268 Phase::Compiled(CompiledTier::Native(native)) => native.memory_as_mut_ptr(),
1269 Phase::Interpreting(None) => unreachable!("promoted backend left no interpreter"),
1270 }
1271 }
1272
1273 fn memory_owner(&self) -> Option<Arc<dyn std::any::Any + Send + Sync>> {
1274 match &self.phase {
1275 Phase::Interpreting(Some(interp)) => interp.memory_owner(),
1276 Phase::Compiled(CompiledTier::Jit(jit)) => jit.memory_owner(),
1277 #[cfg(any(
1278 target_arch = "x86_64",
1279 feature = "arm64-codegen",
1280 target_arch = "aarch64"
1281 ))]
1282 Phase::Compiled(CompiledTier::Native(native)) => native.memory_owner(),
1283 Phase::Interpreting(None) => unreachable!("promoted backend left no interpreter"),
1284 }
1285 }
1286
1287 fn vcd_tracking_enabled(&self) -> bool {
1288 match &self.phase {
1289 Phase::Interpreting(Some(interp)) => interp.vcd_tracking_enabled(),
1290 Phase::Compiled(CompiledTier::Jit(jit)) => jit.vcd_tracking_enabled(),
1291 #[cfg(any(
1292 target_arch = "x86_64",
1293 feature = "arm64-codegen",
1294 target_arch = "aarch64"
1295 ))]
1296 Phase::Compiled(CompiledTier::Native(native)) => native.vcd_tracking_enabled(),
1297 Phase::Interpreting(None) => unreachable!("promoted backend left no interpreter"),
1298 }
1299 }
1300
1301 fn runtime_event_buffer_as_ptr(&self) -> (*const u8, usize) {
1302 match &self.phase {
1303 Phase::Interpreting(Some(interp)) => interp.runtime_event_buffer_as_ptr(),
1304 Phase::Compiled(CompiledTier::Jit(jit)) => jit.runtime_event_buffer_as_ptr(),
1305 #[cfg(any(
1306 target_arch = "x86_64",
1307 feature = "arm64-codegen",
1308 target_arch = "aarch64"
1309 ))]
1310 Phase::Compiled(CompiledTier::Native(native)) => native.runtime_event_buffer_as_ptr(),
1311 Phase::Interpreting(None) => unreachable!("promoted backend left no interpreter"),
1312 }
1313 }
1314
1315 fn runtime_event_buffer(&self) -> Option<Arc<RuntimeEventBuffer>> {
1316 match &self.phase {
1317 Phase::Interpreting(Some(interp)) => interp.runtime_event_buffer(),
1318 Phase::Compiled(CompiledTier::Jit(jit)) => Some(jit.runtime_event_buffer().clone()),
1319 #[cfg(any(
1320 target_arch = "x86_64",
1321 feature = "arm64-codegen",
1322 target_arch = "aarch64"
1323 ))]
1324 Phase::Compiled(CompiledTier::Native(native)) => native.runtime_event_buffer(),
1325 Phase::Interpreting(None) => unreachable!("promoted backend left no interpreter"),
1326 }
1327 }
1328
1329 fn set_comb_capture_event_enabled(&mut self, active_sites: &[bool]) {
1330 match &mut self.phase {
1331 Phase::Interpreting(Some(interp)) => {
1332 interp.set_comb_capture_event_enabled(active_sites)
1333 }
1334 Phase::Compiled(CompiledTier::Jit(jit)) => {
1335 jit.set_comb_capture_event_enabled(active_sites)
1336 }
1337 #[cfg(any(
1338 target_arch = "x86_64",
1339 feature = "arm64-codegen",
1340 target_arch = "aarch64"
1341 ))]
1342 Phase::Compiled(CompiledTier::Native(native)) => {
1343 native.set_comb_capture_event_enabled(active_sites)
1344 }
1345 Phase::Interpreting(None) => unreachable!("promoted backend left no interpreter"),
1346 }
1347 }
1348
1349 fn stable_region_size(&self) -> usize {
1350 match &self.phase {
1351 Phase::Interpreting(Some(interp)) => interp.stable_region_size(),
1352 Phase::Compiled(CompiledTier::Jit(jit)) => jit.stable_region_size(),
1353 #[cfg(any(
1354 target_arch = "x86_64",
1355 feature = "arm64-codegen",
1356 target_arch = "aarch64"
1357 ))]
1358 Phase::Compiled(CompiledTier::Native(native)) => native.stable_region_size(),
1359 Phase::Interpreting(None) => unreachable!("promoted backend left no interpreter"),
1360 }
1361 }
1362
1363 fn layout(&self) -> &MemoryLayout {
1364 match &self.phase {
1365 Phase::Interpreting(Some(interp)) => interp.layout(),
1366 Phase::Compiled(CompiledTier::Jit(jit)) => jit.layout(),
1367 #[cfg(any(
1368 target_arch = "x86_64",
1369 feature = "arm64-codegen",
1370 target_arch = "aarch64"
1371 ))]
1372 Phase::Compiled(CompiledTier::Native(native)) => native.layout(),
1373 Phase::Interpreting(None) => unreachable!("promoted backend left no interpreter"),
1374 }
1375 }
1376
1377 fn id_to_addr_slice(&self) -> &[AbsoluteAddr] {
1378 match &self.phase {
1379 Phase::Interpreting(Some(interp)) => interp.id_to_addr_slice(),
1380 Phase::Compiled(CompiledTier::Jit(jit)) => jit.id_to_addr_slice(),
1381 #[cfg(any(
1382 target_arch = "x86_64",
1383 feature = "arm64-codegen",
1384 target_arch = "aarch64"
1385 ))]
1386 Phase::Compiled(CompiledTier::Native(native)) => native.id_to_addr_slice(),
1387 Phase::Interpreting(None) => unreachable!("promoted backend left no interpreter"),
1388 }
1389 }
1390
1391 fn id_to_event_slice(&self) -> &[TieredEventRef] {
1392 &self.events
1393 }
1394
1395 fn num_events(&self) -> usize {
1396 match &self.phase {
1397 Phase::Interpreting(Some(interp)) => interp.num_events(),
1398 Phase::Compiled(CompiledTier::Jit(jit)) => jit.num_events(),
1399 #[cfg(any(
1400 target_arch = "x86_64",
1401 feature = "arm64-codegen",
1402 target_arch = "aarch64"
1403 ))]
1404 Phase::Compiled(CompiledTier::Native(native)) => native.num_events(),
1405 Phase::Interpreting(None) => unreachable!("promoted backend left no interpreter"),
1406 }
1407 }
1408
1409 fn clear_triggered_bits(&mut self) {
1410 match &mut self.phase {
1411 Phase::Interpreting(Some(interp)) => interp.clear_triggered_bits(),
1412 Phase::Compiled(CompiledTier::Jit(jit)) => jit.clear_triggered_bits(),
1413 #[cfg(any(
1414 target_arch = "x86_64",
1415 feature = "arm64-codegen",
1416 target_arch = "aarch64"
1417 ))]
1418 Phase::Compiled(CompiledTier::Native(native)) => native.clear_triggered_bits(),
1419 Phase::Interpreting(None) => unreachable!("promoted backend left no interpreter"),
1420 }
1421 }
1422
1423 fn mark_triggered_bit(&mut self, id: usize) {
1424 match &mut self.phase {
1425 Phase::Interpreting(Some(interp)) => interp.mark_triggered_bit(id),
1426 Phase::Compiled(CompiledTier::Jit(jit)) => jit.mark_triggered_bit(id),
1427 #[cfg(any(
1428 target_arch = "x86_64",
1429 feature = "arm64-codegen",
1430 target_arch = "aarch64"
1431 ))]
1432 Phase::Compiled(CompiledTier::Native(native)) => native.mark_triggered_bit(id),
1433 Phase::Interpreting(None) => unreachable!("promoted backend left no interpreter"),
1434 }
1435 }
1436
1437 fn get_triggered_bits(&self) -> bit_set::BitSet {
1438 match &self.phase {
1439 Phase::Interpreting(Some(interp)) => interp.get_triggered_bits(),
1440 Phase::Compiled(CompiledTier::Jit(jit)) => jit.get_triggered_bits(),
1441 #[cfg(any(
1442 target_arch = "x86_64",
1443 feature = "arm64-codegen",
1444 target_arch = "aarch64"
1445 ))]
1446 Phase::Compiled(CompiledTier::Native(native)) => native.get_triggered_bits(),
1447 Phase::Interpreting(None) => unreachable!("promoted backend left no interpreter"),
1448 }
1449 }
1450}
1451
1452#[cfg(test)]
1453mod tests {
1454 use super::*;
1455 use crate::backend::native::NativeBackend;
1456 use crate::{CodegenError, RuntimeEvent, Simulator, SimulatorBuilder, SimulatorErrorKind};
1457 use std::sync::atomic::{AtomicBool, Ordering};
1458
1459 const PIPELINE: &str = r#"
1461module Top (
1462 clk: input clock,
1463 rst: input reset,
1464 d: input logic<8>,
1465 q: output logic<8>,
1466) {
1467 var stage1: logic<8>;
1468 var stage2: logic<8>;
1469 always_ff (clk, rst) {
1470 if_reset {
1471 stage1 = 0;
1472 stage2 = 0;
1473 } else {
1474 stage1 = d;
1475 stage2 = stage1;
1476 }
1477 }
1478 assign q = stage2;
1479}
1480"#;
1481
1482 struct Gate(Arc<AtomicBool>);
1483
1484 impl Gate {
1485 fn closed() -> Self {
1486 Self(Arc::new(AtomicBool::new(false)))
1487 }
1488
1489 fn open(&self) {
1490 self.0.store(true, Ordering::SeqCst);
1491 }
1492 }
1493
1494 fn wait_for_gate_or_cancel(
1495 gate: &AtomicBool,
1496 cancel: &CompileCancel,
1497 ) -> Result<(), SimulatorError> {
1498 while !gate.load(Ordering::Acquire) {
1499 if cancel.is_cancelled() {
1500 return Err(super::super::compile_cancel::cancelled_error());
1501 }
1502 std::thread::sleep(std::time::Duration::from_millis(1));
1503 }
1504 Ok(())
1505 }
1506
1507 fn build_gated(gate: &Gate) -> Simulator<TieredBackend> {
1511 let worker_gate = gate.0.clone();
1512 SimulatorBuilder::<Simulator>::new(PIPELINE, "Top")
1513 .build_tiered_with_compiler(move |laid_out, options, cancel| {
1514 wait_for_gate_or_cancel(&worker_gate, cancel)?;
1515 {
1516 let image = NativeBackend::compile_image(laid_out, options)?;
1517 let shared = unsafe { SharedNativeCode::from_image(image)? };
1518 Ok(CompiledCode::Native(Arc::new(shared)))
1519 }
1520 })
1521 .unwrap()
1522 }
1523
1524 fn drive(sim: &mut Simulator<TieredBackend>, inputs: &[u8]) -> Vec<u8> {
1529 let clk = sim.event("clk");
1530 let rst = sim.signal("rst");
1531 let d = sim.signal("d");
1532
1533 sim.modify(|io| {
1534 io.set(rst, 0u8);
1535 io.set(d, inputs[0]);
1536 })
1537 .unwrap();
1538 sim.tick(clk).unwrap();
1539 sim.tick(clk).unwrap();
1540 sim.modify(|io| io.set(rst, 1u8)).unwrap();
1541
1542 let mut observed = Vec::new();
1543 for &value in inputs {
1544 sim.modify(|io| io.set(d, value)).unwrap();
1545 sim.tick(clk).unwrap();
1546 sim.tick(clk).unwrap();
1547 observed.push(sim.get_as::<u8>(sim.signal("q")));
1548 }
1549 observed
1550 }
1551
1552 fn reference_outputs(inputs: &[u8]) -> Vec<u8> {
1553 inputs.to_vec()
1557 }
1558
1559 #[test]
1560 fn unpacked_array_works_across_promotion_on_strided_layout() {
1561 let code = r#"
1565module Top (
1566 clk: input clock,
1567 we: input logic,
1568 waddr: input logic<3>,
1569 wdata: input logic<8>,
1570 raddr: input logic<3>,
1571 q: output logic<8>,
1572) {
1573 var mem: logic<8>[8];
1574 always_ff (clk) {
1575 if we {
1576 mem[waddr] = wdata;
1577 }
1578 }
1579 assign q = mem[raddr];
1580}
1581"#;
1582 let gate = Gate::closed();
1583 let worker_gate = gate.0.clone();
1584 let mut sim: Simulator<TieredBackend> = SimulatorBuilder::<Simulator>::new(code, "Top")
1585 .build_tiered_with_compiler(move |laid_out, options, cancel| {
1586 wait_for_gate_or_cancel(&worker_gate, cancel)?;
1587 {
1588 let image = NativeBackend::compile_image(laid_out, options)?;
1589 let shared = unsafe { SharedNativeCode::from_image(image)? };
1590 Ok(CompiledCode::Native(Arc::new(shared)))
1591 }
1592 })
1593 .unwrap();
1594 let clk = sim.event("clk");
1595 let we = sim.signal("we");
1596 let waddr = sim.signal("waddr");
1597 let wdata = sim.signal("wdata");
1598 let raddr = sim.signal("raddr");
1599 let q = sim.signal("q");
1600
1601 for i in 0..8u8 {
1603 sim.modify(|io| {
1604 io.set(we, 1u8);
1605 io.set(waddr, i);
1606 io.set(wdata, i * 7 + 1);
1607 })
1608 .unwrap();
1609 sim.tick(clk).unwrap();
1610 }
1611 sim.modify(|io| io.set(we, 0u8)).unwrap();
1612
1613 let read_back = |sim: &mut Simulator<TieredBackend>, index: u8| -> u8 {
1614 sim.modify(|io| io.set(raddr, index)).unwrap();
1615 sim.get_as::<u8>(q)
1616 };
1617
1618 for i in 0..8u8 {
1620 assert_eq!(read_back(&mut sim, i), i * 7 + 1);
1621 }
1622
1623 gate.open();
1625 let deadline = std::time::Instant::now() + std::time::Duration::from_secs(60);
1626 while !sim.is_compiled() && std::time::Instant::now() < deadline {
1627 sim.tick(clk).unwrap();
1628 std::thread::sleep(std::time::Duration::from_millis(1));
1629 }
1630 assert!(sim.is_compiled());
1631
1632 for i in 0..8u8 {
1634 assert_eq!(read_back(&mut sim, i), i * 7 + 1, "element {i}");
1635 }
1636
1637 sim.modify(|io| {
1639 io.set(we, 1u8);
1640 io.set(waddr, 3u8);
1641 io.set(wdata, 250u8);
1642 })
1643 .unwrap();
1644 sim.tick(clk).unwrap();
1645 sim.modify(|io| io.set(we, 0u8)).unwrap();
1646 assert_eq!(read_back(&mut sim, 3), 250u8);
1647 }
1648
1649 #[test]
1654 fn four_state_unpacked_array_planes_initialize_and_survive_promotion() {
1655 let code = r#"
1656module Top (
1657 clk: input clock,
1658 we: input logic,
1659 waddr: input logic<2>,
1660 wdata: input logic<8>,
1661 raddr: input logic<2>,
1662 q: output logic<8>,
1663) {
1664 var mem: logic<8>[4];
1665 always_ff (clk) {
1666 if we {
1667 mem[waddr] = wdata;
1668 }
1669 }
1670 assign q = mem[raddr];
1671}
1672"#;
1673 let gate = Gate::closed();
1674 let worker_gate = gate.0.clone();
1675 let mut sim: Simulator<TieredBackend> = SimulatorBuilder::<Simulator>::new(code, "Top")
1676 .four_state(true)
1677 .build_tiered_with_compiler(move |laid_out, options, cancel| {
1678 wait_for_gate_or_cancel(&worker_gate, cancel)?;
1679 let image = NativeBackend::compile_image(laid_out, options)?;
1680 let shared = unsafe { SharedNativeCode::from_image(image)? };
1681 Ok(CompiledCode::Native(Arc::new(shared)))
1682 })
1683 .unwrap();
1684 let clk = sim.event("clk");
1685 let we = sim.signal("we");
1686 let waddr = sim.signal("waddr");
1687 let wdata = sim.signal("wdata");
1688 let raddr = sim.signal("raddr");
1689 let q = sim.signal("q");
1690
1691 for i in 0..4u8 {
1693 sim.modify(|io| {
1694 io.set(we, 1u8);
1695 io.set(waddr, i);
1696 io.set(wdata, i * 60 + 5);
1697 })
1698 .unwrap();
1699 sim.tick(clk).unwrap();
1700 }
1701 sim.modify(|io| io.set(we, 0u8)).unwrap();
1702
1703 for i in 0..4u8 {
1705 sim.modify(|io| io.set(raddr, i)).unwrap();
1706 assert_eq!(sim.get_as::<u8>(q), i * 60 + 5);
1707 }
1708
1709 gate.open();
1711 let deadline = std::time::Instant::now() + std::time::Duration::from_secs(60);
1712 while !sim.is_compiled() && std::time::Instant::now() < deadline {
1713 sim.tick(clk).unwrap();
1714 std::thread::sleep(std::time::Duration::from_millis(2));
1715 }
1716 assert!(sim.is_compiled());
1717
1718 for i in 0..4u8 {
1720 sim.modify(|io| io.set(raddr, i)).unwrap();
1721 assert_eq!(
1722 sim.get_as::<u8>(q),
1723 i * 60 + 5,
1724 "element {i} after native promotion"
1725 );
1726 }
1727
1728 sim.modify(|io| {
1730 io.set(we, 1u8);
1731 io.set(waddr, 2u8);
1732 io.set(wdata, 200u8);
1733 })
1734 .unwrap();
1735 sim.tick(clk).unwrap();
1736 sim.modify(|io| io.set(we, 0u8)).unwrap();
1737 sim.modify(|io| io.set(raddr, 2u8)).unwrap();
1738 assert_eq!(sim.get_as::<u8>(q), 200u8, "native-tier write");
1739 }
1740
1741 #[test]
1745 fn strided_interp_matches_packed_interp_for_four_state_array() {
1746 let code = r#"
1747module Top (
1748 clk: input clock,
1749 we: input logic,
1750 waddr: input logic<2>,
1751 wdata: input logic<8>,
1752 raddr: input logic<2>,
1753 q: output logic<8>,
1754) {
1755 var mem: logic<8>[4];
1756 always_ff (clk) {
1757 if we {
1758 mem[waddr] = wdata;
1759 }
1760 }
1761 assign q = mem[raddr];
1762}
1763"#;
1764 let mut packed_sim = SimulatorBuilder::<Simulator>::new(code, "Top")
1768 .four_state(true)
1769 .build_interpreter()
1770 .unwrap();
1771
1772 let gate = Gate::closed();
1774 let worker_gate = gate.0.clone();
1775 let mut strided_sim: Simulator<TieredBackend> =
1776 SimulatorBuilder::<Simulator>::new(code, "Top")
1777 .four_state(true)
1778 .build_tiered_with_compiler(move |laid_out, options, cancel| {
1779 wait_for_gate_or_cancel(&worker_gate, cancel)?;
1780 {
1781 let image = NativeBackend::compile_image(laid_out, options)?;
1782 let shared = unsafe { SharedNativeCode::from_image(image)? };
1783 Ok(CompiledCode::Native(Arc::new(shared)))
1784 }
1785 })
1786 .unwrap();
1787
1788 let packed_clk = packed_sim.event("clk");
1789 let strided_clk = strided_sim.event("clk");
1790 let packed_we = packed_sim.signal("we");
1791 let strided_we = strided_sim.signal("we");
1792 let packed_waddr = packed_sim.signal("waddr");
1793 let strided_waddr = strided_sim.signal("waddr");
1794 let packed_wdata = packed_sim.signal("wdata");
1795 let strided_wdata = strided_sim.signal("wdata");
1796 let packed_raddr = packed_sim.signal("raddr");
1797 let strided_raddr = strided_sim.signal("raddr");
1798
1799 for i in 0..4u8 {
1801 packed_sim
1803 .modify(|io| {
1804 io.set(packed_we, 1u8);
1805 io.set(packed_waddr, i);
1806 io.set(packed_wdata, i * 60 + 5);
1807 })
1808 .unwrap();
1809 packed_sim.tick(packed_clk).unwrap();
1810 strided_sim
1812 .modify(|io| {
1813 io.set(strided_we, 1u8);
1814 io.set(strided_waddr, i);
1815 io.set(strided_wdata, i * 60 + 5);
1816 })
1817 .unwrap();
1818 strided_sim.tick(strided_clk).unwrap();
1819 }
1820 packed_sim.modify(|io| io.set(packed_we, 0u8)).unwrap();
1821 strided_sim.modify(|io| io.set(strided_we, 0u8)).unwrap();
1822
1823 for i in 0..4u8 {
1825 packed_sim.modify(|io| io.set(packed_raddr, i)).unwrap();
1826 strided_sim.modify(|io| io.set(strided_raddr, i)).unwrap();
1827 let pv = packed_sim.get_as::<u8>(packed_sim.signal("q"));
1828 let sv = strided_sim.get_as::<u8>(strided_sim.signal("q"));
1829 assert_eq!(
1830 pv, sv,
1831 "element {i} diverges between Packed and ElementStrided interp"
1832 );
1833 }
1834 }
1835
1836 #[test]
1837 fn outputs_match_reference_when_promotion_never_happens() {
1838 let inputs: Vec<u8> = (10..40).collect();
1839 let gate = Gate::closed();
1840 let mut sim = build_gated(&gate);
1841 let observed = drive(&mut sim, &inputs);
1842 assert_eq!(observed, reference_outputs(&inputs));
1843 assert!(!sim.is_compiled());
1844 assert!(sim.promotion_error().is_none(), "gate still closed");
1845 }
1846
1847 #[test]
1848 fn outputs_match_reference_when_promotion_lands_mid_run() {
1849 let inputs: Vec<u8> = (10..40).collect();
1850 let gate = Gate::closed();
1851 let mut sim = build_gated(&gate);
1852
1853 let clk = sim.event("clk");
1854 let rst = sim.signal("rst");
1855 let d = sim.signal("d");
1856 sim.modify(|io| {
1857 io.set(rst, 0u8);
1858 io.set(d, inputs[0]);
1859 })
1860 .unwrap();
1861 sim.tick(clk).unwrap();
1862 sim.tick(clk).unwrap();
1863 sim.modify(|io| io.set(rst, 1u8)).unwrap();
1864
1865 let mut observed = Vec::new();
1867 for &value in &inputs[..inputs.len() / 2] {
1868 sim.modify(|io| io.set(d, value)).unwrap();
1869 sim.tick(clk).unwrap();
1870 sim.tick(clk).unwrap();
1871 observed.push(sim.get_as::<u8>(sim.signal("q")));
1872 }
1873
1874 gate.open();
1876 let deadline = std::time::Instant::now() + std::time::Duration::from_secs(60);
1877 while !sim.is_compiled() && std::time::Instant::now() < deadline {
1878 sim.tick(clk).unwrap();
1880 std::thread::sleep(std::time::Duration::from_millis(1));
1881 }
1882 assert!(sim.is_compiled());
1883 assert!(sim.promotion_error().is_none());
1884
1885 for &value in &inputs[inputs.len() / 2..] {
1886 sim.modify(|io| io.set(d, value)).unwrap();
1887 sim.tick(clk).unwrap();
1888 sim.tick(clk).unwrap();
1889 observed.push(sim.get_as::<u8>(sim.signal("q")));
1890 }
1891
1892 assert_eq!(observed, reference_outputs(&inputs));
1893 }
1894
1895 #[test]
1896 fn waveform_activity_survives_promotion_before_dump() {
1897 for expose_raw in [false, true] {
1898 let dir = tempfile::tempdir().unwrap();
1899 let path = dir.path().join("promotion.vcd");
1900 let gate = Gate::closed();
1901 let worker_gate = gate.0.clone();
1902 let mut sim: Simulator<TieredBackend> =
1903 SimulatorBuilder::<Simulator>::new(PIPELINE, "Top")
1904 .vcd(&path)
1905 .build_tiered_with_compiler(move |laid_out, options, cancel| {
1906 wait_for_gate_or_cancel(&worker_gate, cancel)?;
1907 let image = NativeBackend::compile_image(laid_out, options)?;
1908 let shared = unsafe { SharedNativeCode::from_image(image)? };
1909 Ok(CompiledCode::Native(Arc::new(shared)))
1910 })
1911 .unwrap();
1912 let descs = sim.build_vcd_descs(false);
1913 let mut reference = crate::VcdWriter::from_writer(Vec::new(), &descs);
1914 let dump = |sim: &mut Simulator<TieredBackend>,
1915 reference: &mut crate::VcdWriter<Vec<u8>>,
1916 time| {
1917 sim.dump(time);
1918 let (ptr, size) = sim.memory_as_ptr();
1919 reference
1920 .dump(time, unsafe { std::slice::from_raw_parts(ptr, size) })
1921 .unwrap();
1922 };
1923 let clk = sim.event("clk");
1924 let rst = sim.signal("rst");
1925 let d = sim.signal("d");
1926 sim.set(rst, 0u8);
1927 sim.tick(clk).unwrap();
1928 dump(&mut sim, &mut reference, 0);
1929 let raw = expose_raw.then(|| {
1930 let (ptr, size) = sim.memory_as_ptr();
1931 let snapshot = unsafe { std::slice::from_raw_parts(ptr, size) }.to_vec();
1933 let (ptr, _) = sim.memory_as_mut_ptr();
1934 unsafe {
1935 std::ptr::copy_nonoverlapping(snapshot.as_ptr(), ptr, size);
1936 }
1937 ptr
1938 });
1939 sim.set(rst, 1u8);
1940 sim.set(d, 0xa5u8);
1941 sim.tick(clk).unwrap();
1942 sim.tick(clk).unwrap();
1943 assert!(!sim.is_compiled());
1944 gate.open();
1946 let deadline = std::time::Instant::now() + std::time::Duration::from_secs(60);
1947 while !sim.is_compiled() && std::time::Instant::now() < deadline {
1948 sim.tick(clk).unwrap();
1949 std::thread::sleep(std::time::Duration::from_millis(1));
1950 }
1951 assert!(sim.is_compiled(), "{:?}", sim.promotion_error());
1952 dump(&mut sim, &mut reference, 1);
1953 let before = sim.vcd_statistics().unwrap().comparisons;
1954 if let Some(raw) = raw {
1955 unsafe {
1958 *raw.add(d.offset) = 0x3c;
1959 }
1960 }
1961 dump(&mut sim, &mut reference, 2);
1962 assert_eq!(
1963 sim.vcd_statistics().unwrap().comparisons,
1964 before + if expose_raw { descs.len() as u64 } else { 0 }
1965 );
1966 sim.flush_vcd().unwrap();
1967 let parse = |bytes: Vec<u8>| {
1968 let mut parser = vcd::Parser::new(bytes.as_slice());
1969 parser.parse_header().unwrap();
1970 parser.map(Result::unwrap).collect::<Vec<_>>()
1971 };
1972 assert_eq!(
1973 parse(std::fs::read(path).unwrap()),
1974 parse(reference.into_inner().unwrap())
1975 );
1976 }
1977 }
1978
1979 #[test]
1980 fn compilation_failure_keeps_the_interpreter() {
1981 let mut sim: Simulator<TieredBackend> = SimulatorBuilder::<Simulator>::new(PIPELINE, "Top")
1982 .build_tiered_with_compiler(|_, _, _| {
1983 Err(SimulatorError::from(crate::RuntimeErrorCode::InternalError))
1984 })
1985 .unwrap();
1986
1987 let inputs: Vec<u8> = (10..20).collect();
1988 let observed = drive(&mut sim, &inputs);
1989 assert_eq!(observed, reference_outputs(&inputs));
1990 assert!(!sim.is_compiled());
1991
1992 let clk = sim.event("clk");
1995 let deadline = std::time::Instant::now() + std::time::Duration::from_secs(30);
1996 while sim.promotion_error().is_none() && std::time::Instant::now() < deadline {
1997 sim.tick(clk).unwrap();
1998 std::thread::sleep(std::time::Duration::from_millis(1));
1999 }
2000 assert!(sim.promotion_error().is_some());
2001 }
2002
2003 #[test]
2004 fn runtime_events_stay_continuous_across_promotion() {
2005 let code = r#"
2006module Top (
2007 clk: input clock,
2008 cnt: output logic<8>,
2009) {
2010 var c: logic<8>;
2011 always_ff (clk) {
2012 c = c + 1;
2013 $display("tick %0d", c);
2014 }
2015 assign cnt = c;
2016}
2017"#;
2018 let gate = Gate::closed();
2019 let worker_gate = gate.0.clone();
2020 let mut sim: Simulator<TieredBackend> = SimulatorBuilder::<Simulator>::new(code, "Top")
2021 .build_tiered_with_compiler(move |laid_out, options, cancel| {
2022 wait_for_gate_or_cancel(&worker_gate, cancel)?;
2023 {
2024 let image = NativeBackend::compile_image(laid_out, options)?;
2025 let shared = unsafe { SharedNativeCode::from_image(image)? };
2026 Ok(CompiledCode::Native(Arc::new(shared)))
2027 }
2028 })
2029 .unwrap();
2030 let clk = sim.event("clk");
2031
2032 for _ in 0..5 {
2035 sim.tick(clk).unwrap();
2036 }
2037 let pre = sim.drain_runtime_events();
2038 assert_eq!(pre.len(), 5);
2039 for (index, event) in pre.iter().enumerate() {
2040 let RuntimeEvent::Display { message } = event else {
2041 panic!("unexpected event {event:?}");
2042 };
2043 assert_eq!(message.as_str(), format!("tick {index}"));
2044 }
2045
2046 gate.open();
2047 let deadline = std::time::Instant::now() + std::time::Duration::from_secs(60);
2048 while !sim.is_compiled() && std::time::Instant::now() < deadline {
2049 sim.tick(clk).unwrap();
2050 std::thread::sleep(std::time::Duration::from_millis(1));
2051 }
2052 assert!(sim.is_compiled());
2053 let _ = sim.drain_runtime_events();
2055
2056 const POST_TICKS: u32 = 8;
2058 for _ in 0..POST_TICKS {
2059 sim.tick(clk).unwrap();
2060 }
2061 let base = sim
2062 .get_as::<u8>(sim.signal("cnt"))
2063 .wrapping_sub(POST_TICKS as u8);
2064 let post = sim.drain_runtime_events();
2065 assert_eq!(post.len(), POST_TICKS as usize);
2066 for (index, event) in post.iter().enumerate() {
2067 let RuntimeEvent::Display { message } = event else {
2068 panic!("unexpected event {event:?}");
2069 };
2070 assert_eq!(
2071 message.as_str(),
2072 format!("tick {}", base.wrapping_add(index as u8))
2073 );
2074 }
2075 }
2076
2077 #[test]
2081 fn cancel_background_compilation_stays_on_the_interpreter() {
2082 let mut sim: Simulator<TieredBackend> = SimulatorBuilder::<Simulator>::new(PIPELINE, "Top")
2083 .build_tiered_with_compiler(|laid_out, options, cancel| {
2084 while !cancel.is_cancelled() {
2085 std::thread::sleep(std::time::Duration::from_millis(1));
2086 }
2087 let image = NativeBackend::compile_image_with_cancel(laid_out, options, cancel)?;
2088 let shared = unsafe { SharedNativeCode::from_image(image)? };
2089 Ok(CompiledCode::Native(Arc::new(shared)))
2090 })
2091 .unwrap();
2092
2093 assert!(sim.cancel_background_compilation());
2094
2095 let clk = sim.event("clk");
2096 let deadline = std::time::Instant::now() + std::time::Duration::from_secs(30);
2097 while sim.promotion_error().is_none() && std::time::Instant::now() < deadline {
2098 sim.tick(clk).unwrap();
2099 std::thread::sleep(std::time::Duration::from_millis(1));
2100 }
2101 assert!(!sim.is_compiled());
2102 match sim.promotion_error() {
2103 Some(error) => assert!(matches!(
2104 error.kind(),
2105 SimulatorErrorKind::Codegen(CodegenError::Cancelled)
2106 )),
2107 None => panic!("cancellation was not reported through promotion_error"),
2108 }
2109
2110 let inputs: Vec<u8> = (0..8).collect();
2112 assert_eq!(drive(&mut sim, &inputs), reference_outputs(&inputs));
2113 }
2114
2115 #[test]
2117 fn drop_cancels_pending_background_compilation() {
2118 let saw_cancel = Arc::new(AtomicBool::new(false));
2119 let worker_saw_cancel = Arc::clone(&saw_cancel);
2120 let sim: Simulator<TieredBackend> = SimulatorBuilder::<Simulator>::new(PIPELINE, "Top")
2121 .build_tiered_with_compiler(move |_laid_out, _options, cancel| {
2122 while !cancel.is_cancelled() {
2123 std::thread::sleep(std::time::Duration::from_millis(1));
2124 }
2125 worker_saw_cancel.store(true, Ordering::SeqCst);
2126 Err(SimulatorError::new(SimulatorErrorKind::Codegen(
2127 CodegenError::Cancelled,
2128 )))
2129 })
2130 .unwrap();
2131 drop(sim);
2132 assert!(saw_cancel.load(Ordering::Acquire));
2133 }
2134
2135 #[test]
2138 fn tier_promotion_never_skips_background_compilation() {
2139 let invoked = Arc::new(AtomicBool::new(false));
2140 let worker_invoked = Arc::clone(&invoked);
2141 let mut sim: Simulator<TieredBackend> = SimulatorBuilder::<Simulator>::new(PIPELINE, "Top")
2142 .tier_promotion(crate::TierPromotion::Never)
2143 .build_tiered_with_compiler(move |_laid_out, _options, _cancel| {
2144 worker_invoked.store(true, Ordering::SeqCst);
2145 Err(SimulatorError::from(crate::RuntimeErrorCode::InternalError))
2146 })
2147 .unwrap();
2148
2149 std::thread::sleep(std::time::Duration::from_millis(50));
2152 assert!(!invoked.load(Ordering::SeqCst));
2153 assert!(!sim.is_compiled());
2154 assert!(sim.promotion_error().is_none());
2155 let before = sim.tiered_execution_stats();
2156 assert_eq!(before.tier, TieredExecutionTier::Interpreter);
2157 assert_eq!(before.promotion, TieredPromotionStatus::Disabled);
2158 assert_eq!(before.compiled_evaluations, 0);
2159
2160 let inputs: Vec<u8> = (0..8).collect();
2162 assert_eq!(drive(&mut sim, &inputs), reference_outputs(&inputs));
2163 let after = sim.tiered_execution_stats();
2164 assert!(after.interpreted_evaluations > before.interpreted_evaluations);
2165 assert_eq!(after.compiled_evaluations, 0);
2166 }
2167
2168 #[test]
2171 fn tier_promotion_after_steps_defers_adoption() {
2172 const THRESHOLD: u64 = 1000;
2173 let sim_build: Simulator<TieredBackend> =
2174 SimulatorBuilder::<Simulator>::new(PIPELINE, "Top")
2175 .tier_promotion(crate::TierPromotion::AfterSteps(THRESHOLD))
2176 .build_tiered_with_compiler(|laid_out, options, _cancel| {
2177 let image = NativeBackend::compile_image(laid_out, options)?;
2178 let shared = unsafe { SharedNativeCode::from_image(image)? };
2179 Ok(CompiledCode::Native(Arc::new(shared)))
2180 })
2181 .unwrap();
2182 let mut sim = sim_build;
2183 let clk = sim.event("clk");
2184 let initial = sim.tiered_execution_stats();
2185 assert!(initial.interpreted_evaluations < THRESHOLD);
2186
2187 for _ in 0..4 {
2190 sim.tick(clk).unwrap();
2191 }
2192 assert!(
2193 !sim.is_compiled(),
2194 "adopted before the interpreted-step threshold"
2195 );
2196
2197 let deadline = std::time::Instant::now() + std::time::Duration::from_secs(60);
2199 while !sim.is_compiled() && std::time::Instant::now() < deadline {
2200 sim.tick(clk).unwrap();
2201 std::thread::sleep(std::time::Duration::from_millis(1));
2202 }
2203 assert!(sim.is_compiled());
2204 assert!(sim.promotion_error().is_none());
2205 let promoted = sim.tiered_execution_stats();
2206 assert_eq!(promoted.tier, TieredExecutionTier::Compiled);
2207 assert_eq!(promoted.promotion, TieredPromotionStatus::Promoted);
2208 assert!(promoted.interpreted_evaluations >= THRESHOLD);
2209 assert_eq!(
2210 promoted.promoted_after_interpreted_evaluations,
2211 Some(promoted.interpreted_evaluations)
2212 );
2213 assert!(promoted.threshold_deferrals > 0);
2214
2215 sim.tick(clk).unwrap();
2216 assert!(sim.tiered_execution_stats().compiled_evaluations > promoted.compiled_evaluations);
2217 }
2218
2219 #[test]
2224 fn cranelift_promotion_covers_its_image_requirement_without_moving_the_image() {
2225 use std::sync::Mutex;
2226
2227 let gate = Gate::closed();
2228 let worker_gate = gate.0.clone();
2229 let observed: Arc<Mutex<Option<(usize, usize)>>> = Arc::new(Mutex::new(None));
2230 let worker_observed = observed.clone();
2231
2232 let mut sim: Simulator<TieredBackend> = SimulatorBuilder::<Simulator>::new(PIPELINE, "Top")
2233 .build_tiered_with_compiler(move |laid_out, options, cancel| {
2234 wait_for_gate_or_cancel(&worker_gate, cancel)?;
2235 let shared = Arc::new(JitBackend::compile(laid_out, options, None)?);
2236 let code = CompiledCode::Cranelift(Arc::clone(&shared));
2237 *worker_observed.lock().unwrap() = Some((
2239 shared.layout.merged_total_size.div_ceil(8),
2240 code.required_image_words(),
2241 ));
2242 Ok(code)
2243 })
2244 .unwrap();
2245
2246 let clk = sim.event("clk");
2247 for _ in 0..2 {
2248 sim.tick(clk).unwrap();
2249 }
2250 let (base_before, _) = sim.memory_as_ptr();
2251
2252 gate.open();
2253 let deadline = std::time::Instant::now() + std::time::Duration::from_secs(60);
2254 while !sim.is_compiled() && std::time::Instant::now() < deadline {
2255 sim.tick(clk).unwrap();
2256 std::thread::sleep(std::time::Duration::from_millis(1));
2257 }
2258 assert!(sim.is_compiled());
2259 assert!(sim.promotion_error().is_none());
2260
2261 let (adopt_target, required) = observed.lock().unwrap().expect("compiler ran");
2262 assert!(
2263 required >= adopt_target,
2264 "required_image_words ({required}) must cover the Cranelift \
2265 adoption resize target ({adopt_target})"
2266 );
2267
2268 let (base_after, _) = sim.memory_as_ptr();
2269 assert_eq!(
2270 base_before, base_after,
2271 "promotion must not move the live memory image"
2272 );
2273 }
2274}