use std::collections::HashMap;
use std::path::Path;
use std::process::Command;
use object::{Object, ObjectSection, ObjectSymbol, SymbolKind};
use sha2::{Digest, Sha256};
fn synth() -> &'static str {
env!("CARGO_BIN_EXE_synth")
}
fn fixture(rel: &str) -> std::path::PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../..")
.join(rel)
}
fn compile(rel: &str, out: &str, default_on: bool) -> Vec<u8> {
let mut cmd = Command::new(synth());
cmd.env_remove("SYNTH_CONST_CSE");
if default_on {
cmd.env_remove("SYNTH_BASE_CSE");
} else {
cmd.env("SYNTH_BASE_CSE", "0");
}
let out_status = cmd
.args([
"compile",
fixture(rel).to_str().unwrap(),
"-o",
out,
"-b",
"arm",
"--target",
"cortex-m4",
"--all-exports",
])
.output()
.expect("run synth compile");
assert!(
out_status.status.success(),
"synth compile failed ({rel}, default_on={default_on}): {}",
String::from_utf8_lossy(&out_status.stderr)
);
std::fs::read(out).expect("read ELF")
}
fn text_bytes(elf: &[u8]) -> Vec<u8> {
let obj = object::File::parse(elf).expect("parse ELF");
obj.section_by_name(".text")
.expect(".text present")
.data()
.expect("read .text")
.to_vec()
}
fn sha256_hex(bytes: &[u8]) -> String {
Sha256::digest(bytes)
.iter()
.map(|b| format!("{b:02x}"))
.collect()
}
fn func_sizes(elf: &[u8]) -> HashMap<String, usize> {
let obj = object::File::parse(elf).expect("parse ELF");
let mut out = HashMap::new();
for sym in obj.symbols() {
if sym.kind() == SymbolKind::Text
&& sym.size() > 0
&& let Ok(name) = sym.name()
{
out.insert(name.to_string(), sym.size() as usize);
}
}
out
}
const GOLDENS: [(&str, &str, usize, &str, usize); 2] = [
(
"scripts/repro/redundant_base_materialization.wat",
"a0f684bcfaaece182b55fdb2e98b94d54bbeab72243764ff354ed67b79a56aef",
224,
"b44084bebcc57701b39510d365a7e066d62f9bfae5a6f93a7860fe6e87f90a05",
326,
),
(
"scripts/repro/volatile_segment_543.wat",
"86af859f443eaaddf01a2a99811b81a60c66b6fe4118b53ceea121511c88070b",
194,
"eb9c8355416778fba8bedc26d1cf672a6f0334d915492f5cdffd95381d7572f7",
256,
),
];
#[test]
fn base_cse_escape_hatch_restores_old_bytes_468() {
for &(rel, on_sha, on_len, off_sha, off_len) in &GOLDENS {
let tag = Path::new(rel).file_stem().unwrap().to_str().unwrap();
let on = text_bytes(&compile(rel, &format!("/tmp/bcse468_{tag}_on.elf"), true));
assert_eq!(
(on.len(), sha256_hex(&on).as_str()),
(on_len, on_sha),
"{rel}: shipped DEFAULT .text drifted from the flip golden"
);
let off = text_bytes(&compile(rel, &format!("/tmp/bcse468_{tag}_off.elf"), false));
assert_eq!(
(off.len(), sha256_hex(&off).as_str()),
(off_len, off_sha),
"{rel}: SYNTH_BASE_CSE=0 must restore the pre-flip bytes (rollback broken)"
);
}
}
#[test]
fn base_cse_no_grow_corpus_468() {
let corpus = [
"scripts/repro/redundant_base_materialization.wat",
"scripts/repro/volatile_segment_543.wat",
"scripts/repro/base_cse_branch.wat",
"scripts/repro/const_cse.wat",
"scripts/repro/flight_seam.wat",
"scripts/repro/flat_flight/flat_flight.loom.wasm",
];
let mut fired = 0usize;
for rel in corpus {
let tag = format!(
"ng_{}",
Path::new(rel).file_stem().unwrap().to_str().unwrap()
);
let off = func_sizes(&compile(rel, &format!("/tmp/bcse468_{tag}_off.elf"), false));
let on = func_sizes(&compile(rel, &format!("/tmp/bcse468_{tag}_on.elf"), true));
for (name, &o) in &off {
let n = *on.get(name).unwrap_or(&o);
assert!(
n <= o,
"no function may grow under default base-CSE: {name} opt-out={o}B default={n}B ({rel})"
);
if n < o {
fired += 1;
}
}
}
assert!(
fired >= 2,
"non-vacuity: base-CSE must shrink at least the two firing fixtures, saw {fired} shrinking function(s)"
);
}