Skip to content

Commit 1dc2f94

Browse files
authored
Merge pull request #3157 from veryl-lang/fix/wave-vs-aot-c-localize
fix(simulator): waveform dumping turns comb localization off
2 parents 3226bad + b4050ab commit 1dc2f94

4 files changed

Lines changed: 62 additions & 19 deletions

File tree

crates/simulator/src/backend/aot_c.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,20 @@ use crate::backend::{Backend, CompileCtx, CompiledWhole, DispatchOutcome};
1313
use crate::ir::{Event, ProtoStatement};
1414
use std::sync::Arc;
1515

16+
/// Turn chunk-local comb localization off for this process.
17+
///
18+
/// Localization keeps a comb signal in a C local and leaves its `comb_values`
19+
/// word holding whatever it last held. Nothing outside the chunk reads it,
20+
/// so the simulation is unaffected and the validate dual-run skips those
21+
/// bytes — but a waveform dump shows them, and then disagrees with a run that
22+
/// took the Cranelift or interpreted path. Waveform dumping is the caller.
23+
///
24+
/// Latches: once off, localization never comes back on. Must be called
25+
/// before analysis, since the blocklist is computed during conv.
26+
pub fn force_disable_localize() {
27+
emit::force_disable_localize();
28+
}
29+
1630
pub struct AotCBackend {
1731
async_mode: bool,
1832
/// When false, only whole-comb compile is attempted.

crates/simulator/src/backend/aot_c/emit.rs

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,14 @@ use crate::ir::{
1313
ProtoForBound, ProtoForRange, ProtoForStatement, ProtoStatement, ProtoSystemFunctionCall,
1414
VarOffset, native_bytes, veryl_aot_sysfn_print,
1515
};
16+
use crate::{HashMap, HashSet};
1617
use std::cell::RefCell;
17-
use std::collections::{HashMap, HashSet};
1818
use std::ffi::c_void;
1919
use std::fs;
2020
use std::io::ErrorKind;
2121
use std::path::{Path, PathBuf};
2222
use std::process::{Command, Stdio};
23+
use std::sync::atomic::{AtomicBool, Ordering};
2324
use std::sync::mpsc::Sender;
2425
use std::sync::{Arc, Mutex, OnceLock};
2526
use std::thread;
@@ -270,22 +271,43 @@ fn reset_wide_tmp() {
270271
}
271272

272273
// ---------------------------------------------------------------------------
273-
// Chunk-local comb intermediate localization (VERYL_AOT_C_LOCALIZE, default on,
274-
// `=0` to opt out). A comb scalar written and read only within its emit chunk
275-
// is kept in a C local instead of round-tripping `comb_values` (gcc can't drop
276-
// the store — escaping restrict param — but the emitter's global read-set can).
274+
// Chunk-local comb intermediate localization (on by default;
275+
// `VERYL_AOT_C_LOCALIZE=0` or `force_disable_localize` opts out). A comb
276+
// scalar written and read only within its emit chunk is kept in a C local
277+
// instead of round-tripping `comb_values` (gcc can't drop the store —
278+
// escaping restrict param — but the emitter's global read-set can).
277279
// Soundness: localize only a signal (a) written by one top-level unconditional
278280
// full-width scalar (≤64-bit) Assign, (b) read only in that chunk, (c) not
279281
// blocklisted (event-read / array-range / partial-write / port). Blocklist
280282
// built in `module.rs`.
283+
284+
/// Set by [`force_disable_localize`]; latches on and is never cleared, so
285+
/// localization can only ever be turned off, never back on.
286+
static LOCALIZE_FORCED_OFF: AtomicBool = AtomicBool::new(false);
287+
288+
/// Whether chunk-local localization runs: on unless `VERYL_AOT_C_LOCALIZE=0`
289+
/// or a caller turned it off for the process.
290+
pub fn localize_enabled() -> bool {
291+
if LOCALIZE_FORCED_OFF.load(Ordering::Relaxed) {
292+
return false;
293+
}
294+
std::env::var("VERYL_AOT_C_LOCALIZE").as_deref() != Ok("0")
295+
}
296+
297+
/// Latch the process-wide off switch; `super::force_disable_localize` is the
298+
/// public entry point and carries the rationale.
299+
pub fn force_disable_localize() {
300+
LOCALIZE_FORCED_OFF.store(true, Ordering::Relaxed);
301+
}
302+
281303
thread_local! {
282304
/// Comb offsets the caller marked unsafe to localize (read outside the
283305
/// comb function / dynamically / partial-written / port-visible).
284306
static LOCALIZE_BLOCKLIST: RefCell<HashSet<isize>> =
285-
RefCell::new(HashSet::new());
307+
RefCell::new(HashSet::default());
286308
/// Comb offsets localized in the chunk currently being emitted.
287309
static CURRENT_LOCAL: RefCell<HashSet<isize>> =
288-
RefCell::new(HashSet::new());
310+
RefCell::new(HashSet::default());
289311
/// Runtime-indexed comb array ranges (base, num_elements, stride) — a
290312
/// candidate offset inside any of these is excluded (a constant-indexed
291313
/// element could be read dynamically by an event / another statement).
@@ -687,7 +709,7 @@ fn compute_localize_sets(
687709
a.walk_stmt(s, i, true);
688710
}
689711
}
690-
let mut sets: Vec<HashSet<isize>> = vec![HashSet::new(); chunks.len()];
712+
let mut sets: Vec<HashSet<isize>> = vec![HashSet::default(); chunks.len()];
691713
for (off, wc) in &a.write_chunk {
692714
let Some(i) = wc else { continue };
693715
if a.bad.contains(off)
@@ -3389,7 +3411,7 @@ pub fn emit_function(stmts: &[ProtoStatement]) -> Option<String> {
33893411
});
33903412
sets
33913413
} else {
3392-
vec![HashSet::new(); chunks.len()]
3414+
vec![HashSet::default(); chunks.len()]
33933415
};
33943416
clear_current_local();
33953417
let mut chunk_bodies: Vec<String> = Vec::with_capacity(chunks.len());

crates/simulator/src/ir/module.rs

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2910,16 +2910,15 @@ impl Conv<&air::Module> for ProtoModule {
29102910
);
29112911
}
29122912

2913-
// Chunk-local localization (VERYL_AOT_C_LOCALIZE): while events +
2914-
// derived-clock candidates are in scope, precompute the comb offsets the
2915-
// emitter must NOT localize — event-touched, in a runtime-indexed array
2916-
// range, or externally-visible (port / user-var / clock).
2913+
// Chunk-local localization (gated by `emit::localize_enabled`): while
2914+
// events + derived-clock candidates are in scope, precompute the comb
2915+
// offsets the emitter must NOT localize — event-touched, in a
2916+
// runtime-indexed array range, or externally-visible (port / user-var
2917+
// / clock).
29172918
// LocalizeInfo = (blocklist offsets, array ranges).
2918-
type LocalizeInfo = (std::collections::HashSet<isize>, Vec<(isize, usize, isize)>);
2919+
type LocalizeInfo = (HashSet<isize>, Vec<(isize, usize, isize)>);
29192920
#[cfg(not(target_family = "wasm"))]
2920-
let localize_info: Option<LocalizeInfo> = if std::env::var("VERYL_AOT_C_LOCALIZE")
2921-
.as_deref()
2922-
!= Ok("0")
2921+
let localize_info: Option<LocalizeInfo> = if crate::backend::aot_c::emit::localize_enabled()
29232922
{
29242923
let event_slices: Vec<&[ProtoStatement]> = all_event_statements
29252924
.values()
@@ -2946,7 +2945,7 @@ impl Conv<&air::Module> for ProtoModule {
29462945
for (_, off, _) in &nested_derived_clock_candidates {
29472946
block_vo.insert(*off);
29482947
}
2949-
let mut block: std::collections::HashSet<isize> = std::collections::HashSet::new();
2948+
let mut block: HashSet<isize> = HashSet::default();
29502949
for vo in &block_vo {
29512950
if !vo.is_ff() {
29522951
block.insert(vo.raw());
@@ -3256,7 +3255,7 @@ impl Conv<&air::Module> for ProtoModule {
32563255
#[cfg(not(target_family = "wasm"))]
32573256
if std::env::var("VERYL_AOT_C_DIAG").as_deref() == Ok("1") {
32583257
let census = crate::backend::aot_c::emit::comb_uncovered_census(&pre_jit_stmts);
3259-
let mut counts: std::collections::HashMap<String, usize> = Default::default();
3258+
let mut counts: HashMap<String, usize> = Default::default();
32603259
for c in census {
32613260
*counts.entry(c).or_default() += 1;
32623261
}

crates/veryl/src/cmd_test.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,14 @@ impl CmdTest {
173173
}
174174

175175
pub fn exec(&self, metadata: &mut Metadata) -> Result<bool> {
176+
// A dump wants every comb word, and localization leaves the ones no
177+
// later reader needs holding stale values (see
178+
// `aot_c::force_disable_localize`). Before analysis: the blocklist is
179+
// computed during conv.
180+
if self.opt.wave {
181+
veryl_simulator::backend::aot_c::force_disable_localize();
182+
}
183+
176184
// force filelist_type to absolute which can be refered from temporary directory
177185
metadata.build.filelist_type = FilelistType::Absolute;
178186

0 commit comments

Comments
 (0)