#![allow(
clippy::unwrap_used,
clippy::expect_used,
reason = "tests build known-valid functions and assert on concrete results"
)]
use ir_lang::{BinOp, Builder, Type, Value};
use jit_lang::compile;
use proptest::prelude::*;
#[derive(Debug, Clone)]
struct IntProgram {
nparams: usize,
consts: Vec<i64>,
ops: Vec<(u8, usize, usize)>,
inputs: Vec<i64>,
}
fn int_program() -> impl Strategy<Value = IntProgram> {
(1usize..=3).prop_flat_map(|nparams| {
(
Just(nparams),
prop::collection::vec(any::<i64>(), 0..=4),
prop::collection::vec((0u8..3, any::<usize>(), any::<usize>()), 1..=12),
prop::collection::vec(any::<i64>(), nparams),
)
.prop_map(|(nparams, consts, ops, inputs)| IntProgram {
nparams,
consts,
ops,
inputs,
})
})
}
fn op_of(tag: u8) -> (BinOp, fn(i64, i64) -> i64) {
match tag % 3 {
0 => (BinOp::Add, i64::wrapping_add),
1 => (BinOp::Sub, i64::wrapping_sub),
_ => (BinOp::Mul, i64::wrapping_mul),
}
}
fn reference(prog: &IntProgram) -> i64 {
let mut pool: Vec<i64> = prog.inputs.clone();
pool.extend_from_slice(&prog.consts);
for &(tag, a, b) in &prog.ops {
let (_, f) = op_of(tag);
let len = pool.len();
let value = f(pool[a % len], pool[b % len]);
pool.push(value);
}
pool[pool.len() - 1]
}
fn jit(prog: &IntProgram) -> i64 {
let mut b = Builder::new("prop", &vec![Type::Int; prog.nparams], Type::Int);
let mut pool: Vec<Value> = b.block_params(b.entry()).to_vec();
for &k in &prog.consts {
pool.push(b.iconst(k));
}
for &(tag, a, b_idx) in &prog.ops {
let (op, _) = op_of(tag);
let len = pool.len();
let value = b.bin(op, pool[a % len], pool[b_idx % len]);
pool.push(value);
}
let result = pool[pool.len() - 1];
b.ret(Some(result));
let compiled = compile(&b.finish()).expect("a generated function is well-formed");
let inputs = &prog.inputs;
match prog.nparams {
1 => {
let f: extern "C" fn(i64) -> i64 = unsafe { compiled.entry() };
f(inputs[0])
}
2 => {
let f: extern "C" fn(i64, i64) -> i64 = unsafe { compiled.entry() };
f(inputs[0], inputs[1])
}
_ => {
let f: extern "C" fn(i64, i64, i64) -> i64 = unsafe { compiled.entry() };
f(inputs[0], inputs[1], inputs[2])
}
}
}
proptest! {
#[test]
fn int_jit_matches_reference(prog in int_program()) {
prop_assert_eq!(jit(&prog), reference(&prog));
}
#[test]
fn float_binop_matches_reference(
a in any::<f64>().prop_filter("finite", |x| x.is_finite()),
c in any::<f64>().prop_filter("finite", |x| x.is_finite()),
tag in 0u8..3,
) {
let (op, expected): (BinOp, f64) = match tag {
0 => (BinOp::Add, a + c),
1 => (BinOp::Sub, a - c),
_ => (BinOp::Mul, a * c),
};
let mut b = Builder::new("fop", &[Type::Float, Type::Float], Type::Float);
let x = b.block_params(b.entry())[0];
let y = b.block_params(b.entry())[1];
let r = b.bin(op, x, y);
b.ret(Some(r));
let compiled = compile(&b.finish()).expect("fop is well-formed");
let f: extern "C" fn(f64, f64) -> f64 = unsafe { compiled.entry() };
let got = f(a, c);
prop_assert!(got == expected || (got.is_nan() && expected.is_nan()));
}
}