#![allow(dead_code)]
use std::collections::BTreeSet;
use thiserror::Error;
const OP_MANIFEST_JSON: &str = include_str!("../fixtures/kiss-corpus/op_manifest.json");
const OPS_ARITH_JSON: &str = include_str!("../fixtures/kiss-corpus/ops-arith.json");
const OPS_MINMAX_ORDINARY_JSON: &str =
include_str!("../fixtures/kiss-corpus/ops-minmax-ordinary.json");
const OPS_MINMAX_SIGNED_ZERO_JSON: &str =
include_str!("../fixtures/kiss-corpus/ops-minmax-signed-zero.json");
const OPS_NARROW_MOVE_NAN_JSON: &str =
include_str!("../fixtures/kiss-corpus/ops-narrow-move-nan.json");
const OPS_NARROW_SELECT_NAN_JSON: &str =
include_str!("../fixtures/kiss-corpus/ops-narrow-select-nan.json");
const OPS_TRANSCENDENTAL_NAN_JSON: &str =
include_str!("../fixtures/kiss-corpus/ops-transcendental-nan.json");
const OPS_MINMAX_NAN_JSON: &str = include_str!("../fixtures/kiss-corpus/ops-minmax-nan.json");
const OPS_ROUNDING_JSON: &str = include_str!("../fixtures/kiss-corpus/ops-rounding.json");
#[derive(Debug, Error)]
pub enum CorpusError {
#[error("corpus JSON parse error in {file}: {source}")]
Json {
file: &'static str,
#[source]
source: serde_json::Error,
},
#[error("corpus schema error in {file}: {detail}")]
Schema { file: &'static str, detail: String },
#[error("corpus hex parse error in {file} (tcId {tc_id}): {detail}")]
Hex {
file: &'static str,
tc_id: u64,
detail: String,
},
}
#[derive(Debug, Clone)]
pub struct CorpusVector {
pub tc_id: u64,
pub op: String,
pub dtype: String,
pub rounding: String,
pub inputs: Vec<Vec<u8>>,
pub expected: Vec<u8>,
pub class: String,
pub ulp_bound: u64,
}
#[derive(Debug, Clone, Default)]
pub struct Corpus {
pub all_ops: Vec<String>,
pub transcendental_atoms: Vec<String>,
pub declared_coverage: Vec<String>,
pub vectors: Vec<CorpusVector>,
}
impl Corpus {
pub fn covers(&self, op: &str, dtype: &str) -> bool {
self.vectors.iter().any(|v| v.op == op && v.dtype == dtype)
}
pub fn cells(&self, op: &str, dtype: &str) -> Vec<&CorpusVector> {
self.vectors
.iter()
.filter(|v| v.op == op && v.dtype == dtype)
.collect()
}
pub fn covered_cells(&self) -> BTreeSet<(String, String)> {
self.vectors
.iter()
.map(|v| (v.op.clone(), v.dtype.clone()))
.collect()
}
pub fn declares_op(&self, op: &str) -> bool {
self.declared_coverage.iter().any(|o| o == op)
}
}
fn parse_hex_bytes(s: &str, file: &'static str, tc_id: u64) -> Result<Vec<u8>, CorpusError> {
let cleaned: Vec<u8> = s
.bytes()
.filter(|b| !b.is_ascii_whitespace() && *b != b'.')
.filter(|b| *b != 0xC2 && *b != 0xB7)
.collect();
if !cleaned.len().is_multiple_of(2) {
return Err(CorpusError::Hex {
file,
tc_id,
detail: format!("odd hex-digit count {} in {s:?}", cleaned.len()),
});
}
let hexval = |b: u8| -> Option<u8> {
match b {
b'0'..=b'9' => Some(b - b'0'),
b'a'..=b'f' => Some(b - b'a' + 10),
b'A'..=b'F' => Some(b - b'A' + 10),
_ => None,
}
};
let mut out = Vec::with_capacity(cleaned.len() / 2);
let (pairs, _odd_tail) = cleaned.as_chunks::<2>();
for pair in pairs {
match (hexval(pair[0]), hexval(pair[1])) {
(Some(hi), Some(lo)) => out.push((hi << 4) | lo),
_ => {
return Err(CorpusError::Hex {
file,
tc_id,
detail: format!("non-hex digit in {s:?}"),
});
}
}
}
Ok(out)
}
fn parse_vector_file(
json: &'static str,
file: &'static str,
) -> Result<Vec<CorpusVector>, CorpusError> {
let doc: serde_json::Value =
serde_json::from_str(json).map_err(|source| CorpusError::Json { file, source })?;
let arr = doc
.get("vectors")
.and_then(|v| v.as_array())
.ok_or_else(|| CorpusError::Schema {
file,
detail: "missing `vectors` array".to_string(),
})?;
let mut vectors = Vec::with_capacity(arr.len());
for v in arr {
let tc_id = v.get("tcId").and_then(|x| x.as_u64()).unwrap_or(0);
let req_str = |key: &str| -> Result<String, CorpusError> {
v.get(key)
.and_then(|x| x.as_str())
.map(String::from)
.ok_or_else(|| CorpusError::Schema {
file,
detail: format!("tcId {tc_id}: missing string field `{key}`"),
})
};
let op = req_str("op")?;
let dtype = req_str("dtype")?;
let rounding = v
.get("rounding")
.and_then(|x| x.as_str())
.unwrap_or("")
.to_string();
let class = v
.get("class")
.and_then(|x| x.as_str())
.unwrap_or("")
.to_string();
let ulp_bound = v.get("ulp_bound").and_then(|x| x.as_u64()).unwrap_or(0);
let inputs_arr =
v.get("inputs")
.and_then(|x| x.as_array())
.ok_or_else(|| CorpusError::Schema {
file,
detail: format!("tcId {tc_id}: missing `inputs` array"),
})?;
let mut inputs = Vec::with_capacity(inputs_arr.len());
for inp in inputs_arr {
let bits =
inp.get("bits")
.and_then(|x| x.as_str())
.ok_or_else(|| CorpusError::Schema {
file,
detail: format!("tcId {tc_id}: input missing `bits`"),
})?;
inputs.push(parse_hex_bytes(bits, file, tc_id)?);
}
let expected_bits = v
.get("expected")
.and_then(|x| x.get("bits"))
.and_then(|x| x.as_str())
.ok_or_else(|| CorpusError::Schema {
file,
detail: format!("tcId {tc_id}: missing `expected.bits`"),
})?;
let expected = parse_hex_bytes(expected_bits, file, tc_id)?;
vectors.push(CorpusVector {
tc_id,
op,
dtype,
rounding,
inputs,
expected,
class,
ulp_bound,
});
}
Ok(vectors)
}
pub fn load_vendored_corpus() -> Result<Corpus, CorpusError> {
let manifest: serde_json::Value =
serde_json::from_str(OP_MANIFEST_JSON).map_err(|source| CorpusError::Json {
file: "op_manifest.json",
source,
})?;
let string_list = |key: &str| -> Vec<String> {
manifest
.get(key)
.and_then(|v| v.as_array())
.map(|a| {
a.iter()
.filter_map(|x| x.as_str().map(String::from))
.collect()
})
.unwrap_or_default()
};
let all_ops = string_list("all_ops");
let transcendental_atoms = string_list("transcendental_atoms");
let declared_coverage = string_list("declared_coverage_set");
let mut vectors = Vec::new();
for (json, file) in [
(OPS_ARITH_JSON, "ops-arith.json"),
(OPS_MINMAX_ORDINARY_JSON, "ops-minmax-ordinary.json"),
(OPS_MINMAX_SIGNED_ZERO_JSON, "ops-minmax-signed-zero.json"),
(OPS_NARROW_MOVE_NAN_JSON, "ops-narrow-move-nan.json"),
(OPS_NARROW_SELECT_NAN_JSON, "ops-narrow-select-nan.json"),
(OPS_TRANSCENDENTAL_NAN_JSON, "ops-transcendental-nan.json"),
(OPS_MINMAX_NAN_JSON, "ops-minmax-nan.json"),
(OPS_ROUNDING_JSON, "ops-rounding.json"),
] {
vectors.extend(parse_vector_file(json, file)?);
}
Ok(Corpus {
all_ops,
transcendental_atoms,
declared_coverage,
vectors,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn vendored_corpus_covers_add_f32_with_five_exact_byte_vectors() {
let corpus = load_vendored_corpus().expect("vendored corpus parses");
assert!(
corpus.declares_op("add"),
"op_manifest declared_coverage_set should include add"
);
assert!(
corpus.covers("add", "f32"),
"corpus should cover (add, f32)"
);
let cells = corpus.cells("add", "f32");
assert_eq!(
cells.len(),
5,
"expected 5 add/f32 vectors, got {}",
cells.len()
);
for c in &cells {
assert_eq!(c.class, "exact-byte");
assert_eq!(c.ulp_bound, 0);
}
let tc4 = cells.iter().find(|c| c.tc_id == 4).expect("tcId 4 present");
assert_eq!(
tc4.inputs,
vec![vec![0x3F, 0x80, 0x00, 0x00], vec![0x3F, 0x80, 0x00, 0x00]]
);
assert_eq!(tc4.expected, vec![0x40, 0x00, 0x00, 0x00]);
assert!(
!corpus.covers("add", "f64"),
"corpus should NOT cover (add, f64)"
);
assert!(
!corpus.covers("mul", "f32"),
"corpus should NOT cover (mul, f32)"
);
assert!(corpus.cells("mul", "f32").is_empty());
}
#[test]
fn corpus_reader_exposes_manifest_metadata() {
let corpus = load_vendored_corpus().expect("vendored corpus parses");
assert!(corpus.all_ops.contains(&"add".to_string()));
assert!(corpus.transcendental_atoms.contains(&"exp".to_string()));
assert!(!corpus.transcendental_atoms.contains(&"add".to_string()));
}
#[test]
fn hex_parser_rejects_bad_input_without_panicking() {
assert_eq!(
parse_hex_bytes("3F 80 00 00", "t", 0).unwrap(),
vec![0x3F, 0x80, 0x00, 0x00]
);
assert!(parse_hex_bytes("3F 8", "t", 0).is_err()); assert!(parse_hex_bytes("ZZ", "t", 0).is_err()); }
#[test]
fn the_minmax_vectors_are_loaded_not_merely_vendored() {
let corpus = load_vendored_corpus().expect("vendored corpus parses");
for (op, want) in [
("max_prop", 38usize),
("min_prop", 38),
("fmax_ieee", 40),
("fmin_ieee", 40),
] {
let n = corpus.vectors.iter().filter(|v| v.op == op).count();
assert_eq!(
n, want,
"expected {want} vectors for `{op}`; got {n}. A shortfall of 8 \
(prop) or 12 (ieee) means `ops-narrow-select-nan.json` is in the \
directory but not in `include_str!`."
);
}
assert_eq!(
corpus.cells("add", "f32").len(),
5,
"the original 5 add/f32 vectors must survive the re-vendor"
);
}
type MinmaxCells = std::collections::BTreeMap<
(String, Vec<Vec<u8>>),
std::collections::BTreeMap<String, Vec<u8>>,
>;
fn minmax_cells(corpus: &Corpus) -> MinmaxCells {
let mut cells = MinmaxCells::new();
for v in &corpus.vectors {
if matches!(
v.op.as_str(),
"max_prop" | "min_prop" | "fmax_ieee" | "fmin_ieee"
) {
cells
.entry((v.dtype.clone(), v.inputs.clone()))
.or_default()
.insert(v.op.clone(), v.expected.clone());
}
}
cells
}
fn cell_discriminates_prop_from_ieee(
ops: &std::collections::BTreeMap<String, Vec<u8>>,
) -> bool {
let differs =
|a: &str, b: &str| matches!((ops.get(a), ops.get(b)), (Some(x), Some(y)) if x != y);
differs("max_prop", "fmax_ieee") || differs("min_prop", "fmin_ieee")
}
fn is_nan_bits(dtype: &str, bytes: &[u8]) -> bool {
match (dtype, bytes) {
("bf16", [h, l]) => {
let v = u16::from_be_bytes([*h, *l]);
v & 0x7F80 == 0x7F80 && v & 0x007F != 0
}
("f16", [h, l]) => {
let v = u16::from_be_bytes([*h, *l]);
v & 0x7C00 == 0x7C00 && v & 0x03FF != 0
}
("f8e5m2", [b]) => b & 0x7C == 0x7C && b & 0x03 != 0,
("f8e4m3fn", [b]) => b & 0x7F == 0x7F,
("f32", [a, b, c, d]) => {
let v = u32::from_be_bytes([*a, *b, *c, *d]);
v & 0x7F80_0000 == 0x7F80_0000 && v & 0x007F_FFFF != 0
}
("f64", b) if b.len() == 8 => {
let v = u64::from_be_bytes([b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]]);
v & 0x7FF0_0000_0000_0000 == 0x7FF0_0000_0000_0000 && v & 0x000F_FFFF_FFFF_FFFF != 0
}
_ => false,
}
}
#[test]
fn the_corpus_discriminates_prop_from_ieee_minmax_on_the_nan_axis() {
let corpus = load_vendored_corpus().expect("vendored corpus parses");
let cells = minmax_cells(&corpus);
let max_vs_min = cells
.values()
.filter(|ops| {
matches!(
(ops.get("max_prop"), ops.get("min_prop")),
(Some(a), Some(b)) if a != b
)
})
.count();
assert!(
max_vs_min > 0,
"control failed: no cell separates max from min, so the cell grouping is broken"
);
let discriminating: Vec<_> = cells
.iter()
.filter(|(_, ops)| cell_discriminates_prop_from_ieee(ops))
.collect();
assert_eq!(
discriminating.len(),
18,
"expected 18 prop-vs-ieee discriminating cells: 2 orderings x 4 narrow dtypes \
(8, ops-narrow-select-nan) + 5 f32 + 5 f64 (ops-minmax-nan)"
);
for ((dtype, inputs), _) in &discriminating {
assert!(
inputs.iter().any(|b| is_nan_bits(dtype, b)),
"a discriminating cell with no NaN operand ({dtype} {inputs:?}) would mean the \
separation is NOT on the NaN axis, so the reading above is wrong"
);
}
let mut dtypes: Vec<&str> = discriminating
.iter()
.map(|((d, _), _)| d.as_str())
.collect();
dtypes.sort_unstable();
dtypes.dedup();
assert_eq!(dtypes, ["bf16", "f16", "f32", "f64", "f8e4m3fn", "f8e5m2"]);
}
#[test]
fn the_corpus_discriminates_prop_from_ieee_minmax_for_f32_and_f64() {
let corpus = load_vendored_corpus().expect("vendored corpus parses");
let cells = minmax_cells(&corpus);
for dtype in ["f32", "f64"] {
let separates_max_min = cells.iter().any(|((d, _), ops)| {
d == dtype
&& matches!(
(ops.get("max_prop"), ops.get("min_prop")),
(Some(a), Some(b)) if a != b
)
});
assert!(separates_max_min, "control failed for {dtype}");
}
for dtype in ["f32", "f64"] {
let hits: Vec<_> = cells
.iter()
.filter(|((d, _), ops)| d == dtype && cell_discriminates_prop_from_ieee(ops))
.collect();
assert_eq!(
hits.len(),
5,
"expected 5 {dtype} prop-vs-ieee discriminating cells, got {}",
hits.len()
);
for ((d, inputs), _) in &hits {
assert!(
inputs.iter().any(|b| is_nan_bits(d, b)),
"an f32/f64 discriminating cell with no NaN operand: {d} {inputs:?}"
);
}
}
}
#[test]
fn the_nan_vectors_are_loaded_not_merely_vendored() {
let corpus = load_vendored_corpus().expect("vendored corpus parses");
assert_eq!(corpus.vectors.len(), 276);
for (op, dtype) in [
("neg", "bf16"),
("abs", "f8e4m3fn"),
("copysign", "f16"),
("exp", "f32"),
("log", "f64"),
("sin", "f32"),
] {
assert!(
corpus.covers(op, dtype),
"({op}, {dtype}) is in a NaN file but not covered: the file is not in `include_str!`"
);
}
let ulp: Vec<_> = corpus.vectors.iter().filter(|v| v.class == "ULP").collect();
assert_eq!(ulp.len(), 13);
assert!(ulp.iter().all(|v| v.ulp_bound > 0));
}
}