Skip to content

Commit 242a9c0

Browse files
authored
Merge pull request #3154 from veryl-lang/pr/10-report-effective-engine
feat(simulator): report the engine a run actually used as degraded_modules
2 parents 9cfe579 + 7dda1f2 commit 242a9c0

5 files changed

Lines changed: 113 additions & 19 deletions

File tree

crates/simulator/src/ir.rs

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,14 @@ pub use variable::{
4040
pub use veryl_analyzer::ir::{Op, Type, VarId, VarPath};
4141
pub use veryl_analyzer::value::Value;
4242

43-
use crate::HashMap;
4443
use crate::backend::{self, BackendRegistry, CompiledWhole, DispatchOutcome};
44+
use crate::residency;
4545
use crate::simulator::SimProfile;
4646
use crate::simulator_error::SimulatorError;
47-
use std::sync::Arc;
48-
use std::sync::OnceLock;
47+
use crate::{HashMap, HashSet};
48+
use std::path::PathBuf;
49+
use std::sync::atomic::{AtomicBool, Ordering};
50+
use std::sync::{Arc, OnceLock};
4951

5052
use veryl_analyzer::ir as air;
5153
use veryl_analyzer::value::MaskCache;
@@ -126,15 +128,19 @@ pub struct Ir {
126128
/// Base directory for component file I/O (the project root). Relative
127129
/// reads resolve against it; relative writes go to a per-test output
128130
/// directory beneath it. `None` leaves paths process-CWD relative.
129-
pub component_file_base: Option<std::path::PathBuf>,
131+
pub component_file_base: Option<PathBuf>,
130132
/// See `Module::rtl_driven`.
131-
pub rtl_driven: crate::HashSet<VarId>,
133+
pub rtl_driven: HashSet<VarId>,
134+
/// A failed compile leaves the cell empty forever, so the fallback is
135+
/// taken every cycle; the residency table (a mutex) must be touched once.
136+
whole_comb_fallback_recorded: AtomicBool,
137+
pub(crate) whole_event_fallback_recorded: AtomicBool,
132138
}
133139

134140
/// A built component library on disk and the type name to look up in it.
135141
#[derive(Clone, Debug)]
136142
pub struct ComponentLibrary {
137-
pub path: std::path::PathBuf,
143+
pub path: PathBuf,
138144
pub type_name: String,
139145
}
140146

@@ -172,6 +178,8 @@ impl Ir {
172178
component_libraries: config.component_libraries.clone(),
173179
component_file_base: config.component_file_base.clone(),
174180
rtl_driven: module.rtl_driven,
181+
whole_comb_fallback_recorded: Default::default(),
182+
whole_event_fallback_recorded: Default::default(),
175183
};
176184
// Bake the WriteLogBuffer's heap-stable address into every
177185
// JIT-dispatched Compiled/CompiledBatch so emitted code can perform
@@ -373,7 +381,13 @@ impl Ir {
373381
DispatchOutcome::Done => {}
374382
DispatchOutcome::NotReady => {
375383
// Async compile not finished yet — drop to
376-
// Cranelift for this cycle.
384+
// Cranelift for this cycle (see `residency`).
385+
if !self
386+
.whole_comb_fallback_recorded
387+
.swap(true, Ordering::Relaxed)
388+
{
389+
residency::record_fallback("whole_comb", &self.name.to_string());
390+
}
377391
self.run_chunked_settle(mask_cache, profile);
378392
return;
379393
}
@@ -680,7 +694,7 @@ pub struct Config {
680694
/// name. Missing entries fall back to the static registry.
681695
pub component_libraries: std::collections::HashMap<String, ComponentLibrary>,
682696
/// See `Ir::component_file_base`.
683-
pub component_file_base: Option<std::path::PathBuf>,
697+
pub component_file_base: Option<PathBuf>,
684698
}
685699

686700
impl Config {

crates/simulator/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ pub mod file_table;
55
pub mod ir;
66
pub mod output_buffer;
77
pub mod random_table;
8+
pub mod residency;
89
pub mod simulator;
910
pub mod simulator_error;
1011
pub mod testbench;

crates/simulator/src/residency.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
//! Which engine a run actually used, as opposed to the one it asked for.
2+
//!
3+
//! A whole-comb / whole-event handle exists as soon as the emit is accepted,
4+
//! but its artifact lands asynchronously (`cc` runs off the critical path).
5+
//! Until then every dispatch returns `NotReady` and the module falls back to
6+
//! the per-chunk path — correct, but a different engine, and `--format json`
7+
//! names the *requested* backend, so a run can report `cc` while timing the
8+
//! fallback. What is recorded here surfaces as its `degraded_modules`.
9+
//!
10+
//! A module that never had an artifact to wait for (emit declined, below the
11+
//! AOT size threshold) holds no whole-* handle, so nothing is recorded for it.
12+
13+
use crate::HashSet;
14+
use std::sync::{Mutex, OnceLock};
15+
16+
fn seen() -> &'static Mutex<HashSet<String>> {
17+
static SEEN: OnceLock<Mutex<HashSet<String>>> = OnceLock::new();
18+
SEEN.get_or_init(|| Mutex::new(HashSet::default()))
19+
}
20+
21+
/// `kind` is the dispatch site (`"whole_comb"` / `"whole_event"`). Repeats
22+
/// collapse to one entry per (kind, module).
23+
pub fn record_fallback(kind: &str, module: &str) {
24+
let Ok(mut seen) = seen().lock() else { return };
25+
seen.insert(format!("{kind}:{module}"));
26+
}
27+
28+
/// Every `kind:module` that fell back, sorted. Empty means the run used the
29+
/// engine it asked for throughout.
30+
pub fn degraded_modules() -> Vec<String> {
31+
let Ok(seen) = seen().lock() else {
32+
return Vec::new();
33+
};
34+
let mut out: Vec<String> = seen.iter().cloned().collect();
35+
out.sort();
36+
out
37+
}
38+
39+
#[cfg(test)]
40+
mod tests {
41+
use super::*;
42+
43+
#[test]
44+
fn fallbacks_are_recorded_once_and_sorted() {
45+
record_fallback("whole_comb", "resid_test_b");
46+
record_fallback("whole_event", "resid_test_a");
47+
record_fallback("whole_comb", "resid_test_b");
48+
let listed: Vec<String> = degraded_modules()
49+
.into_iter()
50+
.filter(|s| s.contains("resid_test_"))
51+
.collect();
52+
assert_eq!(
53+
listed,
54+
["whole_comb:resid_test_b", "whole_event:resid_test_a"],
55+
"one entry per (kind, module), sorted"
56+
);
57+
}
58+
}

crates/simulator/src/simulator.rs

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::backend::CompiledWhole;
1+
use crate::backend::{CompiledWhole, DispatchOutcome};
22
use crate::component::loader::ComponentError;
33
use crate::component::runtime::{RuntimeComponent, build_components};
44
use crate::ir::write_log::{
@@ -8,11 +8,15 @@ use crate::ir::{
88
Event, Ir, ModuleVariables, Statement, Value, VarId, VarPath, dispatch_stmt_fast,
99
read_native_value, write_native_value,
1010
};
11+
use crate::residency;
1112
use crate::wave_dumper::{DumpVar, WaveDumper};
1213
use smallvec::SmallVec;
1314
use std::collections::{BTreeSet, HashMap};
1415
use std::str::FromStr;
1516
use std::sync::Arc;
17+
use std::sync::atomic::Ordering;
18+
#[cfg(feature = "profile")]
19+
use std::time::Instant;
1620
use veryl_analyzer::value::MaskCache;
1721

1822
#[cfg(feature = "profile")]
@@ -455,7 +459,7 @@ impl Simulator {
455459
pub fn ensure_comb_updated(&mut self) {
456460
if self.comb_dirty {
457461
#[cfg(feature = "profile")]
458-
let start = std::time::Instant::now();
462+
let start = Instant::now();
459463

460464
self.do_settle_comb();
461465
self.comb_dirty = false;
@@ -514,7 +518,7 @@ impl Simulator {
514518

515519
if self.comb_dirty {
516520
#[cfg(feature = "profile")]
517-
let start = std::time::Instant::now();
521+
let start = Instant::now();
518522

519523
self.do_settle_comb();
520524
self.comb_dirty = false;
@@ -690,7 +694,7 @@ impl Simulator {
690694
/// one pre-commit state and one commit.
691695
fn eval_event_stmts(&mut self, event: &Event) {
692696
#[cfg(feature = "profile")]
693-
let event_start = std::time::Instant::now();
697+
let event_start = Instant::now();
694698

695699
// Cache both the per-stmt list AND the whole-event AOT-C handle for
696700
// the current event, keyed on `last_event`. `event_statements` and
@@ -719,7 +723,6 @@ impl Simulator {
719723
// current values and pushes WriteLogEntries into the buffer
720724
// (3rd arg), exactly as the Cranelift event JIT does;
721725
// `ff_commit_from_log` below applies them.
722-
use crate::backend::DispatchOutcome;
723726
let dispatched = if let Some(wptr) = whole_event_ptr {
724727
// SAFETY: `wptr` = `Arc::as_ptr` of an `Arc` owned by
725728
// `self.ir.whole_events`, which is never mutated after `Ir`
@@ -734,10 +737,21 @@ impl Simulator {
734737
let validate = self.ir.aot_c_validate;
735738

736739
if !validate {
737-
matches!(
738-
whole.try_dispatch(ff_ptr, comb_ptr, log_ptr),
739-
DispatchOutcome::Done,
740-
)
740+
match whole.try_dispatch(ff_ptr, comb_ptr, log_ptr) {
741+
DispatchOutcome::Done => true,
742+
DispatchOutcome::NotReady => {
743+
// `false` degrades to the per-stmt path below
744+
// (see `residency`).
745+
if !self
746+
.ir
747+
.whole_event_fallback_recorded
748+
.swap(true, Ordering::Relaxed)
749+
{
750+
residency::record_fallback("whole_event", &self.ir.name.to_string());
751+
}
752+
false
753+
}
754+
}
741755
} else {
742756
// For validate, the wrapper compares the whole-event
743757
// dispatch against the per-stmt Cranelift path and panics
@@ -768,7 +782,7 @@ impl Simulator {
768782
/// Apply the accumulated write log to FF storage and reset the buffer.
769783
fn commit_event_log(&mut self) {
770784
#[cfg(feature = "profile")]
771-
let ff_start = std::time::Instant::now();
785+
let ff_start = Instant::now();
772786

773787
ff_commit_from_log(&mut self.ir.ff_values, &self.ir.write_log_buffer);
774788

@@ -1019,7 +1033,7 @@ impl Simulator {
10191033
// Whole-event backend, then capture its pushed entries + ff/comb.
10201034
if matches!(
10211035
whole.try_dispatch(ff_ptr, comb_ptr, log_ptr),
1022-
crate::backend::DispatchOutcome::NotReady,
1036+
DispatchOutcome::NotReady,
10231037
) {
10241038
return false;
10251039
}

crates/veryl/src/cmd_test.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,13 @@ fn random_seed() -> u64 {
3232
struct TestSuiteReport {
3333
/// Bump on any breaking change to the report shape.
3434
format_version: u32,
35+
/// The backend that was asked for; see `degraded_modules` for what ran.
3536
backend: String,
37+
/// `kind:module` for every module that fell back, empty when the run used
38+
/// `backend` throughout. A timing comparison is only meaningful when this
39+
/// is empty in both arms.
40+
#[serde(skip_serializing_if = "Vec::is_empty")]
41+
degraded_modules: Vec<String>,
3642
passed: i32,
3743
failed: i32,
3844
ignored: usize,
@@ -684,6 +690,7 @@ impl CmdTest {
684690
let report = TestSuiteReport {
685691
format_version: 1,
686692
backend: backend_name.to_string(),
693+
degraded_modules: veryl_simulator::residency::degraded_modules(),
687694
passed: success,
688695
failed: failure,
689696
ignored: ignored_count,

0 commit comments

Comments
 (0)