use std::process::Command;
use object::{Object, ObjectSection};
use sha2::{Digest, Sha256};
fn synth() -> &'static str {
env!("CARGO_BIN_EXE_synth")
}
fn fixture(name: &str) -> std::path::PathBuf {
std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../..")
.join("scripts/repro")
.join(name)
}
fn text_sha256(wasm: &str, backend: &str, target: &str) -> (String, usize) {
let path = fixture(wasm);
let elf = format!("/tmp/frozenbytes_{backend}_{wasm}.elf");
let out = Command::new(synth())
.env_remove("SYNTH_NO_CMP_SELECT_FUSE")
.env_remove("SYNTH_NO_LOCAL_PROMOTE")
.env_remove("SYNTH_NO_IMM_SHIFT_FOLD")
.env_remove("SYNTH_CONST_CSE")
.args([
"compile",
path.to_str().unwrap(),
"-o",
&elf,
"-b",
backend,
"--target",
target,
"--all-exports",
"--relocatable",
])
.output()
.expect("run synth");
assert!(
out.status.success(),
"synth compile failed for {wasm} ({backend}/{target}): {}",
String::from_utf8_lossy(&out.stderr)
);
let bytes = std::fs::read(&elf).expect("read elf");
let obj = object::File::parse(&*bytes).expect("parse elf");
let text = obj
.section_by_name(".text")
.expect("frozen fixture must have a .text section");
let data = text.data().expect("read .text");
let mut hasher = Sha256::new();
hasher.update(data);
let digest = hasher.finalize();
let hex: String = digest.iter().map(|b| format!("{b:02x}")).collect();
(hex, data.len())
}
fn assert_frozen(cases: &[(&str, &str, usize)], backend: &str, target: &str) {
for &(wasm, golden, golden_len) in cases {
let (got, len) = text_sha256(wasm, backend, target);
assert_eq!(
len, golden_len,
"{wasm} ({backend}): .text length changed ({len} vs locked {golden_len}) \
— codegen moved. If intentional, re-freeze the golden HERE and re-run \
scripts/repro/*_differential.py on this commit (they move together)."
);
assert_eq!(
got, golden,
"{wasm} ({backend}): .text SHA-256 changed — a frozen fixture's machine \
code drifted with no deliberate re-freeze. This is the gate working: \
either a codegen change leaked in (investigate — dep bump? flag? \
selector?), or it is intentional, in which case update the golden HERE \
and re-run the scripts/repro/*_differential.py result checks on the \
SAME commit."
);
}
}
#[test]
fn frozen_fixtures_text_is_bit_identical_oracle_001() {
let cases = [
(
"control_step.wasm",
"1a97711cfb4754794a8577814388f08b81eff444edcba3de7d3e3d18ff435183",
304usize,
),
(
"flight_seam.wasm",
"9e73eea3867ba085820329951e84a7d650c38a7fc78d9d03a6a83d02963f9670",
774,
),
(
"flight_seam_flat.wasm",
"887ea546429a4569112147fdc94b0ba90f02a6ccd2b511aa2ca48dab017dbc2c",
910,
),
(
"signed_div_const.wasm",
"b277453b7829a5b2c64527131298d89fa63f5641231b1d4c7336675e8cdab9b0",
34,
),
];
assert_frozen(&cases, "arm", "cortex-m4");
}
#[test]
fn frozen_fixtures_rv32_text_is_bit_identical_oracle_001() {
let cases = [
(
"control_step.wasm",
"6e734c4c67b651067a56ff2a7593015f48ea24fe068a7a20c17f1c08a4e48e83",
504usize,
),
(
"signed_div_const.wasm",
"15fa429d5ef5474f8b65fdd9c81b3da4c70176fb077df25131e2ec3988eb999e",
88,
),
];
assert_frozen(&cases, "riscv", "rv32imac");
}