use criterion::{black_box, criterion_group, criterion_main, Criterion};
use fusevm::{ChunkBuilder, Op, VMResult, Value, VM};
fn run_chunk(chunk: &fusevm::Chunk) -> Value {
let mut vm = VM::new(chunk.clone());
match vm.run() {
VMResult::Ok(val) => val,
_ => Value::Undef,
}
}
fn fib_iterative_chunk(n: i64) -> fusevm::Chunk {
let mut b = ChunkBuilder::new();
let fib_name = b.add_name("fib");
b.emit(Op::LoadInt(n), 1);
b.emit(Op::Call(fib_name, 1), 1);
let jump_end = b.emit(Op::Jump(0), 1);
let fib_ip = b.current_pos();
b.add_sub_entry(fib_name, fib_ip);
b.emit(Op::SetSlot(2), 2); b.emit(Op::LoadInt(0), 2);
b.emit(Op::SetSlot(0), 2); b.emit(Op::LoadInt(1), 2);
b.emit(Op::SetSlot(1), 2); let loop_start = b.current_pos();
b.emit(Op::GetSlot(2), 2); b.emit(Op::LoadInt(0), 2);
b.emit(Op::NumLe, 2); let exit_jump = b.emit(Op::JumpIfTrue(0), 2);
b.emit(Op::GetSlot(0), 2);
b.emit(Op::GetSlot(1), 2);
b.emit(Op::Add, 2);
b.emit(Op::SetSlot(3), 2); b.emit(Op::GetSlot(1), 2);
b.emit(Op::SetSlot(0), 2);
b.emit(Op::GetSlot(3), 2);
b.emit(Op::SetSlot(1), 2);
b.emit(Op::GetSlot(2), 2);
b.emit(Op::Dec, 2);
b.emit(Op::SetSlot(2), 2);
b.emit(Op::Jump(loop_start), 2);
let exit_ip = b.current_pos();
b.patch_jump(exit_jump, exit_ip);
b.emit(Op::GetSlot(0), 2); b.emit(Op::ReturnValue, 2);
let end_ip = b.current_pos();
b.patch_jump(jump_end, end_ip);
b.build()
}
fn bench_fib_35(c: &mut Criterion) {
let chunk = fib_iterative_chunk(35);
c.bench_function("fib_iterative_35", |b| {
b.iter(|| {
let val = run_chunk(black_box(&chunk));
black_box(val);
})
});
}
fn fib_recursive_chunk(n: i64) -> fusevm::Chunk {
let mut b = ChunkBuilder::new();
let fib_name = b.add_name("fib");
b.emit(Op::LoadInt(n), 1);
b.emit(Op::Call(fib_name, 1), 1);
let jump_end = b.emit(Op::Jump(0), 1);
let fib_ip = b.current_pos();
b.add_sub_entry(fib_name, fib_ip);
b.emit(Op::SetSlot(0), 2); b.emit(Op::GetSlot(0), 2);
b.emit(Op::LoadInt(1), 2);
b.emit(Op::NumLe, 2); let base_jump = b.emit(Op::JumpIfTrue(0), 2);
b.emit(Op::GetSlot(0), 2);
b.emit(Op::Dec, 2); b.emit(Op::Call(fib_name, 1), 2); b.emit(Op::GetSlot(0), 2);
b.emit(Op::LoadInt(2), 2);
b.emit(Op::Sub, 2); b.emit(Op::Call(fib_name, 1), 2); b.emit(Op::Add, 2); b.emit(Op::ReturnValue, 2);
let base_ip = b.current_pos();
b.patch_jump(base_jump, base_ip);
b.emit(Op::GetSlot(0), 2);
b.emit(Op::ReturnValue, 2);
let end_ip = b.current_pos();
b.patch_jump(jump_end, end_ip);
b.build()
}
fn bench_fib_recursive_20(c: &mut Criterion) {
let chunk = fib_recursive_chunk(20);
c.bench_function("fib_recursive_20", |b| {
b.iter(|| {
let val = run_chunk(black_box(&chunk));
black_box(val);
})
});
}
fn sum_fused_chunk(n: i32) -> fusevm::Chunk {
let mut b = ChunkBuilder::new();
b.emit(Op::PushFrame, 1);
b.emit(Op::LoadInt(0), 1);
b.emit(Op::SetSlot(0), 1);
b.emit(Op::LoadInt(0), 1);
b.emit(Op::SetSlot(1), 1);
b.emit(Op::AccumSumLoop(0, 1, n), 1);
b.emit(Op::GetSlot(0), 1);
b.build()
}
fn sum_unfused_chunk(n: i64) -> fusevm::Chunk {
let mut b = ChunkBuilder::new();
b.emit(Op::PushFrame, 1);
b.emit(Op::LoadInt(0), 1);
b.emit(Op::SetSlot(0), 1);
b.emit(Op::LoadInt(0), 1);
b.emit(Op::SetSlot(1), 1);
b.emit(Op::GetSlot(0), 1);
b.emit(Op::GetSlot(1), 1);
b.emit(Op::Add, 1);
b.emit(Op::SetSlot(0), 1);
b.emit(Op::PreIncSlotVoid(1), 1);
b.emit(Op::SlotLtIntJumpIfFalse(1, n as i32, 12), 1);
b.emit(Op::Jump(5), 1);
b.emit(Op::GetSlot(0), 1);
b.build()
}
fn bench_sum_1m_fused(c: &mut Criterion) {
let chunk = sum_fused_chunk(1_000_000);
c.bench_function("sum_1M_fused", |b| {
b.iter(|| {
let val = run_chunk(black_box(&chunk));
black_box(val);
})
});
}
fn bench_sum_1m_unfused(c: &mut Criterion) {
let chunk = sum_unfused_chunk(1_000_000);
c.bench_function("sum_1M_unfused", |b| {
b.iter(|| {
let val = run_chunk(black_box(&chunk));
black_box(val);
})
});
}
fn bench_sum_1m_native(c: &mut Criterion) {
c.bench_function("sum_1M_native_rust", |b| {
b.iter(|| {
let mut sum: i64 = 0;
for i in 0i64..1_000_000 {
sum += i;
}
black_box(sum);
})
});
}
fn nested_loop_chunk(outer: i32, inner: i32) -> fusevm::Chunk {
let mut b = ChunkBuilder::new();
b.emit(Op::PushFrame, 1);
b.emit(Op::LoadInt(0), 1);
b.emit(Op::SetSlot(0), 1); b.emit(Op::LoadInt(0), 1);
b.emit(Op::SetSlot(1), 1); let outer_start = b.current_pos();
b.emit(Op::LoadInt(0), 1);
b.emit(Op::SetSlot(2), 1); let inner_start = b.current_pos();
b.emit(Op::GetSlot(0), 1); b.emit(Op::GetSlot(1), 1); b.emit(Op::GetSlot(2), 1); b.emit(Op::Add, 1); b.emit(Op::Add, 1); b.emit(Op::SetSlot(0), 1); b.emit(Op::SlotIncLtIntJumpBack(2, inner, inner_start), 1);
b.emit(Op::SlotIncLtIntJumpBack(1, outer, outer_start), 1);
b.emit(Op::GetSlot(0), 1);
b.build()
}
fn bench_nested_100x100(c: &mut Criterion) {
let chunk = nested_loop_chunk(100, 100);
c.bench_function("nested_loop_100x100", |b| {
b.iter(|| {
let val = run_chunk(black_box(&chunk));
black_box(val);
})
});
}
fn bench_nested_100x100_native(c: &mut Criterion) {
c.bench_function("nested_loop_100x100_native", |b| {
b.iter(|| {
let mut sum: i64 = 0;
for i in 0i64..100 {
for j in 0i64..100 {
sum += i + j;
}
}
black_box(sum);
})
});
}
fn ackermann_chunk() -> fusevm::Chunk {
let mut b = ChunkBuilder::new();
let ack_name = b.add_name("ack");
b.emit(Op::LoadInt(3), 1);
b.emit(Op::LoadInt(4), 1);
b.emit(Op::Call(ack_name, 2), 1);
let jump_end = b.emit(Op::Jump(0), 1);
let ack_ip = b.current_pos();
b.add_sub_entry(ack_name, ack_ip);
b.emit(Op::SetSlot(1), 2); b.emit(Op::SetSlot(0), 2);
b.emit(Op::GetSlot(0), 2);
b.emit(Op::LoadInt(0), 2);
b.emit(Op::NumEq, 2);
let m0_jump = b.emit(Op::JumpIfTrue(0), 2);
b.emit(Op::GetSlot(1), 2);
b.emit(Op::LoadInt(0), 2);
b.emit(Op::NumEq, 2);
let n0_jump = b.emit(Op::JumpIfTrue(0), 2);
b.emit(Op::GetSlot(0), 2); b.emit(Op::GetSlot(1), 2); b.emit(Op::Dec, 2); b.emit(Op::Call(ack_name, 2), 2); b.emit(Op::GetSlot(0), 2); b.emit(Op::Dec, 2); b.emit(Op::Swap, 2); b.emit(Op::Call(ack_name, 2), 2); b.emit(Op::ReturnValue, 2);
let n0_ip = b.current_pos();
b.patch_jump(n0_jump, n0_ip);
b.emit(Op::GetSlot(0), 2);
b.emit(Op::Dec, 2); b.emit(Op::LoadInt(1), 2); b.emit(Op::Call(ack_name, 2), 2); b.emit(Op::ReturnValue, 2);
let m0_ip = b.current_pos();
b.patch_jump(m0_jump, m0_ip);
b.emit(Op::GetSlot(1), 2);
b.emit(Op::Inc, 2); b.emit(Op::ReturnValue, 2);
let end_ip = b.current_pos();
b.patch_jump(jump_end, end_ip);
b.build()
}
fn bench_ackermann_3_4(c: &mut Criterion) {
let chunk = ackermann_chunk();
c.bench_function("ackermann_3_4", |b| {
b.iter(|| {
let val = run_chunk(black_box(&chunk));
black_box(val);
})
});
}
fn bench_dispatch_1m(c: &mut Criterion) {
let mut b = ChunkBuilder::new();
for _ in 0..1_000_000 {
b.emit(Op::Nop, 1);
}
b.emit(Op::LoadInt(0), 1);
let chunk = b.build();
c.bench_function("dispatch_nop_1M", |b| {
b.iter(|| {
let val = run_chunk(black_box(&chunk));
black_box(val);
})
});
}
fn bench_string_build_10k(c: &mut Criterion) {
let mut builder = ChunkBuilder::new();
let _const_idx = builder.add_constant(Value::str("x"));
let mut b2 = ChunkBuilder::new();
let ci = b2.add_constant(Value::str("x"));
b2.emit(Op::PushFrame, 1);
b2.emit(Op::LoadConst(ci), 1);
b2.emit(Op::SetSlot(0), 1);
b2.emit(Op::LoadInt(0), 1);
b2.emit(Op::SetSlot(1), 1);
b2.emit(Op::ConcatConstLoop(ci, 0, 1, 10_000), 1);
b2.emit(Op::GetSlot(0), 1);
let chunk = b2.build();
c.bench_function("string_build_10k", |b| {
b.iter(|| {
let val = run_chunk(black_box(&chunk));
black_box(val);
})
});
}
fn bench_string_build_10k_native(c: &mut Criterion) {
c.bench_function("string_build_10k_native", |b| {
b.iter(|| {
let mut s = String::with_capacity(10_001);
s.push('x');
for _ in 0..10_000 {
s.push('x');
}
black_box(s);
})
});
}
#[cfg(feature = "jit")]
fn bench_sum_1m_block_jit_unfused(c: &mut Criterion) {
let chunk = sum_unfused_chunk(1_000_000);
let jit = fusevm::JitCompiler::new();
assert!(
jit.is_block_eligible(&chunk),
"unfused sum loop should be block-eligible"
);
let mut slots = vec![0i64; 4];
let _ = jit.try_run_block_eager(&chunk, &mut slots);
c.bench_function("sum_1M_block_jit_unfused", |b| {
b.iter(|| {
let mut slots = vec![0i64; 4];
let result = jit.try_run_block(black_box(&chunk), &mut slots);
black_box(result);
})
});
}
#[cfg(feature = "jit")]
fn bench_sum_1m_block_jit_fused(c: &mut Criterion) {
let chunk = sum_fused_chunk(1_000_000);
let jit = fusevm::JitCompiler::new();
assert!(
jit.is_block_eligible(&chunk),
"fused sum should be block-eligible"
);
let mut slots = vec![0i64; 4];
let _ = jit.try_run_block_eager(&chunk, &mut slots);
c.bench_function("sum_1M_block_jit_fused", |b| {
b.iter(|| {
let mut slots = vec![0i64; 4];
let result = jit.try_run_block(black_box(&chunk), &mut slots);
black_box(result);
})
});
}
#[cfg(feature = "jit")]
fn bench_nested_100x100_block_jit(c: &mut Criterion) {
let chunk = nested_loop_chunk(100, 100);
let jit = fusevm::JitCompiler::new();
assert!(jit.is_block_eligible(&chunk));
let mut slots = vec![0i64; 4];
let _ = jit.try_run_block_eager(&chunk, &mut slots);
c.bench_function("nested_100x100_block_jit", |b| {
b.iter(|| {
let mut slots = vec![0i64; 4];
let result = jit.try_run_block(black_box(&chunk), &mut slots);
black_box(result);
})
});
}
#[cfg(feature = "jit")]
criterion_group!(
benches,
bench_fib_35,
bench_fib_recursive_20,
bench_sum_1m_fused,
bench_sum_1m_unfused,
bench_sum_1m_native,
bench_sum_1m_block_jit_unfused,
bench_sum_1m_block_jit_fused,
bench_nested_100x100,
bench_nested_100x100_native,
bench_nested_100x100_block_jit,
bench_ackermann_3_4,
bench_dispatch_1m,
bench_string_build_10k,
bench_string_build_10k_native,
);
#[cfg(not(feature = "jit"))]
criterion_group!(
benches,
bench_fib_35,
bench_fib_recursive_20,
bench_sum_1m_fused,
bench_sum_1m_unfused,
bench_sum_1m_native,
bench_nested_100x100,
bench_nested_100x100_native,
bench_ackermann_3_4,
bench_dispatch_1m,
bench_string_build_10k,
bench_string_build_10k_native,
);
criterion_main!(benches);