use std::collections::BTreeMap;
use fusevm::{Chunk, ChunkBuilder, JitCompiler, Op};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Loop {
pub anchor: usize,
pub trace_eligible: bool,
pub traced: bool,
pub blacklisted: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ChunkTiers {
pub name: String,
pub ops: usize,
pub block_eligible: bool,
pub block_compiled: bool,
pub largest_eligible_region: Option<(usize, usize)>,
pub loops: Vec<Loop>,
pub ineligible: BTreeMap<String, usize>,
}
impl ChunkTiers {
pub fn reaches_native(&self) -> bool {
self.block_compiled || self.loops.iter().any(|l| l.traced)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Report {
pub chunks: Vec<ChunkTiers>,
}
impl Report {
pub fn reaches_native(&self) -> bool {
self.chunks.iter().any(|c| c.reaches_native())
}
}
impl std::fmt::Display for ChunkTiers {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "ops {}", self.ops)?;
writeln!(f, "block-JIT eligible {}", self.block_eligible)?;
writeln!(f, "block-JIT compiled {}", self.block_compiled)?;
match self.largest_eligible_region {
Some((s, e)) => writeln!(f, "largest eligible region {s}..{e} ({} ops)", e - s)?,
None => writeln!(f, "largest eligible region none")?,
}
if self.loops.is_empty() {
writeln!(f, "loops none")?;
}
for l in &self.loops {
writeln!(
f,
"loop @{:<4} trace-eligible={} traced={} blacklisted={}",
l.anchor, l.trace_eligible, l.traced, l.blacklisted
)?;
}
if self.ineligible.is_empty() {
writeln!(f, "block-ineligible ops none")?;
} else {
writeln!(f, "block-ineligible ops")?;
for (name, count) in &self.ineligible {
writeln!(f, " {name:<22}{count}")?;
}
}
Ok(())
}
}
impl std::fmt::Display for Report {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let label = self.chunks.len() > 1;
for c in &self.chunks {
if label {
writeln!(f, "== {} ==", c.name)?;
}
write!(f, "{c}")?;
}
write!(f, "reaches native code {}", self.reaches_native())
}
}
pub fn report(src: &str) -> Result<Report, String> {
let named = program_chunks(&crate::compile(src)?);
crate::eval_str(src)?;
Ok(inspect_all(&named))
}
fn program_chunks(prog: &crate::compiler::Program) -> Vec<(String, Chunk)> {
let mut out = vec![("main".to_string(), prog.main.clone())];
for (name, f) in &prog.functions {
out.push((format!("function {name}"), f.chunk.clone()));
}
for (i, t) in prog.tries.iter().enumerate() {
out.push((format!("try #{i}"), t.block.clone()));
if let Some((_, ch)) = &t.handler {
out.push((format!("try #{i} catch"), ch.clone()));
}
if let Some(ch) = &t.finalizer {
out.push((format!("try #{i} finally"), ch.clone()));
}
}
out
}
pub fn inspect(chunk: &Chunk) -> Report {
Report {
chunks: vec![inspect_chunk("main", chunk)],
}
}
pub fn inspect_all(named: &[(String, Chunk)]) -> Report {
Report {
chunks: named.iter().map(|(n, c)| inspect_chunk(n, c)).collect(),
}
}
pub fn inspect_chunk(name: &str, chunk: &Chunk) -> ChunkTiers {
let jit = JitCompiler::new();
let loops = loop_anchors(&chunk.ops)
.into_iter()
.map(|anchor| Loop {
anchor,
trace_eligible: body_of(&chunk.ops, anchor)
.is_some_and(|body| jit.is_trace_eligible(body, anchor)),
traced: jit.trace_is_compiled(chunk, anchor),
blacklisted: jit.trace_is_blacklisted(chunk, anchor),
})
.collect();
let mut ineligible: BTreeMap<String, usize> = BTreeMap::new();
for op in &chunk.ops {
if !op_is_eligible(&jit, op) {
*ineligible.entry(op_name(op)).or_default() += 1;
}
}
ChunkTiers {
name: name.to_string(),
ops: chunk.ops.len(),
block_eligible: jit.is_block_eligible(chunk),
block_compiled: jit.block_jit_is_compiled(chunk),
largest_eligible_region: jit.find_jit_region(chunk),
loops,
ineligible,
}
}
fn loop_anchors(ops: &[Op]) -> Vec<usize> {
let mut anchors: Vec<usize> = ops
.iter()
.enumerate()
.filter_map(|(ip, op)| match op {
Op::Jump(t)
| Op::JumpIfTrue(t)
| Op::JumpIfFalse(t)
| Op::JumpIfTrueKeep(t)
| Op::JumpIfFalseKeep(t)
if *t <= ip =>
{
Some(*t)
}
_ => None,
})
.collect();
anchors.sort_unstable();
anchors.dedup();
anchors
}
fn body_of(ops: &[Op], anchor: usize) -> Option<&[Op]> {
let close = ops.iter().enumerate().position(|(ip, op)| {
ip >= anchor
&& matches!(
op,
Op::Jump(t) | Op::JumpIfTrue(t) | Op::JumpIfFalse(t)
if *t == anchor
)
})?;
Some(&ops[anchor..=close])
}
fn op_is_eligible(jit: &JitCompiler, op: &Op) -> bool {
let mut b = ChunkBuilder::new();
b.emit(op.clone(), 1);
jit.is_block_eligible(&b.build())
}
fn op_name(op: &Op) -> String {
let text = format!("{op:?}");
match text.split_once('(') {
Some((name, _)) => name.to_string(),
None => text,
}
}
#[cfg(test)]
const PROGRAM: &str = "function f(n) {\n let t = 0;\n let i = 0;\n while (i < n) { t += i; i += 1; }\n return t;\n}\nf(200000);\n";
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_rotated_slot_loop_reaches_a_compiled_trace() {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(0), 1);
b.emit(Op::SetSlot(0), 1);
let enter = b.emit(Op::Jump(usize::MAX), 1);
let body = b.current_pos();
b.emit(Op::GetSlot(0), 1);
b.emit(Op::LoadInt(1), 1);
b.emit(Op::Add, 1);
b.emit(Op::SetSlot(0), 1);
let cond = b.current_pos();
b.patch_jump(enter, cond);
b.emit(Op::GetSlot(0), 1);
b.emit(Op::LoadInt(200_000), 1);
b.emit(Op::NumLt, 1);
b.emit(Op::JumpIfTrue(body), 1);
b.emit(Op::GetSlot(0), 1);
let chunk = b.build();
let mut vm = fusevm::VM::new(chunk.clone());
vm.enable_tracing_jit();
vm.run();
let report = inspect(&chunk);
assert_eq!(report.chunks[0].loops.len(), 1, "{report}");
assert!(report.chunks[0].loops[0].traced, "{report}");
assert!(report.reaches_native(), "{report}");
}
#[test]
fn the_loop_lives_in_a_chunk_other_than_main() {
let report = report(PROGRAM).expect("runs");
assert!(report.chunks.len() > 1, "{report}");
assert!(
report.chunks[0].loops.is_empty(),
"main has no loop: {report}"
);
assert!(
report.chunks[1..].iter().any(|c| !c.loops.is_empty()),
"{report}"
);
}
#[test]
fn the_counted_loop_reaches_a_compiled_trace() {
let report = report(PROGRAM).expect("runs");
let looped = report
.chunks
.iter()
.find(|c| !c.loops.is_empty())
.unwrap_or_else(|| panic!("a chunk with a loop: {report}"));
assert!(looped.loops[0].trace_eligible, "{report}");
assert!(looped.loops[0].traced, "{report}");
assert!(report.reaches_native(), "{report}");
let anchor = looped.loops[0].anchor;
let chunks = program_chunks(&crate::compile(PROGRAM).expect("compiles"));
let (_, chunk) = chunks
.iter()
.find(|(name, _)| name == &looped.name)
.expect("the looped chunk is one of the program's chunks");
let close = body_of(&chunk.ops, anchor)
.and_then(<[Op]>::last)
.expect("the loop is closed");
assert!(
matches!(close, Op::JumpIfTrue(t) if *t == anchor),
"the loop closes with a conditional JumpIfTrue({anchor}), got {close:?}"
);
}
#[test]
fn an_untested_for_reaches_a_compiled_trace() {
const SRC: &str =
"function f(n) {\n let i = 0;\n for (;;) { i += 1; if (i >= n) break; }\n return i;\n}\nf(200000);\n";
let report = report(SRC).expect("runs");
let looped = report
.chunks
.iter()
.find(|c| !c.loops.is_empty())
.unwrap_or_else(|| panic!("a chunk with a loop: {report}"));
assert!(looped.loops[0].traced, "{report}");
assert!(report.reaches_native(), "{report}");
}
}