Skip to content

Commit 9e585f2

Browse files
authored
Merge pull request #3160 from veryl-lang/clean/incr-settle
feat(simulator): change-driven incremental comb settle, on by default
2 parents 27b3fc9 + 2a8b5e2 commit 9e585f2

19 files changed

Lines changed: 6395 additions & 132 deletions

crates/simulator/src/backend.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ pub mod aot_c;
1919
#[cfg(not(target_family = "wasm"))]
2020
pub mod cranelift;
2121
pub mod inst;
22+
pub mod late;
2223
pub mod registry;
2324
pub mod validate;
2425

@@ -116,6 +117,17 @@ pub struct ChunkArtifact {
116117
/// still distinguishes chunks with different code. `None` before a stamp
117118
/// (non-`dut_reuse` path); `Debug` then falls back to the address.
118119
pub content_fp: Option<u128>,
120+
/// Full-coverage read/write dependency sets of the compiled statements,
121+
/// captured at compile time for the incremental (change-driven) settle
122+
/// plan. `None` unless `VERYL_INCR=1` (see `ir::incremental`). Excluded
123+
/// from `Debug`/`Hash`: derived deterministically from the statements the
124+
/// fingerprint already identifies.
125+
pub deps: Option<Arc<crate::ir::incremental::ChunkDeps>>,
126+
/// The compiled code carries per-sub-block guards reading the mask slot
127+
/// in the write-log header (see `ir::incremental::sub_split_len`); the
128+
/// incremental settle then passes a requested-sub mask before the call.
129+
/// Excluded from `Debug`/`Hash` like `deps` (derived from env + stmts).
130+
pub sub_guarded: bool,
119131
}
120132

121133
impl std::fmt::Debug for ChunkArtifact {

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8167,6 +8167,8 @@ mod tests {
81678167
func: stub,
81688168
keepalive: None,
81698169
content_fp: None,
8170+
deps: None,
8171+
sub_guarded: false,
81708172
})
81718173
}
81728174

crates/simulator/src/backend/cranelift.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ impl Backend for CraneliftBackend {
5757
// unmapped); only the fallback private mapping does.
5858
keepalive: mmap.map(|m| Box::new(m) as Box<dyn Send + Sync>),
5959
content_fp: None,
60+
deps: None,
61+
// Mirrors the guard condition in `build_binary_inner`.
62+
sub_guarded: crate::ir::incremental::enabled()
63+
&& crate::ir::incremental::sub_split_len(stmts.len()).is_some(),
6064
}))
6165
}
6266
}

crates/simulator/src/backend/cranelift/runtime.rs

Lines changed: 67 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -366,8 +366,8 @@ pub fn alloc_wide_slot(builder: &mut FunctionBuilder, nb: usize) -> Value {
366366
builder.ins().stack_addr(I64, slot, 0)
367367
}
368368

369-
/// Compile a chunk into a native function. Returns `(func, mmap)`;
370-
/// the caller must keep `mmap` alive for as long as `func` is callable
369+
/// Compile a chunk into a native function. Returns `(func, mmap)`; the
370+
/// caller must keep `mmap` alive for as long as `func` is callable
371371
/// (typically by wrapping both in `ChunkArtifact`).
372372
pub fn build_binary(
373373
config: &Config,
@@ -527,6 +527,42 @@ fn build_binary_inner(
527527
let zero_hi = builder.ins().iconst(I64, 0);
528528
let zero_128 = builder.ins().iconcat(zero_lo, zero_hi);
529529

530+
// Sub-block guard (incremental settle): split the chunk into the same
531+
// groups as the plan's per-sub dependency sets and gate each group on a
532+
// bit of the caller-provided mask. The mask travels through a slot in
533+
// the write-log header: loaded once in the prologue and immediately
534+
// cleared, so a nested CompiledBlock (compiled with its own guards)
535+
// sees 0 — "run whole" — instead of consuming the outer chunk's mask,
536+
// and so every non-incremental caller runs the chunk whole too.
537+
let sub_len = if crate::ir::incremental::enabled() {
538+
crate::ir::incremental::sub_split_len(proto.len())
539+
} else {
540+
None
541+
};
542+
// Store elimination forwards a skipped store through the load cache;
543+
// a guarded (skippable) region boundary breaks that forwarding, so
544+
// keep every store when guards are present.
545+
let store_elim = if sub_len.is_some() {
546+
HashSet::default()
547+
} else {
548+
store_elim
549+
};
550+
let sub_mask_eff = sub_len.map(|_| {
551+
use crate::ir::write_log::WRITE_LOG_OFFSET_INCR_SUB_MASK;
552+
let flags = MemFlagsData::trusted();
553+
let raw = builder
554+
.ins()
555+
.load(I32, flags, log_buf, WRITE_LOG_OFFSET_INCR_SUB_MASK);
556+
let zero_i32 = builder.ins().iconst(I32, 0);
557+
builder
558+
.ins()
559+
.store(flags, zero_i32, log_buf, WRITE_LOG_OFFSET_INCR_SUB_MASK);
560+
// 0 = no request recorded: run every sub-block.
561+
let is_zero = builder.ins().icmp_imm_s(IntCC::Equal, raw, 0);
562+
let all = builder.ins().iconst(I32, 0xff);
563+
builder.ins().select(is_zero, all, raw)
564+
});
565+
530566
let mut cranelift_context = Context {
531567
use_4state: config.use_4state,
532568
ff_values,
@@ -559,8 +595,32 @@ fn build_binary_inner(
559595
}
560596

561597
let len = proto.len();
598+
// Merge target of the currently open guarded region (jump destination
599+
// for both the skip branch and the region's fall-through).
600+
let mut pending_merge: Option<cranelift::prelude::Block> = None;
562601
for (i, x) in proto.iter().enumerate() {
563-
let is_last = (i + 1) == len;
602+
if let (Some(s), Some(m_eff)) = (sub_len, sub_mask_eff)
603+
&& i % s == 0
604+
{
605+
if let Some(mb) = pending_merge.take() {
606+
builder.ins().jump(mb, &[]);
607+
builder.switch_to_block(mb);
608+
}
609+
let si = (i / s) as i64;
610+
let body = builder.create_block();
611+
let merge = builder.create_block();
612+
let shifted = builder.ins().ushr_imm_s(m_eff, si);
613+
let bit = builder.ins().band_imm_s(shifted, 1);
614+
builder.ins().brif(bit, body, &[], merge, &[]);
615+
builder.switch_to_block(body);
616+
pending_merge = Some(merge);
617+
// Values cached in earlier (possibly skipped) regions must not
618+
// be forwarded across the guard.
619+
cranelift_context.load_cache.clear();
620+
}
621+
// `is_last` lets the final statement return directly, bypassing any
622+
// merge block — incompatible with the guard's region merge.
623+
let is_last = (i + 1) == len && sub_len.is_none();
564624
x.build_binary(&mut cranelift_context, &mut builder, is_last)?;
565625

566626
// Belady: while over capacity, evict the entry whose next read
@@ -589,6 +649,10 @@ fn build_binary_inner(
589649
}
590650
}
591651

652+
if let Some(mb) = pending_merge.take() {
653+
builder.ins().jump(mb, &[]);
654+
builder.switch_to_block(mb);
655+
}
592656
builder.ins().return_(&[]);
593657
builder.seal_all_blocks();
594658
builder.finalize(isa.frontend_config());

0 commit comments

Comments
 (0)