1#![cfg(feature = "host-runtime")]
38
39use std::sync::Arc;
40use std::sync::mpsc;
41use std::thread::JoinHandle;
42use std::time::{Duration, Instant};
43
44use num_bigint::BigUint;
45
46use super::compile_cancel::CompileCancel;
47use super::{
48 EventHandle, MemoryLayout, RuntimeEventBuffer, SharedJitCode, SimBackend, SimulatorErrorCode,
49 memory_image::MemoryImage,
50};
51#[cfg(any(
52 target_arch = "x86_64",
53 feature = "arm64-codegen",
54 target_arch = "aarch64"
55))]
56use crate::backend::native::SharedNativeCode;
57use crate::backend::{InterpBackend, JitBackend};
58use crate::{
59 SimulatorError, SimulatorOptions,
60 ir::{AbsoluteAddr, LaidOutProgram, SignalRef},
61};
62
63pub(crate) fn native_is_default_target() -> bool {
66 #[cfg(any(
67 all(target_arch = "x86_64", not(feature = "arm64-codegen")),
68 all(target_arch = "aarch64", not(feature = "x86_64-codegen"))
69 ))]
70 {
71 true
72 }
73 #[cfg(not(any(
74 all(target_arch = "x86_64", not(feature = "arm64-codegen")),
75 all(target_arch = "aarch64", not(feature = "x86_64-codegen"))
76 )))]
77 {
78 false
79 }
80}
81
82pub(crate) fn default_target_layout_mode() -> crate::backend::memory_layout::MemoryLayoutMode {
88 if native_is_default_target() {
89 crate::backend::memory_layout::MemoryLayoutMode::ElementStrided
90 } else {
91 crate::backend::memory_layout::MemoryLayoutMode::Packed
92 }
93}
94
95pub(crate) fn tiered_layout_mode(
101 vcd_recording: bool,
102) -> crate::backend::memory_layout::MemoryLayoutMode {
103 if vcd_recording {
104 crate::backend::memory_layout::MemoryLayoutMode::Packed
105 } else {
106 default_target_layout_mode()
107 }
108}
109
110enum CompiledTier {
112 Jit(Box<JitBackend>),
113 #[cfg(any(
114 target_arch = "x86_64",
115 feature = "arm64-codegen",
116 target_arch = "aarch64"
117 ))]
118 Native(Box<crate::backend::native::NativeBackend>),
119}
120
121impl CompiledCode {
122 fn required_image_words(&self) -> usize {
125 match self {
126 CompiledCode::Cranelift(shared) => shared.layout.merged_total_size.div_ceil(8),
131 #[cfg(any(
132 target_arch = "x86_64",
133 feature = "arm64-codegen",
134 target_arch = "aarch64"
135 ))]
136 CompiledCode::Native(shared) => shared.native_memory_size.div_ceil(8) + 1,
137 }
138 }
139}
140
141impl CompiledTier {
142 fn adopt(
150 code: CompiledCode,
151 mut memory: MemoryImage,
152 runtime_event_buffer: Arc<RuntimeEventBuffer>,
153 comb_capture_enabled: Vec<u8>,
154 ) -> Self {
155 debug_assert!(memory.capacity_words() >= code.required_image_words());
156 if memory.len_words() < code.required_image_words() {
157 memory.resize_zeroed_within_capacity(code.required_image_words());
158 }
159 match code {
160 CompiledCode::Cranelift(shared) => {
161 Self::Jit(Box::new(JitBackend::adopt_shared_with_state(
162 shared,
163 memory,
164 runtime_event_buffer,
165 comb_capture_enabled,
166 )))
167 }
168 #[cfg(any(
169 target_arch = "x86_64",
170 feature = "arm64-codegen",
171 target_arch = "aarch64"
172 ))]
173 CompiledCode::Native(shared) => Self::Native(Box::new(
174 crate::backend::native::NativeBackend::adopt_shared_with_state(
175 shared,
176 memory,
177 runtime_event_buffer,
178 comb_capture_enabled,
179 ),
180 )),
181 }
182 }
183
184 fn layout(&self) -> &MemoryLayout {
185 match self {
186 Self::Jit(jit) => jit.layout(),
187 #[cfg(any(
188 target_arch = "x86_64",
189 feature = "arm64-codegen",
190 target_arch = "aarch64"
191 ))]
192 Self::Native(native) => native.layout(),
193 }
194 }
195
196 fn memory_base_mut(&mut self) -> *mut u8 {
197 match self {
198 Self::Jit(jit) => jit.memory_as_ptr().0.cast_mut(),
199 #[cfg(any(
200 target_arch = "x86_64",
201 feature = "arm64-codegen",
202 target_arch = "aarch64"
203 ))]
204 Self::Native(native) => native.memory_as_ptr().0.cast_mut(),
205 }
206 }
207
208 fn memory_word_capacity(&self) -> usize {
210 match self {
211 Self::Jit(jit) => {
212 let (_, size_bytes) = jit.memory_as_ptr();
213 size_bytes.div_ceil(8)
214 }
215 #[cfg(any(
216 target_arch = "x86_64",
217 feature = "arm64-codegen",
218 target_arch = "aarch64"
219 ))]
220 Self::Native(native) => native.memory_word_capacity(),
221 }
222 }
223
224 fn replace_native_code(&mut self, code: CompiledCode) -> bool {
228 #[cfg(any(
229 target_arch = "x86_64",
230 feature = "arm64-codegen",
231 target_arch = "aarch64"
232 ))]
233 if let CompiledCode::Native(shared) = code {
234 return match self {
235 Self::Native(native) => {
236 native.replace_shared_code(shared);
237 true
238 }
239 Self::Jit(_) => false,
240 };
241 }
242 #[cfg(not(any(
243 target_arch = "x86_64",
244 feature = "arm64-codegen",
245 target_arch = "aarch64"
246 )))]
247 {
248 let _ = code;
249 }
250 false
251 }
252
253 fn eval_comb(&mut self) -> Result<(), SimulatorErrorCode> {
254 match self {
255 Self::Jit(jit) => jit.eval_comb(),
256 #[cfg(any(
257 target_arch = "x86_64",
258 feature = "arm64-codegen",
259 target_arch = "aarch64"
260 ))]
261 Self::Native(native) => native.eval_comb(),
262 }
263 }
264
265 fn eval_apply_ff_at(&mut self, id: usize) -> Result<(), SimulatorErrorCode> {
269 match self {
270 Self::Jit(jit) => {
271 let event = jit.id_to_event_slice()[id];
272 jit.eval_apply_ff_at(event)
273 }
274 #[cfg(any(
275 target_arch = "x86_64",
276 feature = "arm64-codegen",
277 target_arch = "aarch64"
278 ))]
279 Self::Native(native) => {
280 let event = native.id_to_event_slice()[id];
281 native.eval_apply_ff_at(event)
282 }
283 }
284 }
285
286 fn eval_comb_apply_ff_at(&mut self, id: usize) -> Result<(), SimulatorErrorCode> {
287 match self {
288 Self::Jit(jit) => {
289 let event = jit.id_to_event_slice()[id];
290 jit.eval_comb_apply_ff_at(event)
291 }
292 #[cfg(any(
293 target_arch = "x86_64",
294 feature = "arm64-codegen",
295 target_arch = "aarch64"
296 ))]
297 Self::Native(native) => {
298 let event = native.id_to_event_slice()[id];
299 native.eval_comb_apply_ff_at(event)
300 }
301 }
302 }
303
304 fn eval_comb_apply_ff_many_at(
309 &mut self,
310 id: usize,
311 count: u64,
312 ) -> (u64, Result<(), SimulatorErrorCode>) {
313 match self {
314 Self::Jit(jit) => (1, {
315 let event = jit.id_to_event_slice()[id];
316 jit.eval_comb_apply_ff_at(event)
317 }),
318 #[cfg(any(
319 target_arch = "x86_64",
320 feature = "arm64-codegen",
321 target_arch = "aarch64"
322 ))]
323 Self::Native(native) => {
324 native.eval_comb_apply_ff_many_at(native.id_to_event_slice()[id], count)
325 }
326 }
327 }
328
329 fn eval_only_ff_at_addr(&mut self, addr: &AbsoluteAddr) -> Result<(), SimulatorErrorCode> {
336 match self {
337 Self::Jit(jit) => {
338 let event = jit
339 .resolve_eval_only_event(addr)
340 .unwrap_or_else(|| panic!("eval-only event not found for {addr:?}"));
341 jit.eval_only_ff_at(event)
342 }
343 #[cfg(any(
344 target_arch = "x86_64",
345 feature = "arm64-codegen",
346 target_arch = "aarch64"
347 ))]
348 Self::Native(native) => {
349 let event = native
350 .resolve_eval_only_event(addr)
351 .unwrap_or_else(|| panic!("eval-only event not found for {addr:?}"));
352 native.eval_only_ff_at(event)
353 }
354 }
355 }
356
357 fn apply_ff_at_addr(&mut self, addr: &AbsoluteAddr) -> Result<(), SimulatorErrorCode> {
360 match self {
361 Self::Jit(jit) => {
362 let event = jit
363 .resolve_apply_event(addr)
364 .unwrap_or_else(|| panic!("apply event not found for {addr:?}"));
365 jit.apply_ff_at(event)
366 }
367 #[cfg(any(
368 target_arch = "x86_64",
369 feature = "arm64-codegen",
370 target_arch = "aarch64"
371 ))]
372 Self::Native(native) => {
373 let event = native
374 .resolve_apply_event(addr)
375 .unwrap_or_else(|| panic!("apply event not found for {addr:?}"));
376 native.apply_ff_at(event)
377 }
378 }
379 }
380}
381
382pub(crate) enum CompiledCode {
385 Cranelift(Arc<SharedJitCode>),
386 #[cfg(any(
387 target_arch = "x86_64",
388 feature = "arm64-codegen",
389 target_arch = "aarch64"
390 ))]
391 Native(Arc<SharedNativeCode>),
392}
393
394#[derive(Clone, Copy, Debug)]
401pub struct TieredEventRef {
402 addr: AbsoluteAddr,
403 id: usize,
404}
405
406impl EventHandle for TieredEventRef {
407 fn id(&self) -> usize {
408 self.id
409 }
410
411 fn addr(&self) -> AbsoluteAddr {
412 self.addr
413 }
414}
415
416enum Phase {
417 Interpreting(Option<Box<InterpBackend>>),
418 Compiled(CompiledTier),
419}
420
421enum Promotion {
422 Pending(mpsc::Receiver<Result<CompiledCode, SimulatorError>>),
425 Failed(SimulatorError),
427 Disabled,
430 Promoted,
431}
432
433#[derive(Clone, Copy, Debug, PartialEq, Eq)]
435#[non_exhaustive]
436pub enum TieredExecutionTier {
437 Interpreter,
439 Compiled,
441}
442
443#[derive(Clone, Copy, Debug, PartialEq, Eq)]
445#[non_exhaustive]
446pub enum TieredPromotionStatus {
447 Pending,
449 Failed,
451 Disabled,
453 Promoted,
455}
456
457#[derive(Clone, Copy, Debug, PartialEq, Eq)]
466#[non_exhaustive]
467pub struct TieredExecutionStats {
468 pub tier: TieredExecutionTier,
470 pub promotion: TieredPromotionStatus,
472 pub interpreted_evaluations: u64,
474 pub compiled_evaluations: u64,
476 pub promoted_after_interpreted_evaluations: Option<u64>,
478 pub safe_point_polls: u64,
480 pub split_apply_deferrals: u64,
482 pub threshold_deferrals: u64,
484}
485
486#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
493pub struct TieredExecutionTiming {
494 promotion_elapsed: Option<Duration>,
495}
496
497impl TieredExecutionTiming {
498 pub fn promotion_elapsed(self) -> Option<Duration> {
500 self.promotion_elapsed
501 }
502}
503
504struct ActiveExecutionTiming {
505 started_at: Instant,
506 promotion_elapsed: Option<Duration>,
507}
508
509pub struct TieredBackend {
512 phase: Phase,
513 promotion: Promotion,
514 events: Vec<TieredEventRef>,
517 cancel: CompileCancel,
520 compiler_worker: Option<JoinHandle<()>>,
524 upgrade_receiver: Option<mpsc::Receiver<Result<CompiledCode, SimulatorError>>>,
528 pending_split_applies: usize,
533 promotion_threshold: u64,
537 interpreted_steps: u64,
539 compiled_steps: u64,
541 promoted_after_interpreted_steps: Option<u64>,
543 safe_point_polls: u64,
545 split_apply_deferrals: u64,
547 threshold_deferrals: u64,
549 execution_timing: Option<ActiveExecutionTiming>,
552}
553
554impl TieredBackend {
555 pub fn new(laid_out: &LaidOutProgram, options: &SimulatorOptions) -> Self {
563 if native_is_default_target() {
564 #[cfg(any(
565 target_arch = "x86_64",
566 feature = "arm64-codegen",
567 target_arch = "aarch64"
568 ))]
569 {
570 if cfg!(all(target_arch = "x86_64", not(feature = "arm64-codegen")))
574 && !options.trace.native
575 && !options.trace.mir
576 && !matches!(
577 options.tier_promotion,
578 crate::simulator::TierPromotion::Never
579 )
580 {
581 let mut baseline_options = options.clone();
582 baseline_options.x86_options.baseline = true;
583 let mut optimizing_options = options.clone();
584 optimizing_options.x86_options.baseline = false;
585 return Self::with_two_stage_compiler(
586 laid_out,
587 &baseline_options,
588 optimizing_options,
589 |laid_out, options, cancel| {
590 use crate::backend::native::{NativeBackend, SharedNativeCode};
591 let image = NativeBackend::compile_image_with_cancel(
594 laid_out, options, cancel,
595 )?;
596 let shared = Arc::new(unsafe { SharedNativeCode::from_image(image)? });
597 Ok(CompiledCode::Native(shared))
598 },
599 );
600 }
601 Self::with_compiler(laid_out, options, |laid_out, options, cancel| {
602 use crate::backend::native::{NativeBackend, SharedNativeCode};
603 let wants_native_trace = options.trace.native || options.trace.mir;
607 let image = if wants_native_trace {
608 let (image, native_trace) =
609 NativeBackend::compile_image_with_cancel_and_trace(
610 laid_out, options, cancel,
611 )?;
612 let compilation = crate::debug::CompilationTrace {
613 native_optimized_sir: Some(native_trace.optimized_sir),
614 mir: Some(native_trace.mir),
615 reactive_event_graph: Some(native_trace.reactive_graph),
616 native_state_layout: Some(native_trace.state_layout),
617 ..crate::debug::CompilationTrace::default()
618 };
619 if options.trace.output_to_stdout {
620 compilation.print();
621 }
622 image
623 } else {
624 NativeBackend::compile_image_with_cancel(laid_out, options, cancel)?
625 };
626 let shared = Arc::new(unsafe { SharedNativeCode::from_image(image)? });
629 Ok(CompiledCode::Native(shared))
630 })
631 }
632 #[cfg(not(any(
633 target_arch = "x86_64",
634 feature = "arm64-codegen",
635 target_arch = "aarch64"
636 )))]
637 {
638 unreachable!("native tier selected on a host without native support")
639 }
640 } else {
641 Self::with_compiler(laid_out, options, |laid_out, options, _cancel| {
642 let mut trace = crate::debug::CompilationTrace::default();
643 let wants_codegen_trace = options.trace.pre_optimized_clif
644 || options.trace.post_optimized_clif
645 || options.trace.native;
646 let shared = Arc::new(JitBackend::compile(
647 laid_out,
648 options,
649 wants_codegen_trace.then_some(&mut trace),
650 )?);
651 if options.trace.output_to_stdout {
652 trace.print();
653 }
654 Ok(CompiledCode::Cranelift(shared))
655 })
656 }
657 }
658
659 pub(crate) fn with_compiler<F>(
666 laid_out: &LaidOutProgram,
667 options: &SimulatorOptions,
668 compile: F,
669 ) -> Self
670 where
671 F: FnOnce(
672 &LaidOutProgram,
673 &SimulatorOptions,
674 &CompileCancel,
675 ) -> Result<CompiledCode, SimulatorError>
676 + Send
677 + 'static,
678 {
679 let mut interp = Box::new(
680 InterpBackend::new(laid_out, options)
681 .expect("interpreter construction cannot fail for a laid-out program"),
682 );
683 if !matches!(
691 options.tier_promotion,
692 crate::simulator::TierPromotion::Never
693 ) {
694 let len = interp.image_word_len();
695 let slack = len.max(1024) / 8;
696 interp.reserve_image_capacity(len + slack.max(1024));
697 }
698 let events = interp
699 .id_to_event_slice()
700 .iter()
701 .map(|ev| TieredEventRef {
702 addr: ev.addr(),
703 id: ev.id(),
704 })
705 .collect();
706
707 let cancel = CompileCancel::new();
708 let worker_cancel = cancel.clone();
709 let (promotion, compiler_worker) = if matches!(
713 options.tier_promotion,
714 crate::simulator::TierPromotion::Never
715 ) {
716 (Promotion::Disabled, None)
719 } else {
720 let (sender, receiver) = mpsc::channel();
721 let background_laid_out = laid_out.clone();
722 let background_options = options.clone();
723 match std::thread::Builder::new()
724 .name("celox-jit-compile".to_string())
725 .spawn(move || {
726 let span = tracing::info_span!(
731 "celox.tiered.compile",
732 target = if native_is_default_target() {
733 "native"
734 } else {
735 "cranelift"
736 }
737 );
738 let result = span.in_scope(|| {
739 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
740 compile(&background_laid_out, &background_options, &worker_cancel)
741 }))
742 .unwrap_or_else(|_| {
743 Err(SimulatorError::from(crate::RuntimeErrorCode::InternalError))
744 });
745 match &result {
746 Ok(_) => tracing::info!("tiered background compilation completed"),
747 Err(error) => tracing::warn!(
748 error = %error,
749 "tiered background compilation failed"
750 ),
751 }
752 result
753 });
754 let _ = sender.send(result);
755 }) {
756 Ok(handle) => (Promotion::Pending(receiver), Some(handle)),
757 Err(error) => (
758 Promotion::Failed(SimulatorError::new(crate::SimulatorErrorKind::Codegen(
759 crate::CodegenError::message(format!(
760 "failed to spawn the background compiler thread: {error}"
761 )),
762 ))),
763 None,
764 ),
765 }
766 };
767
768 let promotion_threshold = match options.tier_promotion {
769 crate::simulator::TierPromotion::AfterSteps(steps) => steps,
770 _ => 0,
771 };
772
773 Self {
774 phase: Phase::Interpreting(Some(interp)),
775 promotion,
776 events,
777 cancel,
778 compiler_worker,
779 upgrade_receiver: None,
780 pending_split_applies: 0,
781 promotion_threshold,
782 interpreted_steps: 0,
783 compiled_steps: 0,
784 promoted_after_interpreted_steps: None,
785 safe_point_polls: 0,
786 split_apply_deferrals: 0,
787 threshold_deferrals: 0,
788 execution_timing: None,
789 }
790 }
791
792 pub(crate) fn with_two_stage_compiler<F>(
801 laid_out: &LaidOutProgram,
802 baseline_options: &SimulatorOptions,
803 optimizing_options: SimulatorOptions,
804 compile: F,
805 ) -> Self
806 where
807 F: Fn(
808 &LaidOutProgram,
809 &SimulatorOptions,
810 &CompileCancel,
811 ) -> Result<CompiledCode, SimulatorError>
812 + Send
813 + 'static,
814 {
815 let mut interp = Box::new(
816 InterpBackend::new(laid_out, baseline_options)
817 .expect("interpreter construction cannot fail for a laid-out program"),
818 );
819 let len = interp.image_word_len();
824 let slack = len.max(1024) / 4;
825 interp.reserve_image_capacity(len + slack.max(1024));
826 let events = interp
827 .id_to_event_slice()
828 .iter()
829 .map(|ev| TieredEventRef {
830 addr: ev.addr(),
831 id: ev.id(),
832 })
833 .collect();
834
835 let cancel = CompileCancel::new();
836 let worker_cancel = cancel.clone();
837 let (baseline_sender, baseline_receiver) = mpsc::channel();
838 let (upgrade_sender, upgrade_receiver) = mpsc::channel();
839 let background_laid_out = laid_out.clone();
840 let worker_baseline_options = baseline_options.clone();
841 let (promotion, compiler_worker) = match std::thread::Builder::new()
844 .name("celox-jit-compile".to_string())
845 .spawn(move || {
846 let span = tracing::info_span!("celox.tiered.compile", target = "native");
847 span.in_scope(|| {
848 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
849 compile(
850 &background_laid_out,
851 &worker_baseline_options,
852 &worker_cancel,
853 )
854 }))
855 .unwrap_or_else(|_| {
856 Err(SimulatorError::from(crate::RuntimeErrorCode::InternalError))
857 });
858 match &result {
859 Ok(_) => tracing::info!("tiered baseline compilation completed"),
860 Err(error) => {
861 tracing::warn!(error = %error, "tiered baseline compilation failed")
862 }
863 }
864 let ready = result.is_ok();
865 let delivered = baseline_sender.send(result).is_ok();
866 if !delivered || !ready || worker_cancel.is_cancelled() {
869 return;
870 }
871 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
872 compile(&background_laid_out, &optimizing_options, &worker_cancel)
873 }))
874 .unwrap_or_else(|_| {
875 Err(SimulatorError::from(crate::RuntimeErrorCode::InternalError))
876 });
877 match &result {
878 Ok(_) => tracing::info!("tiered optimizing compilation completed"),
879 Err(error) => tracing::warn!(
880 error = %error,
881 "tiered optimizing compilation failed"
882 ),
883 }
884 let _ = upgrade_sender.send(result);
885 });
886 }) {
887 Ok(handle) => (Promotion::Pending(baseline_receiver), Some(handle)),
888 Err(error) => (
889 Promotion::Failed(SimulatorError::new(crate::SimulatorErrorKind::Codegen(
890 crate::CodegenError::message(format!(
891 "failed to spawn the background compiler thread: {error}"
892 )),
893 ))),
894 None,
895 ),
896 };
897
898 let promotion_threshold = match baseline_options.tier_promotion {
899 crate::simulator::TierPromotion::AfterSteps(steps) => steps,
900 _ => 0,
901 };
902
903 Self {
904 phase: Phase::Interpreting(Some(interp)),
905 promotion,
906 events,
907 cancel,
908 compiler_worker,
909 upgrade_receiver: Some(upgrade_receiver),
910 pending_split_applies: 0,
911 promotion_threshold,
912 interpreted_steps: 0,
913 compiled_steps: 0,
914 promoted_after_interpreted_steps: None,
915 safe_point_polls: 0,
916 split_apply_deferrals: 0,
917 threshold_deferrals: 0,
918 execution_timing: None,
919 }
920 }
921
922 pub fn is_compiled(&self) -> bool {
924 matches!(self.phase, Phase::Compiled(_))
925 }
926
927 pub fn execution_stats(&self) -> TieredExecutionStats {
929 TieredExecutionStats {
930 tier: if self.is_compiled() {
931 TieredExecutionTier::Compiled
932 } else {
933 TieredExecutionTier::Interpreter
934 },
935 promotion: match &self.promotion {
936 Promotion::Pending(_) => TieredPromotionStatus::Pending,
937 Promotion::Failed(_) => TieredPromotionStatus::Failed,
938 Promotion::Disabled => TieredPromotionStatus::Disabled,
939 Promotion::Promoted => TieredPromotionStatus::Promoted,
940 },
941 interpreted_evaluations: self.interpreted_steps,
942 compiled_evaluations: self.compiled_steps,
943 promoted_after_interpreted_evaluations: self.promoted_after_interpreted_steps,
944 safe_point_polls: self.safe_point_polls,
945 split_apply_deferrals: self.split_apply_deferrals,
946 threshold_deferrals: self.threshold_deferrals,
947 }
948 }
949
950 pub fn start_execution_timing(&mut self) {
952 self.execution_timing = Some(ActiveExecutionTiming {
953 started_at: Instant::now(),
954 promotion_elapsed: self.is_compiled().then_some(Duration::ZERO),
955 });
956 }
957
958 pub fn finish_execution_timing(&mut self) -> Option<TieredExecutionTiming> {
960 self.execution_timing
961 .take()
962 .map(|timing| TieredExecutionTiming {
963 promotion_elapsed: timing.promotion_elapsed,
964 })
965 }
966
967 pub fn promotion_error(&self) -> Option<&SimulatorError> {
972 match &self.promotion {
973 Promotion::Failed(error) => Some(error),
974 _ => None,
975 }
976 }
977
978 pub fn cancel_background_compilation(&mut self) -> bool {
990 let promotion_pending = matches!(self.promotion, Promotion::Pending(_));
991 let upgrade_pending = self.upgrade_receiver.take().is_some();
992 self.cancel.cancel();
993 if promotion_pending {
994 self.promotion = Promotion::Failed(super::compile_cancel::cancelled_error());
995 }
996 promotion_pending || upgrade_pending
997 }
998
999 fn count_evaluation_for_current_tier(&mut self) {
1003 if matches!(self.phase, Phase::Interpreting(Some(_))) {
1004 self.interpreted_steps = self.interpreted_steps.saturating_add(1);
1005 } else if matches!(self.phase, Phase::Compiled(_)) {
1006 self.compiled_steps = self.compiled_steps.saturating_add(1);
1007 }
1008 }
1009
1010 fn maybe_promote(&mut self) {
1015 if !matches!(self.phase, Phase::Interpreting(Some(_))) {
1016 return;
1017 }
1018 self.safe_point_polls = self.safe_point_polls.saturating_add(1);
1019 if self.pending_split_applies > 0 {
1023 self.split_apply_deferrals = self.split_apply_deferrals.saturating_add(1);
1024 return;
1025 }
1026 if self.interpreted_steps < self.promotion_threshold {
1029 self.threshold_deferrals = self.threshold_deferrals.saturating_add(1);
1030 return;
1031 }
1032 let Promotion::Pending(receiver) = &self.promotion else {
1033 return;
1034 };
1035 if self.cancel.is_cancelled() {
1038 self.promotion = Promotion::Failed(super::compile_cancel::cancelled_error());
1039 return;
1040 }
1041 let Ok(result) = receiver.try_recv() else {
1042 return;
1043 };
1044
1045 let Ok(code) = result else {
1046 self.promotion = match result {
1049 Err(error) => Promotion::Failed(error),
1050 Ok(_) => unreachable!("checked above"),
1051 };
1052 self.cancel_background_compilation();
1053 return;
1054 };
1055 if let Phase::Interpreting(Some(interp)) = &mut self.phase {
1061 let required = code.required_image_words();
1062 if interp.image_word_capacity() < required {
1063 self.promotion = Promotion::Failed(SimulatorError::new(
1064 crate::SimulatorErrorKind::Codegen(crate::CodegenError::message(format!(
1065 "native image needs {required} words but the reserved interpreter \
1066 image only has {} words of capacity; refusing to move the live \
1067 memory image and staying interpreted",
1068 interp.image_word_capacity()
1069 ))),
1070 ));
1071 self.cancel_background_compilation();
1072 return;
1073 }
1074 }
1075 let mut adopted = None;
1076 if let Phase::Interpreting(slot) = &mut self.phase {
1077 if let Some(mut interp) = slot.take() {
1078 let (memory, runtime_event_buffer, comb_capture_enabled) = interp.tier_transfer();
1079 drop(interp);
1080 adopted = Some(CompiledTier::adopt(
1081 code,
1082 memory,
1083 runtime_event_buffer,
1084 comb_capture_enabled,
1085 ));
1086 }
1087 }
1088 if let Some(mut compiled) = adopted {
1089 let promotion_span = tracing::info_span!(
1090 "celox.tiered.promote",
1091 interpreted_evaluations = self.interpreted_steps
1092 );
1093 let _entered = promotion_span.enter();
1094 let sparse_metas: Vec<_> = {
1098 let layout = compiled.layout();
1099 layout
1100 .sparse_layouts
1101 .values()
1102 .map(|s| {
1103 (
1104 s.dirty_words_offset,
1105 s.dirty_word_count * 8,
1106 s.summary_words_offset,
1107 s.summary_word_count * 8,
1108 )
1109 })
1110 .collect()
1111 };
1112 unsafe {
1113 let base = compiled.memory_base_mut();
1114 for &(dwo, dwc, swo, swc) in &sparse_metas {
1115 std::ptr::write_bytes(base.add(dwo), 0, dwc);
1116 std::ptr::write_bytes(base.add(swo), 0, swc);
1117 }
1118 }
1119 self.phase = Phase::Compiled(compiled);
1120 self.promotion = Promotion::Promoted;
1121 self.promoted_after_interpreted_steps = Some(self.interpreted_steps);
1122 if let Some(timing) = &mut self.execution_timing
1123 && timing.promotion_elapsed.is_none()
1124 {
1125 timing.promotion_elapsed = Some(timing.started_at.elapsed());
1126 }
1127 tracing::info!("tiered backend adopted generated code");
1128 }
1129 }
1130
1131 fn maybe_upgrade(&mut self) {
1139 if self.cancel.is_cancelled() {
1140 self.upgrade_receiver = None;
1141 return;
1142 }
1143 if !matches!(self.phase, Phase::Compiled(_)) || self.pending_split_applies > 0 {
1144 return;
1145 }
1146 let Some(receiver) = self.upgrade_receiver.take() else {
1147 return;
1148 };
1149 let result = match receiver.try_recv() {
1150 Ok(result) => result,
1151 Err(mpsc::TryRecvError::Disconnected) => return,
1155 Err(mpsc::TryRecvError::Empty) => {
1156 self.upgrade_receiver = Some(receiver);
1157 return;
1158 }
1159 };
1160 let code = match result {
1161 Ok(code) => code,
1162 Err(error) => {
1163 tracing::warn!(
1164 error = %error,
1165 "tiered optimizing compilation failed; keeping baseline code"
1166 );
1167 return;
1168 }
1169 };
1170 let required = code.required_image_words();
1171 if let Phase::Compiled(compiled) = &mut self.phase
1172 && compiled.memory_word_capacity() < required
1173 {
1174 tracing::warn!(
1175 required_words = required,
1176 "tiered optimizing image exceeds the reserved image; keeping baseline code"
1177 );
1178 return;
1179 }
1180 if let Phase::Compiled(compiled) = &mut self.phase
1181 && compiled.replace_native_code(code)
1182 {
1183 tracing::info!("tiered backend adopted optimizing native code");
1184 }
1185 }
1186}
1187
1188impl Drop for TieredBackend {
1189 fn drop(&mut self) {
1190 self.cancel.cancel();
1191 if let Some(worker) = self.compiler_worker.take() {
1192 let _ = worker.join();
1193 }
1194 }
1195}
1196
1197impl SimBackend for TieredBackend {
1198 type Event = TieredEventRef;
1199
1200 fn eval_comb(&mut self) -> Result<(), SimulatorErrorCode> {
1201 self.maybe_promote();
1202 self.maybe_upgrade();
1203 self.count_evaluation_for_current_tier();
1204 match &mut self.phase {
1205 Phase::Interpreting(Some(interp)) => interp.eval_comb(),
1206 Phase::Compiled(compiled) => compiled.eval_comb(),
1207 Phase::Interpreting(None) => unreachable!("promoted backend left no interpreter"),
1208 }
1209 }
1210
1211 fn eval_apply_ff_at(&mut self, event: TieredEventRef) -> Result<(), SimulatorErrorCode> {
1212 self.maybe_promote();
1213 self.maybe_upgrade();
1214 self.count_evaluation_for_current_tier();
1215 match &mut self.phase {
1216 Phase::Interpreting(Some(interp)) => interp.eval_apply_ff_at(
1217 super::interp::InterpEventRef::from_parts(event.addr(), event.id()),
1218 ),
1219 Phase::Compiled(compiled) => compiled.eval_apply_ff_at(event.id()),
1220 Phase::Interpreting(None) => unreachable!("promoted backend left no interpreter"),
1221 }
1222 }
1223
1224 fn eval_comb_apply_ff_at(&mut self, event: TieredEventRef) -> Result<(), SimulatorErrorCode> {
1225 self.maybe_promote();
1226 self.maybe_upgrade();
1227 self.count_evaluation_for_current_tier();
1228 match &mut self.phase {
1229 Phase::Interpreting(Some(interp)) => interp.eval_comb_apply_ff_at(
1230 super::interp::InterpEventRef::from_parts(event.addr(), event.id()),
1231 ),
1232 Phase::Compiled(compiled) => compiled.eval_comb_apply_ff_at(event.id()),
1233 Phase::Interpreting(None) => unreachable!("promoted backend left no interpreter"),
1234 }
1235 }
1236
1237 fn eval_comb_apply_ff_many_at(
1238 &mut self,
1239 event: TieredEventRef,
1240 count: u64,
1241 ) -> (u64, Result<(), SimulatorErrorCode>) {
1242 if count == 0 {
1245 return (0, Ok(()));
1246 }
1247 self.maybe_promote();
1248 self.maybe_upgrade();
1249 match &mut self.phase {
1250 Phase::Interpreting(Some(interp)) => {
1251 self.interpreted_steps = self.interpreted_steps.saturating_add(1);
1252 (
1253 1,
1254 interp.eval_comb_apply_ff_at(super::interp::InterpEventRef::from_parts(
1255 event.addr(),
1256 event.id(),
1257 )),
1258 )
1259 }
1260 Phase::Compiled(compiled) => {
1261 let (completed, result) = compiled.eval_comb_apply_ff_many_at(event.id(), count);
1262 self.compiled_steps = self.compiled_steps.saturating_add(completed);
1263 (completed, result)
1264 }
1265 Phase::Interpreting(None) => unreachable!("promoted backend left no interpreter"),
1266 }
1267 }
1268
1269 fn eval_only_ff_at(&mut self, event: TieredEventRef) -> Result<(), SimulatorErrorCode> {
1270 self.maybe_promote();
1271 self.maybe_upgrade();
1272 self.count_evaluation_for_current_tier();
1273 let result = match &mut self.phase {
1274 Phase::Interpreting(Some(interp)) => interp.eval_only_ff_at(
1275 super::interp::InterpEventRef::from_parts(event.addr(), event.id()),
1276 ),
1277 Phase::Compiled(compiled) => compiled.eval_only_ff_at_addr(&event.addr()),
1278 Phase::Interpreting(None) => unreachable!("promoted backend left no interpreter"),
1279 };
1280 self.pending_split_applies = self.pending_split_applies.saturating_add(1);
1283 result
1284 }
1285
1286 fn apply_ff_at(&mut self, event: TieredEventRef) -> Result<(), SimulatorErrorCode> {
1287 self.maybe_promote();
1289 self.maybe_upgrade();
1290 self.count_evaluation_for_current_tier();
1291 let result = match &mut self.phase {
1292 Phase::Interpreting(Some(interp)) => interp.apply_ff_at(
1293 super::interp::InterpEventRef::from_parts(event.addr(), event.id()),
1294 ),
1295 Phase::Compiled(compiled) => compiled.apply_ff_at_addr(&event.addr()),
1296 Phase::Interpreting(None) => unreachable!("promoted backend left no interpreter"),
1297 };
1298 self.pending_split_applies = self.pending_split_applies.saturating_sub(1);
1299 result
1300 }
1301
1302 fn resolve_signal(&self, addr: &AbsoluteAddr) -> SignalRef {
1303 match &self.phase {
1304 Phase::Interpreting(Some(interp)) => interp.resolve_signal(addr),
1305 Phase::Compiled(CompiledTier::Jit(jit)) => jit.resolve_signal(addr),
1306 #[cfg(any(
1307 target_arch = "x86_64",
1308 feature = "arm64-codegen",
1309 target_arch = "aarch64"
1310 ))]
1311 Phase::Compiled(CompiledTier::Native(native)) => native.resolve_signal(addr),
1312 Phase::Interpreting(None) => unreachable!("promoted backend left no interpreter"),
1313 }
1314 }
1315
1316 fn resolve_event(&self, addr: &AbsoluteAddr) -> TieredEventRef {
1317 match &self.phase {
1318 Phase::Interpreting(Some(interp)) => {
1319 let ev = interp.resolve_event(addr);
1320 TieredEventRef {
1321 addr: ev.addr(),
1322 id: ev.id(),
1323 }
1324 }
1325 Phase::Compiled(CompiledTier::Jit(jit)) => {
1326 let ev = jit.resolve_event(addr);
1327 TieredEventRef {
1328 addr: ev.addr(),
1329 id: ev.id(),
1330 }
1331 }
1332 #[cfg(any(
1333 target_arch = "x86_64",
1334 feature = "arm64-codegen",
1335 target_arch = "aarch64"
1336 ))]
1337 Phase::Compiled(CompiledTier::Native(native)) => {
1338 let ev = native.resolve_event(addr);
1339 TieredEventRef {
1340 addr: ev.addr(),
1341 id: ev.id(),
1342 }
1343 }
1344 Phase::Interpreting(None) => unreachable!("promoted backend left no interpreter"),
1345 }
1346 }
1347
1348 fn resolve_event_opt(&self, addr: &AbsoluteAddr) -> Option<TieredEventRef> {
1349 match &self.phase {
1350 Phase::Interpreting(Some(interp)) => {
1351 interp.resolve_event_opt(addr).map(|ev| TieredEventRef {
1352 addr: ev.addr(),
1353 id: ev.id(),
1354 })
1355 }
1356 Phase::Compiled(CompiledTier::Jit(jit)) => {
1357 jit.resolve_event_opt(addr).map(|ev| TieredEventRef {
1358 addr: ev.addr(),
1359 id: ev.id(),
1360 })
1361 }
1362 #[cfg(any(
1363 target_arch = "x86_64",
1364 feature = "arm64-codegen",
1365 target_arch = "aarch64"
1366 ))]
1367 Phase::Compiled(CompiledTier::Native(native)) => {
1368 native.resolve_event_opt(addr).map(|ev| TieredEventRef {
1369 addr: ev.addr(),
1370 id: ev.id(),
1371 })
1372 }
1373 Phase::Interpreting(None) => unreachable!("promoted backend left no interpreter"),
1374 }
1375 }
1376
1377 fn resolve_eval_only_event(&self, addr: &AbsoluteAddr) -> Option<TieredEventRef> {
1378 match &self.phase {
1379 Phase::Interpreting(Some(interp)) => {
1380 interp
1381 .resolve_eval_only_event(addr)
1382 .map(|ev| TieredEventRef {
1383 addr: ev.addr(),
1384 id: ev.id(),
1385 })
1386 }
1387 Phase::Compiled(CompiledTier::Jit(jit)) => {
1388 jit.resolve_eval_only_event(addr).map(|ev| TieredEventRef {
1389 addr: ev.addr(),
1390 id: ev.id(),
1391 })
1392 }
1393 #[cfg(any(
1394 target_arch = "x86_64",
1395 feature = "arm64-codegen",
1396 target_arch = "aarch64"
1397 ))]
1398 Phase::Compiled(CompiledTier::Native(native)) => native
1399 .resolve_eval_only_event(addr)
1400 .map(|ev| TieredEventRef {
1401 addr: ev.addr(),
1402 id: ev.id(),
1403 }),
1404 Phase::Interpreting(None) => unreachable!("promoted backend left no interpreter"),
1405 }
1406 }
1407
1408 fn resolve_apply_event(&self, addr: &AbsoluteAddr) -> Option<TieredEventRef> {
1409 match &self.phase {
1410 Phase::Interpreting(Some(interp)) => {
1411 interp.resolve_apply_event(addr).map(|ev| TieredEventRef {
1412 addr: ev.addr(),
1413 id: ev.id(),
1414 })
1415 }
1416 Phase::Compiled(CompiledTier::Jit(jit)) => {
1417 jit.resolve_apply_event(addr).map(|ev| TieredEventRef {
1418 addr: ev.addr(),
1419 id: ev.id(),
1420 })
1421 }
1422 #[cfg(any(
1423 target_arch = "x86_64",
1424 feature = "arm64-codegen",
1425 target_arch = "aarch64"
1426 ))]
1427 Phase::Compiled(CompiledTier::Native(native)) => {
1428 native.resolve_apply_event(addr).map(|ev| TieredEventRef {
1429 addr: ev.addr(),
1430 id: ev.id(),
1431 })
1432 }
1433 Phase::Interpreting(None) => unreachable!("promoted backend left no interpreter"),
1434 }
1435 }
1436
1437 fn set<T: Copy>(&mut self, signal: SignalRef, value: T) {
1438 match &mut self.phase {
1439 Phase::Interpreting(Some(interp)) => interp.set(signal, value),
1440 Phase::Compiled(CompiledTier::Jit(jit)) => jit.set(signal, value),
1441 #[cfg(any(
1442 target_arch = "x86_64",
1443 feature = "arm64-codegen",
1444 target_arch = "aarch64"
1445 ))]
1446 Phase::Compiled(CompiledTier::Native(native)) => native.set(signal, value),
1447 Phase::Interpreting(None) => unreachable!("promoted backend left no interpreter"),
1448 }
1449 }
1450
1451 fn set_wide(&mut self, signal: SignalRef, value: BigUint) {
1452 match &mut self.phase {
1453 Phase::Interpreting(Some(interp)) => interp.set_wide(signal, value),
1454 Phase::Compiled(CompiledTier::Jit(jit)) => jit.set_wide(signal, value),
1455 #[cfg(any(
1456 target_arch = "x86_64",
1457 feature = "arm64-codegen",
1458 target_arch = "aarch64"
1459 ))]
1460 Phase::Compiled(CompiledTier::Native(native)) => native.set_wide(signal, value),
1461 Phase::Interpreting(None) => unreachable!("promoted backend left no interpreter"),
1462 }
1463 }
1464
1465 fn set_four_state(&mut self, signal: SignalRef, value: BigUint, mask: BigUint) {
1466 match &mut self.phase {
1467 Phase::Interpreting(Some(interp)) => interp.set_four_state(signal, value, mask),
1468 Phase::Compiled(CompiledTier::Jit(jit)) => jit.set_four_state(signal, value, mask),
1469 #[cfg(any(
1470 target_arch = "x86_64",
1471 feature = "arm64-codegen",
1472 target_arch = "aarch64"
1473 ))]
1474 Phase::Compiled(CompiledTier::Native(native)) => {
1475 native.set_four_state(signal, value, mask)
1476 }
1477 Phase::Interpreting(None) => unreachable!("promoted backend left no interpreter"),
1478 }
1479 }
1480
1481 fn get(&self, signal: SignalRef) -> BigUint {
1482 match &self.phase {
1483 Phase::Interpreting(Some(interp)) => interp.get(signal),
1484 Phase::Compiled(CompiledTier::Jit(jit)) => jit.get(signal),
1485 #[cfg(any(
1486 target_arch = "x86_64",
1487 feature = "arm64-codegen",
1488 target_arch = "aarch64"
1489 ))]
1490 Phase::Compiled(CompiledTier::Native(native)) => native.get(signal),
1491 Phase::Interpreting(None) => unreachable!("promoted backend left no interpreter"),
1492 }
1493 }
1494
1495 fn get_as<T: Default + Copy>(&self, signal: SignalRef) -> T {
1496 match &self.phase {
1497 Phase::Interpreting(Some(interp)) => interp.get_as(signal),
1498 Phase::Compiled(CompiledTier::Jit(jit)) => jit.get_as(signal),
1499 #[cfg(any(
1500 target_arch = "x86_64",
1501 feature = "arm64-codegen",
1502 target_arch = "aarch64"
1503 ))]
1504 Phase::Compiled(CompiledTier::Native(native)) => native.get_as(signal),
1505 Phase::Interpreting(None) => unreachable!("promoted backend left no interpreter"),
1506 }
1507 }
1508
1509 fn get_four_state(&self, signal: SignalRef) -> (BigUint, BigUint) {
1510 match &self.phase {
1511 Phase::Interpreting(Some(interp)) => interp.get_four_state(signal),
1512 Phase::Compiled(CompiledTier::Jit(jit)) => jit.get_four_state(signal),
1513 #[cfg(any(
1514 target_arch = "x86_64",
1515 feature = "arm64-codegen",
1516 target_arch = "aarch64"
1517 ))]
1518 Phase::Compiled(CompiledTier::Native(native)) => native.get_four_state(signal),
1519 Phase::Interpreting(None) => unreachable!("promoted backend left no interpreter"),
1520 }
1521 }
1522
1523 fn memory_as_ptr(&self) -> (*const u8, usize) {
1524 match &self.phase {
1525 Phase::Interpreting(Some(interp)) => interp.memory_as_ptr(),
1526 Phase::Compiled(CompiledTier::Jit(jit)) => jit.memory_as_ptr(),
1527 #[cfg(any(
1528 target_arch = "x86_64",
1529 feature = "arm64-codegen",
1530 target_arch = "aarch64"
1531 ))]
1532 Phase::Compiled(CompiledTier::Native(native)) => native.memory_as_ptr(),
1533 Phase::Interpreting(None) => unreachable!("promoted backend left no interpreter"),
1534 }
1535 }
1536
1537 fn memory_as_mut_ptr(&mut self) -> (*mut u8, usize) {
1538 match &mut self.phase {
1539 Phase::Interpreting(Some(interp)) => interp.memory_as_mut_ptr(),
1540 Phase::Compiled(CompiledTier::Jit(jit)) => jit.memory_as_mut_ptr(),
1541 #[cfg(any(
1542 target_arch = "x86_64",
1543 feature = "arm64-codegen",
1544 target_arch = "aarch64"
1545 ))]
1546 Phase::Compiled(CompiledTier::Native(native)) => native.memory_as_mut_ptr(),
1547 Phase::Interpreting(None) => unreachable!("promoted backend left no interpreter"),
1548 }
1549 }
1550
1551 fn memory_owner(&self) -> Option<Arc<dyn std::any::Any + Send + Sync>> {
1552 match &self.phase {
1553 Phase::Interpreting(Some(interp)) => interp.memory_owner(),
1554 Phase::Compiled(CompiledTier::Jit(jit)) => jit.memory_owner(),
1555 #[cfg(any(
1556 target_arch = "x86_64",
1557 feature = "arm64-codegen",
1558 target_arch = "aarch64"
1559 ))]
1560 Phase::Compiled(CompiledTier::Native(native)) => native.memory_owner(),
1561 Phase::Interpreting(None) => unreachable!("promoted backend left no interpreter"),
1562 }
1563 }
1564
1565 fn vcd_tracking_enabled(&self) -> bool {
1566 match &self.phase {
1567 Phase::Interpreting(Some(interp)) => interp.vcd_tracking_enabled(),
1568 Phase::Compiled(CompiledTier::Jit(jit)) => jit.vcd_tracking_enabled(),
1569 #[cfg(any(
1570 target_arch = "x86_64",
1571 feature = "arm64-codegen",
1572 target_arch = "aarch64"
1573 ))]
1574 Phase::Compiled(CompiledTier::Native(native)) => native.vcd_tracking_enabled(),
1575 Phase::Interpreting(None) => unreachable!("promoted backend left no interpreter"),
1576 }
1577 }
1578
1579 fn runtime_event_buffer_as_ptr(&self) -> (*const u8, usize) {
1580 match &self.phase {
1581 Phase::Interpreting(Some(interp)) => interp.runtime_event_buffer_as_ptr(),
1582 Phase::Compiled(CompiledTier::Jit(jit)) => jit.runtime_event_buffer_as_ptr(),
1583 #[cfg(any(
1584 target_arch = "x86_64",
1585 feature = "arm64-codegen",
1586 target_arch = "aarch64"
1587 ))]
1588 Phase::Compiled(CompiledTier::Native(native)) => native.runtime_event_buffer_as_ptr(),
1589 Phase::Interpreting(None) => unreachable!("promoted backend left no interpreter"),
1590 }
1591 }
1592
1593 fn runtime_event_buffer(&self) -> Option<Arc<RuntimeEventBuffer>> {
1594 match &self.phase {
1595 Phase::Interpreting(Some(interp)) => interp.runtime_event_buffer(),
1596 Phase::Compiled(CompiledTier::Jit(jit)) => Some(jit.runtime_event_buffer().clone()),
1597 #[cfg(any(
1598 target_arch = "x86_64",
1599 feature = "arm64-codegen",
1600 target_arch = "aarch64"
1601 ))]
1602 Phase::Compiled(CompiledTier::Native(native)) => native.runtime_event_buffer(),
1603 Phase::Interpreting(None) => unreachable!("promoted backend left no interpreter"),
1604 }
1605 }
1606
1607 fn set_comb_capture_event_enabled(&mut self, active_sites: &[bool]) {
1608 match &mut self.phase {
1609 Phase::Interpreting(Some(interp)) => {
1610 interp.set_comb_capture_event_enabled(active_sites)
1611 }
1612 Phase::Compiled(CompiledTier::Jit(jit)) => {
1613 jit.set_comb_capture_event_enabled(active_sites)
1614 }
1615 #[cfg(any(
1616 target_arch = "x86_64",
1617 feature = "arm64-codegen",
1618 target_arch = "aarch64"
1619 ))]
1620 Phase::Compiled(CompiledTier::Native(native)) => {
1621 native.set_comb_capture_event_enabled(active_sites)
1622 }
1623 Phase::Interpreting(None) => unreachable!("promoted backend left no interpreter"),
1624 }
1625 }
1626
1627 fn stable_region_size(&self) -> usize {
1628 match &self.phase {
1629 Phase::Interpreting(Some(interp)) => interp.stable_region_size(),
1630 Phase::Compiled(CompiledTier::Jit(jit)) => jit.stable_region_size(),
1631 #[cfg(any(
1632 target_arch = "x86_64",
1633 feature = "arm64-codegen",
1634 target_arch = "aarch64"
1635 ))]
1636 Phase::Compiled(CompiledTier::Native(native)) => native.stable_region_size(),
1637 Phase::Interpreting(None) => unreachable!("promoted backend left no interpreter"),
1638 }
1639 }
1640
1641 fn layout(&self) -> &MemoryLayout {
1642 match &self.phase {
1643 Phase::Interpreting(Some(interp)) => interp.layout(),
1644 Phase::Compiled(CompiledTier::Jit(jit)) => jit.layout(),
1645 #[cfg(any(
1646 target_arch = "x86_64",
1647 feature = "arm64-codegen",
1648 target_arch = "aarch64"
1649 ))]
1650 Phase::Compiled(CompiledTier::Native(native)) => native.layout(),
1651 Phase::Interpreting(None) => unreachable!("promoted backend left no interpreter"),
1652 }
1653 }
1654
1655 fn id_to_addr_slice(&self) -> &[AbsoluteAddr] {
1656 match &self.phase {
1657 Phase::Interpreting(Some(interp)) => interp.id_to_addr_slice(),
1658 Phase::Compiled(CompiledTier::Jit(jit)) => jit.id_to_addr_slice(),
1659 #[cfg(any(
1660 target_arch = "x86_64",
1661 feature = "arm64-codegen",
1662 target_arch = "aarch64"
1663 ))]
1664 Phase::Compiled(CompiledTier::Native(native)) => native.id_to_addr_slice(),
1665 Phase::Interpreting(None) => unreachable!("promoted backend left no interpreter"),
1666 }
1667 }
1668
1669 fn id_to_event_slice(&self) -> &[TieredEventRef] {
1670 &self.events
1671 }
1672
1673 fn num_events(&self) -> usize {
1674 match &self.phase {
1675 Phase::Interpreting(Some(interp)) => interp.num_events(),
1676 Phase::Compiled(CompiledTier::Jit(jit)) => jit.num_events(),
1677 #[cfg(any(
1678 target_arch = "x86_64",
1679 feature = "arm64-codegen",
1680 target_arch = "aarch64"
1681 ))]
1682 Phase::Compiled(CompiledTier::Native(native)) => native.num_events(),
1683 Phase::Interpreting(None) => unreachable!("promoted backend left no interpreter"),
1684 }
1685 }
1686
1687 fn clear_triggered_bits(&mut self) {
1688 match &mut self.phase {
1689 Phase::Interpreting(Some(interp)) => interp.clear_triggered_bits(),
1690 Phase::Compiled(CompiledTier::Jit(jit)) => jit.clear_triggered_bits(),
1691 #[cfg(any(
1692 target_arch = "x86_64",
1693 feature = "arm64-codegen",
1694 target_arch = "aarch64"
1695 ))]
1696 Phase::Compiled(CompiledTier::Native(native)) => native.clear_triggered_bits(),
1697 Phase::Interpreting(None) => unreachable!("promoted backend left no interpreter"),
1698 }
1699 }
1700
1701 fn mark_triggered_bit(&mut self, id: usize) {
1702 match &mut self.phase {
1703 Phase::Interpreting(Some(interp)) => interp.mark_triggered_bit(id),
1704 Phase::Compiled(CompiledTier::Jit(jit)) => jit.mark_triggered_bit(id),
1705 #[cfg(any(
1706 target_arch = "x86_64",
1707 feature = "arm64-codegen",
1708 target_arch = "aarch64"
1709 ))]
1710 Phase::Compiled(CompiledTier::Native(native)) => native.mark_triggered_bit(id),
1711 Phase::Interpreting(None) => unreachable!("promoted backend left no interpreter"),
1712 }
1713 }
1714
1715 fn get_triggered_bits(&self) -> bit_set::BitSet {
1716 match &self.phase {
1717 Phase::Interpreting(Some(interp)) => interp.get_triggered_bits(),
1718 Phase::Compiled(CompiledTier::Jit(jit)) => jit.get_triggered_bits(),
1719 #[cfg(any(
1720 target_arch = "x86_64",
1721 feature = "arm64-codegen",
1722 target_arch = "aarch64"
1723 ))]
1724 Phase::Compiled(CompiledTier::Native(native)) => native.get_triggered_bits(),
1725 Phase::Interpreting(None) => unreachable!("promoted backend left no interpreter"),
1726 }
1727 }
1728}
1729
1730#[cfg(test)]
1731mod tests {
1732 use super::*;
1733 use crate::backend::native::NativeBackend;
1734 use crate::{CodegenError, RuntimeEvent, Simulator, SimulatorBuilder, SimulatorErrorKind};
1735 use std::sync::atomic::{AtomicBool, Ordering};
1736
1737 const PIPELINE: &str = r#"
1739module Top (
1740 clk: input clock,
1741 rst: input reset,
1742 d: input logic<8>,
1743 q: output logic<8>,
1744) {
1745 var stage1: logic<8>;
1746 var stage2: logic<8>;
1747 always_ff (clk, rst) {
1748 if_reset {
1749 stage1 = 0;
1750 stage2 = 0;
1751 } else {
1752 stage1 = d;
1753 stage2 = stage1;
1754 }
1755 }
1756 assign q = stage2;
1757}
1758"#;
1759
1760 struct Gate(Arc<AtomicBool>);
1761
1762 impl Gate {
1763 fn closed() -> Self {
1764 Self(Arc::new(AtomicBool::new(false)))
1765 }
1766
1767 fn open(&self) {
1768 self.0.store(true, Ordering::SeqCst);
1769 }
1770 }
1771
1772 fn wait_for_gate_or_cancel(
1773 gate: &AtomicBool,
1774 cancel: &CompileCancel,
1775 ) -> Result<(), SimulatorError> {
1776 while !gate.load(Ordering::Acquire) {
1777 if cancel.is_cancelled() {
1778 return Err(super::super::compile_cancel::cancelled_error());
1779 }
1780 std::thread::sleep(std::time::Duration::from_millis(1));
1781 }
1782 Ok(())
1783 }
1784
1785 fn build_gated(gate: &Gate) -> Simulator<TieredBackend> {
1789 let worker_gate = gate.0.clone();
1790 SimulatorBuilder::<Simulator>::new(PIPELINE, "Top")
1791 .build_tiered_with_compiler(move |laid_out, options, cancel| {
1792 wait_for_gate_or_cancel(&worker_gate, cancel)?;
1793 {
1794 let image = NativeBackend::compile_image(laid_out, options)?;
1795 let shared = unsafe { SharedNativeCode::from_image(image)? };
1796 Ok(CompiledCode::Native(Arc::new(shared)))
1797 }
1798 })
1799 .unwrap()
1800 }
1801
1802 fn drive(sim: &mut Simulator<TieredBackend>, inputs: &[u8]) -> Vec<u8> {
1807 let clk = sim.event("clk");
1808 let rst = sim.signal("rst");
1809 let d = sim.signal("d");
1810
1811 sim.modify(|io| {
1812 io.set(rst, 0u8);
1813 io.set(d, inputs[0]);
1814 })
1815 .unwrap();
1816 sim.tick(clk).unwrap();
1817 sim.tick(clk).unwrap();
1818 sim.modify(|io| io.set(rst, 1u8)).unwrap();
1819
1820 let mut observed = Vec::new();
1821 for &value in inputs {
1822 sim.modify(|io| io.set(d, value)).unwrap();
1823 sim.tick(clk).unwrap();
1824 sim.tick(clk).unwrap();
1825 observed.push(sim.get_as::<u8>(sim.signal("q")));
1826 }
1827 observed
1828 }
1829
1830 fn reference_outputs(inputs: &[u8]) -> Vec<u8> {
1831 inputs.to_vec()
1835 }
1836
1837 #[test_case::test_case(false; "optimizing")]
1838 #[test_case::test_case(true; "baseline")]
1839 fn unpacked_array_works_across_promotion_on_strided_layout(baseline: bool) {
1840 let code = r#"
1844module Top (
1845 clk: input clock,
1846 we: input logic,
1847 waddr: input logic<3>,
1848 wdata: input logic<8>,
1849 raddr: input logic<3>,
1850 q: output logic<8>,
1851) {
1852 var mem: logic<8>[8];
1853 always_ff (clk) {
1854 if we {
1855 mem[waddr] = wdata;
1856 }
1857 }
1858 assign q = mem[raddr];
1859}
1860"#;
1861 let gate = Gate::closed();
1862 let worker_gate = gate.0.clone();
1863 let mut sim: Simulator<TieredBackend> = SimulatorBuilder::<Simulator>::new(code, "Top")
1864 .build_tiered_with_compiler(move |laid_out, options, cancel| {
1865 wait_for_gate_or_cancel(&worker_gate, cancel)?;
1866 {
1867 let mut options = options.clone();
1868 options.x86_options.baseline = baseline;
1869 options.x86_options.diagnostics.verify_mir_passes = true;
1870 options.x86_options.diagnostics.verify_regalloc = true;
1871 let image = NativeBackend::compile_image(laid_out, &options)?;
1872 let shared = unsafe { SharedNativeCode::from_image(image)? };
1873 Ok(CompiledCode::Native(Arc::new(shared)))
1874 }
1875 })
1876 .unwrap();
1877 let clk = sim.event("clk");
1878 let we = sim.signal("we");
1879 let waddr = sim.signal("waddr");
1880 let wdata = sim.signal("wdata");
1881 let raddr = sim.signal("raddr");
1882 let q = sim.signal("q");
1883
1884 for i in 0..8u8 {
1886 sim.modify(|io| {
1887 io.set(we, 1u8);
1888 io.set(waddr, i);
1889 io.set(wdata, i * 7 + 1);
1890 })
1891 .unwrap();
1892 sim.tick(clk).unwrap();
1893 }
1894 sim.modify(|io| io.set(we, 0u8)).unwrap();
1895
1896 let read_back = |sim: &mut Simulator<TieredBackend>, index: u8| -> u8 {
1897 sim.modify(|io| io.set(raddr, index)).unwrap();
1898 sim.get_as::<u8>(q)
1899 };
1900
1901 for i in 0..8u8 {
1903 assert_eq!(read_back(&mut sim, i), i * 7 + 1);
1904 }
1905
1906 gate.open();
1908 let deadline = std::time::Instant::now() + std::time::Duration::from_secs(60);
1909 while !sim.is_compiled() && std::time::Instant::now() < deadline {
1910 sim.tick(clk).unwrap();
1911 std::thread::sleep(std::time::Duration::from_millis(1));
1912 }
1913 assert!(sim.is_compiled());
1914
1915 for i in 0..8u8 {
1917 assert_eq!(read_back(&mut sim, i), i * 7 + 1, "element {i}");
1918 }
1919
1920 sim.modify(|io| {
1922 io.set(we, 1u8);
1923 io.set(waddr, 3u8);
1924 io.set(wdata, 250u8);
1925 })
1926 .unwrap();
1927 sim.tick(clk).unwrap();
1928 sim.modify(|io| io.set(we, 0u8)).unwrap();
1929 assert_eq!(read_back(&mut sim, 3), 250u8);
1930 }
1931
1932 #[test_case::test_case(false; "optimizing")]
1937 #[test_case::test_case(true; "baseline")]
1938 fn four_state_unpacked_array_planes_initialize_and_survive_promotion(baseline: bool) {
1939 let code = r#"
1940module Top (
1941 clk: input clock,
1942 we: input logic,
1943 waddr: input logic<2>,
1944 wdata: input logic<8>,
1945 raddr: input logic<2>,
1946 q: output logic<8>,
1947) {
1948 var mem: logic<8>[4];
1949 always_ff (clk) {
1950 if we {
1951 mem[waddr] = wdata;
1952 }
1953 }
1954 assign q = mem[raddr];
1955}
1956"#;
1957 let gate = Gate::closed();
1958 let worker_gate = gate.0.clone();
1959 let mut sim: Simulator<TieredBackend> = SimulatorBuilder::<Simulator>::new(code, "Top")
1960 .four_state(true)
1961 .build_tiered_with_compiler(move |laid_out, options, cancel| {
1962 wait_for_gate_or_cancel(&worker_gate, cancel)?;
1963 let mut options = options.clone();
1964 options.x86_options.baseline = baseline;
1965 options.x86_options.diagnostics.verify_mir_passes = true;
1966 options.x86_options.diagnostics.verify_regalloc = true;
1967 let image = NativeBackend::compile_image(laid_out, &options)?;
1968 let shared = unsafe { SharedNativeCode::from_image(image)? };
1969 Ok(CompiledCode::Native(Arc::new(shared)))
1970 })
1971 .unwrap();
1972 let clk = sim.event("clk");
1973 let we = sim.signal("we");
1974 let waddr = sim.signal("waddr");
1975 let wdata = sim.signal("wdata");
1976 let raddr = sim.signal("raddr");
1977 let q = sim.signal("q");
1978
1979 for i in 0..4u8 {
1981 sim.modify(|io| {
1982 io.set(we, 1u8);
1983 io.set(waddr, i);
1984 io.set(wdata, i * 60 + 5);
1985 })
1986 .unwrap();
1987 sim.tick(clk).unwrap();
1988 }
1989 sim.modify(|io| io.set(we, 0u8)).unwrap();
1990
1991 for i in 0..4u8 {
1993 sim.modify(|io| io.set(raddr, i)).unwrap();
1994 assert_eq!(sim.get_as::<u8>(q), i * 60 + 5);
1995 }
1996
1997 gate.open();
1999 let deadline = std::time::Instant::now() + std::time::Duration::from_secs(60);
2000 while !sim.is_compiled() && std::time::Instant::now() < deadline {
2001 sim.tick(clk).unwrap();
2002 std::thread::sleep(std::time::Duration::from_millis(2));
2003 }
2004 assert!(sim.is_compiled());
2005
2006 for i in 0..4u8 {
2008 sim.modify(|io| io.set(raddr, i)).unwrap();
2009 assert_eq!(
2010 sim.get_as::<u8>(q),
2011 i * 60 + 5,
2012 "element {i} after native promotion"
2013 );
2014 }
2015
2016 sim.modify(|io| {
2018 io.set(we, 1u8);
2019 io.set(waddr, 2u8);
2020 io.set(wdata, 200u8);
2021 })
2022 .unwrap();
2023 sim.tick(clk).unwrap();
2024 sim.modify(|io| io.set(we, 0u8)).unwrap();
2025 sim.modify(|io| io.set(raddr, 2u8)).unwrap();
2026 assert_eq!(sim.get_as::<u8>(q), 200u8, "native-tier write");
2027
2028 sim.modify(|io| {
2031 io.set(we, 1u8);
2032 io.set_four_state(wdata, 0xa5u8.into(), 0x3cu8.into());
2033 })
2034 .unwrap();
2035 sim.tick(clk).unwrap();
2036 sim.modify(|io| io.set(we, 0u8)).unwrap();
2037 assert_eq!(sim.get_four_state(q), (0xa5u8.into(), 0x3cu8.into()));
2038 }
2039
2040 #[test]
2044 fn strided_interp_matches_packed_interp_for_four_state_array() {
2045 let code = r#"
2046module Top (
2047 clk: input clock,
2048 we: input logic,
2049 waddr: input logic<2>,
2050 wdata: input logic<8>,
2051 raddr: input logic<2>,
2052 q: output logic<8>,
2053) {
2054 var mem: logic<8>[4];
2055 always_ff (clk) {
2056 if we {
2057 mem[waddr] = wdata;
2058 }
2059 }
2060 assign q = mem[raddr];
2061}
2062"#;
2063 let mut packed_sim = SimulatorBuilder::<Simulator>::new(code, "Top")
2067 .four_state(true)
2068 .build_interpreter()
2069 .unwrap();
2070
2071 let gate = Gate::closed();
2073 let worker_gate = gate.0.clone();
2074 let mut strided_sim: Simulator<TieredBackend> =
2075 SimulatorBuilder::<Simulator>::new(code, "Top")
2076 .four_state(true)
2077 .build_tiered_with_compiler(move |laid_out, options, cancel| {
2078 wait_for_gate_or_cancel(&worker_gate, cancel)?;
2079 {
2080 let image = NativeBackend::compile_image(laid_out, options)?;
2081 let shared = unsafe { SharedNativeCode::from_image(image)? };
2082 Ok(CompiledCode::Native(Arc::new(shared)))
2083 }
2084 })
2085 .unwrap();
2086
2087 let packed_clk = packed_sim.event("clk");
2088 let strided_clk = strided_sim.event("clk");
2089 let packed_we = packed_sim.signal("we");
2090 let strided_we = strided_sim.signal("we");
2091 let packed_waddr = packed_sim.signal("waddr");
2092 let strided_waddr = strided_sim.signal("waddr");
2093 let packed_wdata = packed_sim.signal("wdata");
2094 let strided_wdata = strided_sim.signal("wdata");
2095 let packed_raddr = packed_sim.signal("raddr");
2096 let strided_raddr = strided_sim.signal("raddr");
2097
2098 for i in 0..4u8 {
2100 packed_sim
2102 .modify(|io| {
2103 io.set(packed_we, 1u8);
2104 io.set(packed_waddr, i);
2105 io.set(packed_wdata, i * 60 + 5);
2106 })
2107 .unwrap();
2108 packed_sim.tick(packed_clk).unwrap();
2109 strided_sim
2111 .modify(|io| {
2112 io.set(strided_we, 1u8);
2113 io.set(strided_waddr, i);
2114 io.set(strided_wdata, i * 60 + 5);
2115 })
2116 .unwrap();
2117 strided_sim.tick(strided_clk).unwrap();
2118 }
2119 packed_sim.modify(|io| io.set(packed_we, 0u8)).unwrap();
2120 strided_sim.modify(|io| io.set(strided_we, 0u8)).unwrap();
2121
2122 for i in 0..4u8 {
2124 packed_sim.modify(|io| io.set(packed_raddr, i)).unwrap();
2125 strided_sim.modify(|io| io.set(strided_raddr, i)).unwrap();
2126 let pv = packed_sim.get_as::<u8>(packed_sim.signal("q"));
2127 let sv = strided_sim.get_as::<u8>(strided_sim.signal("q"));
2128 assert_eq!(
2129 pv, sv,
2130 "element {i} diverges between Packed and ElementStrided interp"
2131 );
2132 }
2133 }
2134
2135 #[test]
2136 fn outputs_match_reference_when_promotion_never_happens() {
2137 let inputs: Vec<u8> = (10..40).collect();
2138 let gate = Gate::closed();
2139 let mut sim = build_gated(&gate);
2140 let observed = drive(&mut sim, &inputs);
2141 assert_eq!(observed, reference_outputs(&inputs));
2142 assert!(!sim.is_compiled());
2143 assert!(sim.promotion_error().is_none(), "gate still closed");
2144 }
2145
2146 #[test]
2147 fn outputs_match_reference_when_promotion_lands_mid_run() {
2148 let inputs: Vec<u8> = (10..40).collect();
2149 let gate = Gate::closed();
2150 let mut sim = build_gated(&gate);
2151
2152 let clk = sim.event("clk");
2153 let rst = sim.signal("rst");
2154 let d = sim.signal("d");
2155 sim.modify(|io| {
2156 io.set(rst, 0u8);
2157 io.set(d, inputs[0]);
2158 })
2159 .unwrap();
2160 sim.tick(clk).unwrap();
2161 sim.tick(clk).unwrap();
2162 sim.modify(|io| io.set(rst, 1u8)).unwrap();
2163
2164 let mut observed = Vec::new();
2166 for &value in &inputs[..inputs.len() / 2] {
2167 sim.modify(|io| io.set(d, value)).unwrap();
2168 sim.tick(clk).unwrap();
2169 sim.tick(clk).unwrap();
2170 observed.push(sim.get_as::<u8>(sim.signal("q")));
2171 }
2172
2173 gate.open();
2175 let deadline = std::time::Instant::now() + std::time::Duration::from_secs(60);
2176 while !sim.is_compiled() && std::time::Instant::now() < deadline {
2177 sim.tick(clk).unwrap();
2179 std::thread::sleep(std::time::Duration::from_millis(1));
2180 }
2181 assert!(sim.is_compiled());
2182 assert!(sim.promotion_error().is_none());
2183
2184 for &value in &inputs[inputs.len() / 2..] {
2185 sim.modify(|io| io.set(d, value)).unwrap();
2186 sim.tick(clk).unwrap();
2187 sim.tick(clk).unwrap();
2188 observed.push(sim.get_as::<u8>(sim.signal("q")));
2189 }
2190
2191 assert_eq!(observed, reference_outputs(&inputs));
2192 }
2193
2194 #[test]
2195 fn waveform_activity_survives_promotion_before_dump() {
2196 for expose_raw in [false, true] {
2197 let dir = tempfile::tempdir().unwrap();
2198 let path = dir.path().join("promotion.vcd");
2199 let gate = Gate::closed();
2200 let worker_gate = gate.0.clone();
2201 let mut sim: Simulator<TieredBackend> =
2202 SimulatorBuilder::<Simulator>::new(PIPELINE, "Top")
2203 .vcd(&path)
2204 .build_tiered_with_compiler(move |laid_out, options, cancel| {
2205 wait_for_gate_or_cancel(&worker_gate, cancel)?;
2206 let image = NativeBackend::compile_image(laid_out, options)?;
2207 let shared = unsafe { SharedNativeCode::from_image(image)? };
2208 Ok(CompiledCode::Native(Arc::new(shared)))
2209 })
2210 .unwrap();
2211 let descs = sim.build_vcd_descs(false);
2212 let mut reference = crate::VcdWriter::from_writer(Vec::new(), &descs);
2213 let dump = |sim: &mut Simulator<TieredBackend>,
2214 reference: &mut crate::VcdWriter<Vec<u8>>,
2215 time| {
2216 sim.dump(time);
2217 let (ptr, size) = sim.memory_as_ptr();
2218 reference
2219 .dump(time, unsafe { std::slice::from_raw_parts(ptr, size) })
2220 .unwrap();
2221 };
2222 let clk = sim.event("clk");
2223 let rst = sim.signal("rst");
2224 let d = sim.signal("d");
2225 sim.set(rst, 0u8);
2226 sim.tick(clk).unwrap();
2227 dump(&mut sim, &mut reference, 0);
2228 let raw = expose_raw.then(|| {
2229 let (ptr, size) = sim.memory_as_ptr();
2230 let snapshot = unsafe { std::slice::from_raw_parts(ptr, size) }.to_vec();
2232 let (ptr, _) = sim.memory_as_mut_ptr();
2233 unsafe {
2234 std::ptr::copy_nonoverlapping(snapshot.as_ptr(), ptr, size);
2235 }
2236 ptr
2237 });
2238 sim.set(rst, 1u8);
2239 sim.set(d, 0xa5u8);
2240 sim.tick(clk).unwrap();
2241 sim.tick(clk).unwrap();
2242 assert!(!sim.is_compiled());
2243 gate.open();
2245 let deadline = std::time::Instant::now() + std::time::Duration::from_secs(60);
2246 while !sim.is_compiled() && std::time::Instant::now() < deadline {
2247 sim.tick(clk).unwrap();
2248 std::thread::sleep(std::time::Duration::from_millis(1));
2249 }
2250 assert!(sim.is_compiled(), "{:?}", sim.promotion_error());
2251 dump(&mut sim, &mut reference, 1);
2252 let before = sim.vcd_statistics().unwrap().comparisons;
2253 if let Some(raw) = raw {
2254 unsafe {
2257 *raw.add(d.offset) = 0x3c;
2258 }
2259 }
2260 dump(&mut sim, &mut reference, 2);
2261 assert_eq!(
2262 sim.vcd_statistics().unwrap().comparisons,
2263 before + if expose_raw { descs.len() as u64 } else { 0 }
2264 );
2265 sim.flush_vcd().unwrap();
2266 let parse = |bytes: Vec<u8>| {
2267 let mut parser = vcd::Parser::new(bytes.as_slice());
2268 parser.parse_header().unwrap();
2269 parser.map(Result::unwrap).collect::<Vec<_>>()
2270 };
2271 assert_eq!(
2272 parse(std::fs::read(path).unwrap()),
2273 parse(reference.into_inner().unwrap())
2274 );
2275 }
2276 }
2277
2278 #[test]
2279 fn compilation_failure_keeps_the_interpreter() {
2280 let mut sim: Simulator<TieredBackend> = SimulatorBuilder::<Simulator>::new(PIPELINE, "Top")
2281 .build_tiered_with_compiler(|_, _, _| {
2282 Err(SimulatorError::from(crate::RuntimeErrorCode::InternalError))
2283 })
2284 .unwrap();
2285
2286 let inputs: Vec<u8> = (10..20).collect();
2287 let observed = drive(&mut sim, &inputs);
2288 assert_eq!(observed, reference_outputs(&inputs));
2289 assert!(!sim.is_compiled());
2290
2291 let clk = sim.event("clk");
2294 let deadline = std::time::Instant::now() + std::time::Duration::from_secs(30);
2295 while sim.promotion_error().is_none() && std::time::Instant::now() < deadline {
2296 sim.tick(clk).unwrap();
2297 std::thread::sleep(std::time::Duration::from_millis(1));
2298 }
2299 assert!(sim.promotion_error().is_some());
2300 }
2301
2302 #[test]
2303 fn runtime_events_stay_continuous_across_promotion() {
2304 let code = r#"
2305module Top (
2306 clk: input clock,
2307 cnt: output logic<8>,
2308) {
2309 var c: logic<8>;
2310 always_ff (clk) {
2311 c = c + 1;
2312 $display("tick %0d", c);
2313 }
2314 assign cnt = c;
2315}
2316"#;
2317 let gate = Gate::closed();
2318 let worker_gate = gate.0.clone();
2319 let mut sim: Simulator<TieredBackend> = SimulatorBuilder::<Simulator>::new(code, "Top")
2320 .build_tiered_with_compiler(move |laid_out, options, cancel| {
2321 wait_for_gate_or_cancel(&worker_gate, cancel)?;
2322 {
2323 let image = NativeBackend::compile_image(laid_out, options)?;
2324 let shared = unsafe { SharedNativeCode::from_image(image)? };
2325 Ok(CompiledCode::Native(Arc::new(shared)))
2326 }
2327 })
2328 .unwrap();
2329 let clk = sim.event("clk");
2330
2331 for _ in 0..5 {
2334 sim.tick(clk).unwrap();
2335 }
2336 let pre = sim.drain_runtime_events();
2337 assert_eq!(pre.len(), 5);
2338 for (index, event) in pre.iter().enumerate() {
2339 let RuntimeEvent::Display { message } = event else {
2340 panic!("unexpected event {event:?}");
2341 };
2342 assert_eq!(message.as_str(), format!("tick {index}"));
2343 }
2344
2345 gate.open();
2346 let deadline = std::time::Instant::now() + std::time::Duration::from_secs(60);
2347 while !sim.is_compiled() && std::time::Instant::now() < deadline {
2348 sim.tick(clk).unwrap();
2349 std::thread::sleep(std::time::Duration::from_millis(1));
2350 }
2351 assert!(sim.is_compiled());
2352 let _ = sim.drain_runtime_events();
2354
2355 const POST_TICKS: u32 = 8;
2357 for _ in 0..POST_TICKS {
2358 sim.tick(clk).unwrap();
2359 }
2360 let base = sim
2361 .get_as::<u8>(sim.signal("cnt"))
2362 .wrapping_sub(POST_TICKS as u8);
2363 let post = sim.drain_runtime_events();
2364 assert_eq!(post.len(), POST_TICKS as usize);
2365 for (index, event) in post.iter().enumerate() {
2366 let RuntimeEvent::Display { message } = event else {
2367 panic!("unexpected event {event:?}");
2368 };
2369 assert_eq!(
2370 message.as_str(),
2371 format!("tick {}", base.wrapping_add(index as u8))
2372 );
2373 }
2374 }
2375
2376 #[test]
2380 fn cancel_background_compilation_stays_on_the_interpreter() {
2381 let mut sim: Simulator<TieredBackend> = SimulatorBuilder::<Simulator>::new(PIPELINE, "Top")
2382 .build_tiered_with_compiler(|laid_out, options, cancel| {
2383 while !cancel.is_cancelled() {
2384 std::thread::sleep(std::time::Duration::from_millis(1));
2385 }
2386 let image = NativeBackend::compile_image_with_cancel(laid_out, options, cancel)?;
2387 let shared = unsafe { SharedNativeCode::from_image(image)? };
2388 Ok(CompiledCode::Native(Arc::new(shared)))
2389 })
2390 .unwrap();
2391
2392 assert!(sim.cancel_background_compilation());
2393
2394 let clk = sim.event("clk");
2395 let deadline = std::time::Instant::now() + std::time::Duration::from_secs(30);
2396 while sim.promotion_error().is_none() && std::time::Instant::now() < deadline {
2397 sim.tick(clk).unwrap();
2398 std::thread::sleep(std::time::Duration::from_millis(1));
2399 }
2400 assert!(!sim.is_compiled());
2401 match sim.promotion_error() {
2402 Some(error) => assert!(matches!(
2403 error.kind(),
2404 SimulatorErrorKind::Codegen(CodegenError::Cancelled)
2405 )),
2406 None => panic!("cancellation was not reported through promotion_error"),
2407 }
2408
2409 let inputs: Vec<u8> = (0..8).collect();
2411 assert_eq!(drive(&mut sim, &inputs), reference_outputs(&inputs));
2412 }
2413
2414 #[test]
2416 fn drop_cancels_pending_background_compilation() {
2417 let saw_cancel = Arc::new(AtomicBool::new(false));
2418 let worker_saw_cancel = Arc::clone(&saw_cancel);
2419 let sim: Simulator<TieredBackend> = SimulatorBuilder::<Simulator>::new(PIPELINE, "Top")
2420 .build_tiered_with_compiler(move |_laid_out, _options, cancel| {
2421 while !cancel.is_cancelled() {
2422 std::thread::sleep(std::time::Duration::from_millis(1));
2423 }
2424 worker_saw_cancel.store(true, Ordering::SeqCst);
2425 Err(SimulatorError::new(SimulatorErrorKind::Codegen(
2426 CodegenError::Cancelled,
2427 )))
2428 })
2429 .unwrap();
2430 drop(sim);
2431 assert!(saw_cancel.load(Ordering::Acquire));
2432 }
2433
2434 #[test]
2437 fn tier_promotion_never_skips_background_compilation() {
2438 let invoked = Arc::new(AtomicBool::new(false));
2439 let worker_invoked = Arc::clone(&invoked);
2440 let mut sim: Simulator<TieredBackend> = SimulatorBuilder::<Simulator>::new(PIPELINE, "Top")
2441 .tier_promotion(crate::TierPromotion::Never)
2442 .build_tiered_with_compiler(move |_laid_out, _options, _cancel| {
2443 worker_invoked.store(true, Ordering::SeqCst);
2444 Err(SimulatorError::from(crate::RuntimeErrorCode::InternalError))
2445 })
2446 .unwrap();
2447
2448 std::thread::sleep(std::time::Duration::from_millis(50));
2451 assert!(!invoked.load(Ordering::SeqCst));
2452 assert!(!sim.is_compiled());
2453 assert!(sim.promotion_error().is_none());
2454 let before = sim.tiered_execution_stats();
2455 assert_eq!(before.tier, TieredExecutionTier::Interpreter);
2456 assert_eq!(before.promotion, TieredPromotionStatus::Disabled);
2457 assert_eq!(before.compiled_evaluations, 0);
2458
2459 let inputs: Vec<u8> = (0..8).collect();
2461 assert_eq!(drive(&mut sim, &inputs), reference_outputs(&inputs));
2462 let after = sim.tiered_execution_stats();
2463 assert!(after.interpreted_evaluations > before.interpreted_evaluations);
2464 assert_eq!(after.compiled_evaluations, 0);
2465 }
2466
2467 #[test]
2470 fn tier_promotion_after_steps_defers_adoption() {
2471 const THRESHOLD: u64 = 1000;
2472 let sim_build: Simulator<TieredBackend> =
2473 SimulatorBuilder::<Simulator>::new(PIPELINE, "Top")
2474 .tier_promotion(crate::TierPromotion::AfterSteps(THRESHOLD))
2475 .build_tiered_with_compiler(|laid_out, options, _cancel| {
2476 let image = NativeBackend::compile_image(laid_out, options)?;
2477 let shared = unsafe { SharedNativeCode::from_image(image)? };
2478 Ok(CompiledCode::Native(Arc::new(shared)))
2479 })
2480 .unwrap();
2481 let mut sim = sim_build;
2482 let clk = sim.event("clk");
2483 let initial = sim.tiered_execution_stats();
2484 assert!(initial.interpreted_evaluations < THRESHOLD);
2485
2486 for _ in 0..4 {
2489 sim.tick(clk).unwrap();
2490 }
2491 assert!(
2492 !sim.is_compiled(),
2493 "adopted before the interpreted-step threshold"
2494 );
2495
2496 let deadline = std::time::Instant::now() + std::time::Duration::from_secs(60);
2498 while !sim.is_compiled() && std::time::Instant::now() < deadline {
2499 sim.tick(clk).unwrap();
2500 std::thread::sleep(std::time::Duration::from_millis(1));
2501 }
2502 assert!(sim.is_compiled());
2503 assert!(sim.promotion_error().is_none());
2504 let promoted = sim.tiered_execution_stats();
2505 assert_eq!(promoted.tier, TieredExecutionTier::Compiled);
2506 assert_eq!(promoted.promotion, TieredPromotionStatus::Promoted);
2507 assert!(promoted.interpreted_evaluations >= THRESHOLD);
2508 assert_eq!(
2509 promoted.promoted_after_interpreted_evaluations,
2510 Some(promoted.interpreted_evaluations)
2511 );
2512 assert!(promoted.threshold_deferrals > 0);
2513
2514 sim.tick(clk).unwrap();
2515 assert!(sim.tiered_execution_stats().compiled_evaluations > promoted.compiled_evaluations);
2516 }
2517
2518 #[test]
2523 fn cranelift_promotion_covers_its_image_requirement_without_moving_the_image() {
2524 use std::sync::Mutex;
2525
2526 let gate = Gate::closed();
2527 let worker_gate = gate.0.clone();
2528 let observed: Arc<Mutex<Option<(usize, usize)>>> = Arc::new(Mutex::new(None));
2529 let worker_observed = observed.clone();
2530
2531 let mut sim: Simulator<TieredBackend> = SimulatorBuilder::<Simulator>::new(PIPELINE, "Top")
2532 .build_tiered_with_compiler(move |laid_out, options, cancel| {
2533 wait_for_gate_or_cancel(&worker_gate, cancel)?;
2534 let shared = Arc::new(JitBackend::compile(laid_out, options, None)?);
2535 let code = CompiledCode::Cranelift(Arc::clone(&shared));
2536 *worker_observed.lock().unwrap() = Some((
2538 shared.layout.merged_total_size.div_ceil(8),
2539 code.required_image_words(),
2540 ));
2541 Ok(code)
2542 })
2543 .unwrap();
2544
2545 let clk = sim.event("clk");
2546 for _ in 0..2 {
2547 sim.tick(clk).unwrap();
2548 }
2549 let (base_before, _) = sim.memory_as_ptr();
2550
2551 gate.open();
2552 let deadline = std::time::Instant::now() + std::time::Duration::from_secs(60);
2553 while !sim.is_compiled() && std::time::Instant::now() < deadline {
2554 sim.tick(clk).unwrap();
2555 std::thread::sleep(std::time::Duration::from_millis(1));
2556 }
2557 assert!(sim.is_compiled());
2558 assert!(sim.promotion_error().is_none());
2559
2560 let (adopt_target, required) = observed.lock().unwrap().expect("compiler ran");
2561 assert!(
2562 required >= adopt_target,
2563 "required_image_words ({required}) must cover the Cranelift \
2564 adoption resize target ({adopt_target})"
2565 );
2566
2567 let (base_after, _) = sim.memory_as_ptr();
2568 assert_eq!(
2569 base_before, base_after,
2570 "promotion must not move the live memory image"
2571 );
2572 }
2573
2574 #[cfg(all(target_arch = "x86_64", not(feature = "arm64-codegen")))]
2575 fn build_gated_two_stage(
2576 baseline_gate: &Gate,
2577 upgrade_gate: &Gate,
2578 fail_upgrade: bool,
2579 ) -> (Simulator<TieredBackend>, mpsc::Receiver<()>) {
2580 let (layout_sender, layout_receiver) = mpsc::channel();
2583 let code = PIPELINE.replace(
2584 "assign q = stage2;",
2585 "always_comb { q = stage2; $display(\"q %0d\", q); }",
2586 );
2587 let mut sim = SimulatorBuilder::<Simulator>::new(&code, "Top")
2588 .build_tiered_with_compiler(move |laid_out, options, _| {
2589 layout_sender
2590 .send((laid_out.clone(), options.clone()))
2591 .unwrap();
2592 Err(SimulatorError::from(crate::RuntimeErrorCode::InternalError))
2593 })
2594 .unwrap();
2595 let (laid_out, mut baseline_options) = layout_receiver
2596 .recv_timeout(Duration::from_secs(60))
2597 .unwrap();
2598 let mut optimizing_options = baseline_options.clone();
2599 baseline_options.x86_options.baseline = true;
2600 optimizing_options.x86_options.baseline = false;
2601 let worker_baseline_gate = baseline_gate.0.clone();
2602 let worker_upgrade_gate = upgrade_gate.0.clone();
2603 let (baseline_ready_sender, baseline_ready_receiver) = mpsc::channel();
2604 sim.backend = TieredBackend::with_two_stage_compiler(
2605 &laid_out,
2606 &baseline_options,
2607 optimizing_options,
2608 move |laid_out, options, cancel| {
2609 if options.x86_options.baseline {
2610 wait_for_gate_or_cancel(&worker_baseline_gate, cancel)?;
2611 } else {
2612 baseline_ready_sender.send(()).unwrap();
2614 wait_for_gate_or_cancel(&worker_upgrade_gate, cancel)?;
2615 if fail_upgrade {
2616 return Err(SimulatorError::from(crate::RuntimeErrorCode::InternalError));
2617 }
2618 }
2619 let image = NativeBackend::compile_image_with_cancel(laid_out, options, cancel)?;
2620 let shared = unsafe { SharedNativeCode::from_image(image)? };
2621 if !options.x86_options.baseline {
2622 baseline_ready_sender.send(()).unwrap();
2623 }
2624 Ok(CompiledCode::Native(Arc::new(shared)))
2625 },
2626 );
2627 (sim, baseline_ready_receiver)
2628 }
2629
2630 #[cfg(all(target_arch = "x86_64", not(feature = "arm64-codegen")))]
2631 fn adopted_native_code(backend: &TieredBackend) -> Arc<SharedNativeCode> {
2632 let Phase::Compiled(CompiledTier::Native(native)) = &backend.phase else {
2633 panic!("native code was not adopted");
2634 };
2635 native.shared_code()
2636 }
2637
2638 #[cfg(all(target_arch = "x86_64", not(feature = "arm64-codegen")))]
2639 #[test]
2640 fn native_two_stage_preserves_live_pipeline_across_split_apply() {
2641 let baseline_gate = Gate::closed();
2642 let upgrade_gate = Gate::closed();
2643 let (mut sim, baseline_ready) = build_gated_two_stage(&baseline_gate, &upgrade_gate, false);
2644 let clk = sim.event("clk");
2645 let rst = sim.signal("rst");
2646 let d = sim.signal("d");
2647 let q = sim.signal("q");
2648 let backend = &mut sim.backend;
2649 let base = backend.memory_as_ptr().0;
2650 let events: Vec<_> = backend
2651 .id_to_event_slice()
2652 .iter()
2653 .map(|e| (e.addr(), e.id()))
2654 .collect();
2655 backend.set(rst, 0u8);
2656 backend.eval_apply_ff_at(clk).unwrap();
2657 backend.set(rst, 1u8);
2658 backend.set(d, 17u8);
2659 backend.eval_apply_ff_at(clk).unwrap();
2660 backend.eval_comb().unwrap();
2661 assert_eq!(backend.get_as::<u8>(q), 0);
2662
2663 baseline_gate.open();
2664 baseline_ready
2665 .recv_timeout(Duration::from_secs(60))
2666 .unwrap();
2667 backend.maybe_promote();
2668 let baseline = adopted_native_code(backend);
2669 assert_eq!(backend.memory_as_ptr().0, base);
2670
2671 backend.set(d, 29u8);
2674 backend.eval_apply_ff_at(clk).unwrap();
2675 backend.eval_comb().unwrap();
2676 assert_eq!(backend.get_as::<u8>(q), 17, "after2");
2677
2678 let eval = backend.resolve_eval_only_event(&clk.addr()).unwrap();
2679 let apply = backend.resolve_apply_event(&clk.addr()).unwrap();
2680 backend.set(d, 43u8);
2681 backend.eval_only_ff_at(eval).unwrap();
2682 upgrade_gate.open();
2683 backend.compiler_worker.take().unwrap().join().unwrap();
2686 backend.maybe_upgrade();
2687 assert!(Arc::ptr_eq(&baseline, &adopted_native_code(backend)));
2688 assert_eq!(backend.memory_as_ptr().0, base);
2689 backend.apply_ff_at(apply).unwrap();
2690 assert!(Arc::ptr_eq(&baseline, &adopted_native_code(backend)));
2691
2692 backend.set_comb_capture_event_enabled(&[true]);
2694 let capture_start = crate::simulator::runtime_event_write_seq_for_backend(backend);
2695 if let Phase::Compiled(compiled) = &mut backend.phase {
2696 compiled.eval_comb().unwrap();
2697 }
2698 assert_eq!(backend.get_as::<u8>(q), 29);
2699 let event_buffer = backend.runtime_event_buffer().unwrap();
2700 let (buffer_ptr, buffer_size) = backend.runtime_event_buffer_as_ptr();
2701 let queued = unsafe { std::slice::from_raw_parts(buffer_ptr, buffer_size) }.to_vec();
2702 let before = crate::simulator::runtime_event_write_seq_for_backend(backend);
2703 assert_eq!(before, capture_start + 1);
2704 backend.set_comb_capture_event_enabled(&[true]);
2707 backend.clear_triggered_bits();
2708 backend.mark_triggered_bit(clk.id());
2709 let triggered = backend.get_triggered_bits();
2710 backend.maybe_upgrade();
2711 assert!(!Arc::ptr_eq(&baseline, &adopted_native_code(backend)));
2712 assert_eq!(backend.memory_as_ptr().0, base);
2713 assert_eq!(backend.get_as::<u8>(q), 29);
2714 assert_eq!(backend.get_triggered_bits(), triggered);
2715 assert_eq!(
2716 backend.runtime_event_buffer_as_ptr(),
2717 (buffer_ptr, buffer_size)
2718 );
2719 assert!(Arc::ptr_eq(
2720 &event_buffer,
2721 &backend.runtime_event_buffer().unwrap()
2722 ));
2723 assert_eq!(
2724 unsafe { std::slice::from_raw_parts(buffer_ptr, buffer_size) },
2725 queued
2726 );
2727 assert_eq!(
2728 backend
2729 .id_to_event_slice()
2730 .iter()
2731 .map(|e| (e.addr(), e.id()))
2732 .collect::<Vec<_>>(),
2733 events
2734 );
2735 for (addr, id) in events {
2736 assert_eq!(backend.resolve_event(&addr).id(), id);
2737 }
2738
2739 backend.eval_comb().unwrap();
2740 assert_eq!(
2741 crate::simulator::runtime_event_write_seq_for_backend(backend),
2742 before + 1
2743 );
2744 backend.set_comb_capture_event_enabled(&[false]);
2745 backend.eval_comb().unwrap();
2746 assert_eq!(
2747 crate::simulator::runtime_event_write_seq_for_backend(backend),
2748 before + 1
2749 );
2750 backend.set(d, 61u8);
2753 backend.eval_apply_ff_at(clk).unwrap();
2754 backend.eval_comb().unwrap();
2755 assert_eq!(backend.get_as::<u8>(q), 43);
2756 backend.eval_apply_ff_at(clk).unwrap();
2757 backend.eval_comb().unwrap();
2758 assert_eq!(backend.get_as::<u8>(q), 61);
2759 assert_eq!(backend.memory_as_ptr().0, base);
2760 }
2761
2762 #[cfg(all(target_arch = "x86_64", not(feature = "arm64-codegen")))]
2763 #[test_case::test_case(false; "cancelled")]
2764 #[test_case::test_case(true; "failed")]
2765 fn unsuccessful_optimization_keeps_baseline_code(fail_upgrade: bool) {
2766 let baseline_gate = Gate::closed();
2767 let upgrade_gate = Gate::closed();
2768 let (mut sim, baseline_ready) =
2769 build_gated_two_stage(&baseline_gate, &upgrade_gate, fail_upgrade);
2770 let clk = sim.event("clk");
2771 let rst = sim.signal("rst");
2772 let d = sim.signal("d");
2773 let q = sim.signal("q");
2774 let backend = &mut sim.backend;
2775 backend.set(rst, 0u8);
2776 backend.eval_apply_ff_at(clk).unwrap();
2777 backend.set(rst, 1u8);
2778 backend.set(d, 37u8);
2779 backend.eval_apply_ff_at(clk).unwrap();
2780 baseline_gate.open();
2781 baseline_ready
2782 .recv_timeout(Duration::from_secs(60))
2783 .unwrap();
2784 backend.maybe_promote();
2785 let baseline = adopted_native_code(backend);
2786 let base = backend.memory_as_ptr().0;
2787 upgrade_gate.open();
2788 backend.compiler_worker.take().unwrap().join().unwrap();
2789 if fail_upgrade {
2790 backend.maybe_upgrade();
2791 } else {
2792 assert!(backend.cancel_background_compilation());
2793 }
2794 assert!(!backend.cancel_background_compilation());
2795 backend.set(d, 53u8);
2796 backend.eval_apply_ff_at(clk).unwrap();
2797 backend.eval_comb().unwrap();
2798 assert!(Arc::ptr_eq(&baseline, &adopted_native_code(backend)));
2799 assert_eq!(backend.get_as::<u8>(q), 37);
2800 backend.eval_apply_ff_at(clk).unwrap();
2801 backend.eval_comb().unwrap();
2802 assert_eq!(backend.get_as::<u8>(q), 53);
2803 assert_eq!(backend.memory_as_ptr().0, base);
2804 assert_eq!(
2805 backend.execution_stats().promotion,
2806 TieredPromotionStatus::Promoted
2807 );
2808 assert!(backend.promotion_error().is_none());
2809 }
2810}