use rucc_rules::{Rule, parse};
use rucc_verify::{Model, Solver, Verdict, admit, query, verify};
const MODEL: &str = "\
(semantics (value.f32 v) v)
(semantics (value.f64 v) v)
(semantics (value.i32 v) v)
(semantics (value.i64 v) v)
(semantics (fadd.f32 l r) (fp.add l r))
(semantics (fadd.f64 l r) (fp.add l r))
(semantics (fdiv.f32 l r) (fp.div l r))
(semantics (add.i32 l r) (bvadd l r))
(semantics (x64.addss_rr l r) (fp.add l r))
(semantics (x64.addsd_rr l r) (fp.add l r))
(semantics (x64.divss_rr l r) (fp.div l r))
(semantics (x64.add_rr_32 l r) (bvadd l r))
(semantics (load.f32 a)
(float_from_bits 32
(concat (select (mem) (bvadd a 3)) (select (mem) (bvadd a 2))
(select (mem) (bvadd a 1)) (select (mem) a))))
(semantics (x64.movss_rm a)
(float_from_bits 32
(concat (select (mem) (bvadd a 3)) (select (mem) (bvadd a 2))
(select (mem) (bvadd a 1)) (select (mem) a))))
(semantics (x64.mov_rm_32 a)
(concat (select (mem) (bvadd a 3)) (select (mem) (bvadd a 2))
(select (mem) (bvadd a 1)) (select (mem) a)))
(semantics (store.f32 v a)
(store (store (store (store (mem)
a (extract 7 0 (bits_from_float 32 v)))
(bvadd a 1) (extract 15 8 (bits_from_float 32 v)))
(bvadd a 2) (extract 23 16 (bits_from_float 32 v)))
(bvadd a 3) (extract 31 24 (bits_from_float 32 v))))
(semantics (x64.movss_mr a v)
(store (store (store (store (mem)
a (extract 7 0 (bits_from_float 32 v)))
(bvadd a 1) (extract 15 8 (bits_from_float 32 v)))
(bvadd a 2) (extract 23 16 (bits_from_float 32 v)))
(bvadd a 3) (extract 31 24 (bits_from_float 32 v))))
(semantics (fpext.f32.f64 v) (float_from_float 32 64 v))
(semantics (fptosi.f64.i32 v) (signed_from_float 64 32 v))
(semantics (sitofp.i32.f32 v) (float_from_signed 32 32 v))
(semantics (bitcast.i32.f32 v) (float_from_bits 32 v))
(semantics (x64.cvtss2sd v) (float_from_float 32 64 v))
(semantics (x64.cvttsd2si_32 v) (signed_from_float 64 32 v))
(semantics (x64.cvtsi2ss_32 v) (float_from_signed 32 32 v))
(semantics (x64.movd_to_xmm v) (float_from_bits 32 v))
(semantics (amode_base base) base)";
const LOAD: &str = "\
(rule (lower (load.f32 (value.i64 a)))
(x64.movss_rm (amode_base a))
(spec (= (float_from_bits 32
(concat (select (mem) (bvadd a 3)) (select (mem) (bvadd a 2))
(select (mem) (bvadd a 1)) (select (mem) a)))
(result))))";
const ADD: &str = "\
(rule (lower (fadd.f32 (value.f32 x) (value.f32 y)))
(x64.addss_rr x y)
(spec (= (fp.add x y) (result))))";
const TO_INT: &str = "\
(rule (lower (fptosi.f64.i32 (value.f64 x)))
(x64.cvttsd2si_32 x)
(spec (= (signed_from_float 64 32 x) (result))))";
fn rules(text: &str) -> Vec<Rule> {
match parse("t.rules", text) {
Ok(rules) => rules,
Err(errors) => panic!("{}", errors[0]),
}
}
fn model() -> Model {
match Model::read("t.model", MODEL) {
Ok(model) => model,
Err(errors) => panic!("{}", errors[0]),
}
}
fn solver() -> Option<Solver> {
let found = Solver::find();
assert!(
!(found.is_none() && std::env::var_os("RUCC_REQUIRE_SOLVER").is_some()),
"no solver on PATH, and RUCC_REQUIRE_SOLVER says there has to be one"
);
found
}
#[test]
fn a_rule_that_reaches_a_float_is_asked_in_the_theory_that_has_it() {
let asked = query("t.rules", &rules(ADD)[0], &model()).expect("the model covers this rule");
assert!(asked.starts_with("(set-logic QF_FPBV)\n"), "{asked}");
assert!(asked.contains("(declare-const x Float32)"), "{asked}");
assert!(asked.contains("(declare-const y Float32)"), "{asked}");
}
#[test]
fn a_rule_that_has_no_float_is_asked_exactly_as_it_was_before() {
let text = "\
(rule (lower (add.i32 (value.i32 x) (value.i32 y)))
(x64.add_rr_32 x y)
(spec (= (bvadd x y) (result))))";
let asked = query("t.rules", &rules(text)[0], &model()).expect("the model covers this rule");
assert!(asked.starts_with("(set-logic QF_BV)\n"), "{asked}");
assert!(!asked.contains("Float"), "{asked}");
}
#[test]
fn nothing_in_a_rule_says_a_rounding_and_the_verifier_is_what_writes_one() {
let asked = query("t.rules", &rules(ADD)[0], &model()).expect("the model covers this rule");
assert!(!ADD.contains("RNE"));
assert!(asked.contains("(fp.add RNE x y)"), "{asked}");
}
#[test]
fn the_widest_float_the_machine_has_is_not_one_of_these() {
let text = "\
(rule (lower (fadd.f80 (value.f80 x) (value.f80 y)))
(x64.addss_rr x y)
(spec (= (fp.add x y) (result))))";
let problem =
query("t.rules", &rules(text)[0], &model()).expect_err("this rule cannot be asked");
assert!(problem.message.contains("`value.f80`"), "{}", problem.message);
}
#[test]
fn a_float_lowered_to_an_integer_instruction_is_refused() {
let text = "\
(rule (lower (fadd.f32 (value.f32 x) (value.f32 y)))
(x64.add_rr_32 x y)
(spec (= (fp.add x y) (result))))";
let problem =
query("t.rules", &rules(text)[0], &model()).expect_err("this rule cannot be asked");
assert!(problem.message.contains("`bvadd` works on bitvectors"), "{}", problem.message);
assert!(problem.message.contains("32 bits of float"), "{}", problem.message);
}
#[test]
fn an_integer_lowered_to_a_float_instruction_is_refused() {
let text = "\
(rule (lower (add.i32 (value.i32 x) (value.i32 y)))
(x64.addss_rr x y)
(spec (= (bvadd x y) (result))))";
let problem =
query("t.rules", &rules(text)[0], &model()).expect_err("this rule cannot be asked");
assert!(problem.message.contains("`fp.add` works on floats"), "{}", problem.message);
}
#[test]
fn two_formats_in_one_operation_is_refused() {
let text = "\
(rule (lower (fadd.f32 (value.f32 x) (value.f64 y)))
(x64.addss_rr x y)
(spec (= (fp.add x y) (result))))";
let problem =
query("t.rules", &rules(text)[0], &model()).expect_err("this rule cannot be asked");
assert!(
problem.message.contains("32 bits of float and something 64 bits of float"),
"{}",
problem.message
);
}
#[test]
fn the_arithmetic_is_discharged_at_both_formats() {
let Some(solver) = solver() else {
return;
};
let text = format!(
"{ADD}
(rule (lower (fadd.f64 (value.f64 x) (value.f64 y)))
(x64.addsd_rr x y)
(spec (= (fp.add x y) (result))))"
);
let report = admit("t.rules", &rules(&text), &model(), &solver).expect("both are provable");
assert_eq!(report.discharged(), 2, "{report}");
assert_eq!(report.bounded(), 0, "{report}");
}
#[test]
fn a_model_that_has_the_operation_wrong_is_refuted() {
let Some(solver) = solver() else {
return;
};
let wrong = MODEL.replace(
"(semantics (x64.addss_rr l r) (fp.add l r))",
"(semantics (x64.addss_rr l r) (fp.div l r))",
);
let model = Model::read("t.model", &wrong).expect("the model reads");
let report = verify("t.rules", &rules(ADD), &model, &solver).expect("the question is asked");
assert!(
matches!(report.verdicts[0], Verdict::Refuted(_)),
"the wrong operation got through: {report}"
);
}
#[test]
fn a_division_by_zero_is_a_value_here_and_the_rule_covers_it() {
let Some(solver) = solver() else {
return;
};
let text = "\
(rule (lower (fdiv.f32 (value.f32 x) (value.f32 y)))
(x64.divss_rr x y)
(spec (= (fp.div x y) (result))))";
let report = admit("t.rules", &rules(text), &model(), &solver).expect("this is provable");
assert_eq!(report.discharged(), 1, "{report}");
assert!(!text.contains("(if "), "a float division needs no guard");
}
#[test]
fn a_load_asks_about_arrays_and_floats_at_once() {
let asked = query("t.rules", &rules(LOAD)[0], &model()).expect("the model covers this rule");
assert!(asked.starts_with("(set-logic QF_ABVFP)\n"), "{asked}");
assert!(asked.contains("(declare-const a (_ BitVec 64))"), "{asked}");
assert!(asked.contains("((_ to_fp 8 24) (concat"), "{asked}");
}
#[test]
fn a_store_writes_the_bits_of_the_float_and_the_verifier_is_what_names_the_operation() {
let text = "\
(rule (lower (store.f32 (value.f32 v) (value.i64 a)))
(x64.movss_mr (amode_base a) v)
(spec (= (store (store (store (store (mem)
a (extract 7 0 (bits_from_float 32 v)))
(bvadd a 1) (extract 15 8 (bits_from_float 32 v)))
(bvadd a 2) (extract 23 16 (bits_from_float 32 v)))
(bvadd a 3) (extract 31 24 (bits_from_float 32 v)))
(result))))";
let asked = query("t.rules", &rules(text)[0], &model()).expect("the model covers this rule");
assert!(!text.contains("ieee"));
assert!(asked.contains("(fp.to_ieee_bv v)"), "{asked}");
assert!(asked.contains("(declare-const v Float32)"), "{asked}");
}
#[test]
fn a_float_load_lowered_to_an_integer_one_is_refused() {
let text = "\
(rule (lower (load.f32 (value.i64 a)))
(x64.mov_rm_32 (amode_base a))
(spec (= (float_from_bits 32
(concat (select (mem) (bvadd a 3)) (select (mem) (bvadd a 2))
(select (mem) (bvadd a 1)) (select (mem) a)))
(result))))";
let problem =
query("t.rules", &rules(text)[0], &model()).expect_err("this rule cannot be asked");
assert!(
problem.message.contains("replaces something 32 bits of float with something 32 bits"),
"{}",
problem.message
);
}
#[test]
fn a_reinterpretation_at_a_format_the_standard_has_no_name_for_is_refused() {
let text = "\
(rule (lower (load.f32 (value.i64 a)))
(x64.movss_rm (amode_base a))
(spec (= (float_from_bits 80
(concat (select (mem) (bvadd a 3)) (select (mem) (bvadd a 2))
(select (mem) (bvadd a 1)) (select (mem) a)))
(result))))";
let problem =
query("t.rules", &rules(text)[0], &model()).expect_err("this rule cannot be asked");
assert!(problem.message.contains("which is not a float format"), "{}", problem.message);
}
#[test]
fn reading_a_float_as_bits_of_the_wrong_width_is_refused() {
let text = "\
(rule (lower (load.f32 (value.i64 a)))
(x64.movss_rm (amode_base a))
(spec (= (float_from_bits 64
(concat (select (mem) (bvadd a 3)) (select (mem) (bvadd a 2))
(select (mem) (bvadd a 1)) (select (mem) a)))
(result))))";
let problem =
query("t.rules", &rules(text)[0], &model()).expect_err("this rule cannot be asked");
assert!(
problem.message.contains("takes something 64 bits wide and this is 32 bits wide"),
"{}",
problem.message
);
}
#[test]
fn the_bytes_of_a_load_go_back_in_the_order_they_came_out() {
let Some(solver) = solver() else {
return;
};
let report = admit("t.rules", &rules(LOAD), &model(), &solver).expect("this is provable");
assert_eq!(report.discharged(), 1, "{report}");
let wrong = LOAD.replace("(bvadd a 3)) (select (mem) (bvadd a 2))", "a) (select (mem) a)");
let report = verify("t.rules", &rules(&wrong), &model(), &solver).expect("it is asked");
assert!(matches!(report.verdicts[0], Verdict::Refuted(_)), "{report}");
}
#[test]
fn a_conversion_to_an_integer_cuts_towards_zero_rather_than_rounding() {
let asked = query("t.rules", &rules(TO_INT)[0], &model()).expect("the model covers this rule");
assert!(asked.contains("((_ fp.to_sbv 32) RTZ x)"), "{asked}");
assert!(!asked.contains("RNE"), "{asked}");
assert!(!TO_INT.contains("RTZ"));
assert!(asked.starts_with("(set-logic QF_FPBV)\n"), "{asked}");
assert!(asked.contains("(declare-const x Float64)"), "{asked}");
}
#[test]
fn a_conversion_to_a_float_rounds_the_way_the_arithmetic_does() {
let text = "\
(rule (lower (sitofp.i32.f32 (value.i32 x)))
(x64.cvtsi2ss_32 x)
(spec (= (float_from_signed 32 32 x) (result))))";
let asked = query("t.rules", &rules(text)[0], &model()).expect("the model covers this rule");
assert!(asked.contains("((_ to_fp 8 24) RNE x)"), "{asked}");
assert!(asked.contains("(declare-const x (_ BitVec 32))"), "{asked}");
}
#[test]
fn a_conversion_from_a_number_that_is_given_a_float_is_refused() {
let text = "\
(rule (lower (sitofp.i32.f32 (value.f32 x)))
(x64.cvtsi2ss_32 x)
(spec (= (float_from_signed 32 32 x) (result))))";
let problem =
query("t.rules", &rules(text)[0], &model()).expect_err("this rule cannot be asked");
assert!(
problem.message.contains("takes something 32 bits wide and this is 32 bits of float"),
"{}",
problem.message
);
}
#[test]
fn a_reinterpretation_lowered_to_a_conversion_is_refuted() {
let Some(solver) = solver() else {
return;
};
let text = "\
(rule (lower (bitcast.i32.f32 (value.i32 x)))
(x64.cvtsi2ss_32 x)
(spec (= (float_from_bits 32 x) (result))))";
let report = verify("t.rules", &rules(text), &model(), &solver).expect("the question is asked");
assert!(matches!(report.verdicts[0], Verdict::Refuted(_)), "{report}");
}
#[test]
fn the_conversions_are_proved_wherever_they_have_an_answer() {
let Some(solver) = solver() else {
return;
};
let text = format!(
"{TO_INT}
(rule (lower (fpext.f32.f64 (value.f32 x)))
(x64.cvtss2sd x)
(spec (= (float_from_float 32 64 x) (result))))"
);
let report = admit("t.rules", &rules(&text), &model(), &solver).expect("both are provable");
assert_eq!(report.discharged(), 2, "{report}");
assert_eq!(report.bounded(), 0, "{report}");
}