open-standards/wai/wai-ops/src/vectors.rs
Transaction Science 1cdd4a47f0 wai-ops: load step 8, the bounds pass and the certificate (D2b)
`wai_ops::bounds` applies §8.1–§8.3 rule by rule in 512-bit arithmetic,
saturating after every primitive operation and marking the node; classes each
intermediate and producer result (§7.1), checks the parameter conditions
(§7.2), finds the first failure and the accelerator verdict, and writes the
certificate canonically (§8.5). Admissible ranges (§8.4) and the clamps they
propose are there for `--advise`.

`graph::load` now takes the implementation's profile and runs step 8: the
pass, `lut16`'s range check, an empty `check` intersection, and the profile
against the verdict. The `uncertified` switch is gone, with the generator's
own tagger: the generator and the vector recomputation take `load` and
`profiles` from the certifier, and reproduce every committed vector.

Every set the corpus lowers now has its certificate beside its lowered
graphs, and the two falsification sets of §14.3 (an int_synth set one unit
past i64, and one whose pin claims acc32 and that is one weight past it) are
generated, lowered and certified, listed in certificates/falsification.json.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-10 09:26:27 -04:00

328 lines
14 KiB
Rust
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! The vector files of §14.1, recomputed from their inputs.
//!
//! [`recompute_file`] reads a JSON Lines file, keeps every line's inputs, recomputes
//! every line's `load` and `expect` values, and writes the file again canonically. A
//! conforming implementation's file is byte-identical to the committed one; every leg
//! of CI runs this same function, natively, under WASI and under a browser engine.
use alloc::string::{String, ToString};
use alloc::vec;
use alloc::vec::Vec;
use crate::bounds::Profile;
use crate::code::Code;
use crate::graph;
use crate::interp::{self, Sequence, Tensor};
use crate::json::{self, C, Value, hex, unhex};
use crate::stage::{self, rans64, mans, StageRefusal};
/// Which directory a file belongs to: `vectors/` and `graphs/` share the graph
/// vector format, `stage/` has its own.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Kind {
Graph,
Stage,
}
/// Why a line could not be recomputed: it is not a vector of its kind.
pub type Bad = String;
fn bad<T>(what: &str) -> Result<T, Bad> {
Err(what.to_string())
}
fn dec(v: &Value) -> Result<i64, Bad> {
v.as_dec_i64().ok_or_else(|| "an integer outside i64".to_string())
}
fn dec_u(v: &Value) -> Result<u64, Bad> {
let d = dec(v)?;
u64::try_from(d).map_err(|_| "a negative size".to_string())
}
fn get<'a>(v: &'a Value, k: &str) -> Result<&'a Value, Bad> {
v.get(k).ok_or_else(|| alloc::format!("no member {k}"))
}
fn arr<'a>(v: &'a Value, k: &str) -> Result<&'a [Value], Bad> {
get(v, k)?.as_arr().ok_or_else(|| alloc::format!("{k} is not an array"))
}
fn decs(v: &Value, k: &str) -> Result<Vec<i64>, Bad> {
arr(v, k)?.iter().map(dec).collect()
}
/// A tensor `{ "data": [...], "shape": [C, H, W] }`.
pub fn read_tensor(v: &Value) -> Result<Tensor, Bad> {
let s = arr(v, "shape")?;
if s.len() != 3 {
return bad("a shape has three dimensions");
}
let (c, h, w) = (dec_u(&s[0])?, dec_u(&s[1])?, dec_u(&s[2])?);
let data = decs(v, "data")?;
let (c, h, w) = (u32::try_from(c).map_err(|_| "size")?, u32::try_from(h).map_err(|_| "size")?, u32::try_from(w).map_err(|_| "size")?);
Ok(Tensor::new(c, h, w, data))
}
pub fn tensor_c(t: &Tensor) -> C {
C::obj(vec![
("data", C::ints(t.data.iter().map(|&v| v as i128))),
("shape", C::ints([t.c as i128, t.h as i128, t.w as i128])),
])
}
/// Recompute one graph vector (§14.1): `load` (steps 1–8), `profiles` and every
/// decode's `expect`. A graph that loads lists exactly the profiles its verdict
/// meets, and its decodes run; one whose load is refused lists both profiles, since
/// an implementation of either refuses it, and has no decodes. The `profiles` a line
/// carries are not read: they are recomputed.
pub fn graph_line(v: &Value) -> Result<C, Bad> {
let hexg = get(v, "graph")?.as_str().ok_or("graph is not a string")?;
let bytes = unhex(hexg).ok_or("graph is not lowercase hexadecimal")?;
let id = get(v, "id")?.as_str().ok_or("id is not a string")?;
arr(v, "profiles")?;
let decodes = arr(v, "decodes")?;
let loaded = graph::load(&bytes, Profile::Acc64);
let (load, profiles) = match &loaded {
Ok(g) => (C::s("ok"), Profile::ALL.iter().filter(|p| g.meets(**p)).map(|p| C::s(p.as_str())).collect()),
Err(r) => (C::s(r.code.as_str()), Profile::ALL.iter().map(|p| C::s(p.as_str())).collect()),
};
let mut out = Vec::new();
if let Ok(g) = &loaded {
let mut seq = Sequence::new();
for d in decodes {
let inputs: Vec<Tensor> = arr(d, "inputs")?.iter().map(read_tensor).collect::<Result<_, _>>()?;
let scalars = decs(d, "scalars")?;
let expect = match interp::run(g, &inputs, &scalars, &mut seq) {
Ok(outs) => C::obj(vec![("outputs", C::Arr(outs.iter().map(tensor_c).collect()))]),
Err(r) => C::obj(vec![("refuse", C::s(r.code.as_str()))]),
};
out.push(C::obj(vec![
("expect", expect),
("inputs", C::Arr(inputs.iter().map(tensor_c).collect())),
("scalars", C::ints(scalars.iter().map(|&s| s as i128))),
]));
}
}
Ok(C::obj(vec![
("decodes", C::Arr(out)),
("graph", C::Str(hex(&bytes))),
("id", C::s(id)),
("load", load),
("profiles", C::Arr(profiles)),
]))
}
fn refused(r: StageRefusal) -> C {
C::obj(vec![("at", C::int(r.at as i128)), ("refuse", C::s(r.code.as_str()))])
}
fn str_of<'a>(v: &'a Value, k: &str) -> Result<&'a str, Bad> {
get(v, k)?.as_str().ok_or_else(|| alloc::format!("{k} is not a string"))
}
/// The modes of E1 from a vector's `modes`.
pub fn rans_modes(m: &Value) -> Result<rans64::Modes, Bad> {
let table = match str_of(m, "table")? {
"t4097" => rans64::TableRule::T4097,
"t2" => rans64::TableRule::T2,
_ => return bad("table rule"),
};
let escape = match str_of(m, "escape")? {
"lenient" => rans64::Escape::Lenient,
"strict" => {
let lo = i32::try_from(dec(get(m, "lo")?)?).map_err(|_| "lo")?;
let hi = i32::try_from(dec(get(m, "hi")?)?).map_err(|_| "hi")?;
rans64::Escape::Strict { lo, hi }
}
_ => return bad("escape rule"),
};
let end = match str_of(m, "end")? {
"canonical" => rans64::End::Canonical,
"final_only" => rans64::End::FinalOnly,
_ => return bad("end rule"),
};
Ok(rans64::Modes { table, escape, end })
}
/// Compute a stage vector's `expect` from its `op`, `in` and `modes`.
pub fn stage_expect(op: &str, i: &Value, m: &Value) -> Result<C, Bad> {
let one = |k: &str, v: C| C::obj(vec![(k, v)]);
let before = |code: Code| refused(stage::before(code));
let at0 = |code: Code| refused(StageRefusal { code, at: 0 });
Ok(match op {
"rans64_decode" => {
let modes = rans_modes(m)?;
let stream = unhex(str_of(i, "stream")?).ok_or("stream hex")?;
let tables: Vec<rans64::Table> = arr(i, "tables")?
.iter()
.map(|t| Ok(rans64::Table { cdf: decs(t, "cdf")?, offset: dec(get(t, "offset")?)? }))
.collect::<Result<_, Bad>>()?;
let reads: Vec<usize> = decs(i, "reads")?.iter().map(|&r| usize::try_from(r).unwrap_or(usize::MAX)).collect();
match rans64::decode(&stream, &tables, &reads, modes) {
Ok(s) => one("symbols", C::ints(s.iter().map(|&v| v as i128))),
Err(r) => refused(r),
}
}
"dequantize" => {
let (f, sym, base) = (dec(get(i, "f")?)?, dec(get(i, "sym")?)?, dec(get(i, "base")?)?);
match u32::try_from(f).ok().map(|f| stage::dequantize(sym, f, base)) {
Some(Ok(v)) => one("value", C::int(v as i128)),
_ => at0(Code::FieldRange),
}
}
"bucket" => {
let sigma = dec(get(i, "sigma")?)?;
let edges = decs(i, "edges")?;
let r = match str_of(m, "bucket")? {
"ceil" => stage::bucket_ceil(sigma, &edges),
"floor" => stage::bucket_floor(sigma, &edges),
_ => return bad("bucket rule"),
};
match r {
Some(b) => one("index", C::int(b as i128)),
None => before(Code::ModelMismatch),
}
}
"coded" => one("coded", C::Bool(stage::coded(dec(get(i, "v")?)?, dec(get(i, "thr")?)?))),
"scale_index" => {
let s = u32::try_from(dec(get(i, "s")?)?).unwrap_or(u32::MAX);
match stage::scale_index(dec(get(i, "v")?)?, s, dec(get(i, "n")?)?) {
Some(v) => one("index", C::int(v as i128)),
None => before(Code::ModelMismatch),
}
}
"block_stat" => {
let x = read_tensor(get(i, "x")?)?;
let b = u32::try_from(dec(get(i, "b")?)?).unwrap_or(0);
let s = u32::try_from(dec(get(i, "s")?)?).unwrap_or(u32::MAX);
match stage::block_stat(&x, b, s, dec(get(i, "lo")?)?, dec(get(i, "hi")?)?) {
Some(t) => one("out", tensor_c(&t)),
None => before(Code::ModelMismatch),
}
}
"band_lookup" => {
let table: Vec<Vec<i64>> = arr(i, "table")?.iter().map(|r| r.as_arr().ok_or("row").map(|r| r.iter().map(dec).collect::<Result<Vec<_>, _>>())).collect::<Result<Result<_, _>, _>>()??;
let row = usize::try_from(dec(get(i, "row")?)?).unwrap_or(usize::MAX);
match stage::band_lookup(dec(get(i, "v")?)?, &decs(i, "edges")?, &table, row) {
Some(v) => one("value", C::int(v as i128)),
None => before(Code::ModelMismatch),
}
}
"mans_decode" => {
let mem = unhex(str_of(i, "memory")?).ok_or("memory hex")?;
let r = match str_of(i, "mode")? {
"sgm" => {
let pmf: Vec<Vec<i64>> = arr(i, "pmf")?.iter().map(|r| r.as_arr().ok_or("pmf").map(|r| r.iter().map(dec).collect::<Result<Vec<_>, _>>())).collect::<Result<Result<_, _>, _>>()??;
let spans: Vec<mans::Span> = arr(i, "spans")?
.iter()
.map(|s| {
let mask = arr(s, "mask")?.iter().map(|b| b.as_bool().ok_or_else(|| "mask".to_string())).collect::<Result<Vec<_>, _>>()?;
let sigma = decs(s, "sigma")?.iter().map(|&g| usize::try_from(g).unwrap_or(usize::MAX)).collect();
Ok(mans::Span { mask, sigma })
})
.collect::<Result<_, Bad>>()?;
mans::decode_sgm(&mem, &pmf, &decs(i, "bounds")?, &spans)
}
"fac" => {
let rows: Vec<Vec<i64>> = arr(i, "rows")?.iter().map(|r| r.as_arr().ok_or("rows").map(|r| r.iter().map(dec).collect::<Result<Vec<_>, _>>())).collect::<Result<Result<_, _>, _>>()??;
let lengths: Vec<usize> = decs(i, "lengths")?.iter().map(|&l| usize::try_from(l).unwrap_or(usize::MAX)).collect();
mans::decode_fac(&mem, &rows, &lengths)
}
_ => return bad("mans mode"),
};
match r {
Ok(s) => one("symbols", C::Arr(s.iter().map(|row| C::ints(row.iter().map(|&v| v as i128))).collect())),
Err(r) => refused(r),
}
}
"residual_step" => {
let g = |k| dec(get(i, k)?);
match stage::residual_step(g("pred")?, g("r")?, g("q")?, g("lo")?, g("hi")?) {
Some(v) => one("value", C::int(v as i128)),
None => before(Code::ModelMismatch),
}
}
"mc_predict" => {
let f = read_tensor(get(i, "frame")?)?;
let g = |k| dec(get(i, k)?);
let c = usize::try_from(g("c")?).unwrap_or(usize::MAX);
let p = u32::try_from(g("precision")?).unwrap_or(0);
match stage::mc_predict(&f, g("y")?, g("x")?, c, g("dy")?, g("dx")?, p) {
Some(v) => one("value", C::int(v as i128)),
None => before(Code::ModelMismatch),
}
}
"up2_predict" => {
let p = read_tensor(get(i, "prev")?)?;
let d0 = u32::try_from(dec(get(i, "d0")?)?).unwrap_or(0);
let d1 = u32::try_from(dec(get(i, "d1")?)?).unwrap_or(0);
match stage::up2_predict(&p, d0, d1) {
Some(t) => one("out", tensor_c(&t)),
None => before(Code::GeometryMismatch),
}
}
"rate_predict" => {
let p = read_tensor(get(i, "prev")?)?;
let u = |k| u32::try_from(dec(get(i, k)?)?).map_err(|_| "u32".to_string());
match stage::rate_predict(&p, u("rate_num")?, u("rate_den")?, u("d0")?) {
Some(t) => one("out", tensor_c(&t)),
None => before(Code::GeometryMismatch),
}
}
"coding_order" => {
let s = decs(i, "shape")?;
if s.len() != 3 || s.iter().any(|&d| !(1..=u32::MAX as i64).contains(&d)) {
return bad("shape");
}
let cb = match str_of(i, "order")? {
"raster" => None,
"checkerboard" => Some(get(i, "anchor")?.as_bool().ok_or("anchor")?),
_ => return bad("order"),
};
one("indices", C::ints(stage::coding_order(s[0] as u32, s[1] as u32, s[2] as u32, cb).iter().map(|&v| v as i128)))
}
_ => return bad("an unknown stage operator"),
})
}
/// Recompute one stage vector.
pub fn stage_line(v: &Value) -> Result<C, Bad> {
let op = str_of(v, "op")?;
let i = get(v, "in")?;
let m = get(v, "modes")?;
let id = str_of(v, "id")?;
let expect = stage_expect(op, i, m)?;
Ok(C::obj(vec![("expect", expect), ("id", C::s(id)), ("in", value_to_c(i)?), ("modes", value_to_c(m)?), ("op", C::s(op))]))
}
/// A canonical value read back: strings, booleans, arrays, objects and null. A JSON
/// number cannot appear in a canonical file.
pub fn value_to_c(v: &Value) -> Result<C, Bad> {
Ok(match v {
Value::Null => C::Null,
Value::Bool(b) => C::Bool(*b),
Value::Str(s) => C::s(s),
Value::Num(_) => return bad("a number in a canonical file"),
Value::Arr(a) => C::Arr(a.iter().map(value_to_c).collect::<Result<_, _>>()?),
Value::Obj(m) => C::Obj(m.iter().map(|(k, x)| Ok((k.clone(), value_to_c(x)?))).collect::<Result<_, Bad>>()?),
})
}
/// Recompute one line.
pub fn recompute_line(kind: Kind, line: &str) -> Result<String, Bad> {
let v = json::parse(line.as_bytes()).map_err(|e| alloc::format!("JSON at byte {}", e.at))?;
let c = match kind {
Kind::Graph => graph_line(&v)?,
Kind::Stage => stage_line(&v)?,
};
Ok(json::to_string(&c))
}
/// Recompute every line of a file. Every line, the last included, ends with one
/// line feed.
pub fn recompute_file(kind: Kind, text: &str) -> Result<String, Bad> {
let mut out = String::with_capacity(text.len());
for (n, line) in text.split_terminator('\n').enumerate() {
let r = recompute_line(kind, line).map_err(|e| alloc::format!("line {}: {e}", n + 1))?;
out.push_str(&r);
out.push('\n');
}
Ok(out)
}