use std::collections::BTreeMap;
use std::process::Command;
use object::{Object, ObjectSection, ObjectSymbol, SymbolKind};
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)
}
const CORPUS: &[&str] = &[
"control_step.wasm",
"flight_seam.wasm",
"flight_seam_flat.wasm",
"signed_div_const.wasm",
"i32_shift_mask_682.wat",
"gust_mix_686.wat",
"gpio_thin_846.loom.wasm",
];
const VARIANTS: &[(&str, bool)] = &[("relocatable", true), ("default", false)];
fn compile(wasm: &str, relocatable: bool, elide: Option<&str>) -> (Vec<u8>, BTreeMap<String, u64>) {
let path = fixture(wasm);
let elf = format!(
"/tmp/shift_mask_elide_686_{}_{}_{}.o",
wasm.replace('.', "_"),
relocatable,
elide.unwrap_or("unset")
);
let mut cmd = Command::new(synth());
cmd.env_remove("SYNTH_SHIFT_MASK_ELIDE");
if let Some(v) = elide {
cmd.env("SYNTH_SHIFT_MASK_ELIDE", v);
}
cmd.args([
"compile",
path.to_str().unwrap(),
"-o",
&elf,
"-b",
"arm",
"--target",
"cortex-m4",
"--all-exports",
]);
if relocatable {
cmd.arg("--relocatable");
}
let out = cmd.output().expect("run synth");
assert!(
out.status.success(),
"synth compile failed for {wasm} (relocatable={relocatable}): {}",
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(".text");
let data = text.data().expect("read .text").to_vec();
let end = text.address() + data.len() as u64;
let mut starts: Vec<(u64, String)> = obj
.symbols()
.filter(|s| {
!s.name().unwrap_or("").is_empty()
&& matches!(
s.kind(),
SymbolKind::Text | SymbolKind::Label | SymbolKind::Unknown
)
&& s.address() >= text.address()
&& s.address() < end
})
.map(|s| (s.address(), s.name().unwrap().to_string()))
.collect();
starts.sort();
starts.dedup_by(|a, b| a.0 == b.0); let mut sizes = BTreeMap::new();
for (i, (addr, name)) in starts.iter().enumerate() {
let next = starts.get(i + 1).map(|(a, _)| *a).unwrap_or(end);
sizes.insert(name.clone(), next - addr);
}
(data, sizes)
}
#[test]
fn shift_mask_elide_686_default_is_on_and_optout_rolls_back() {
let mut optout_differs = false;
for &(vname, reloc) in VARIANTS {
for &wasm in CORPUS {
let (unset, _) = compile(wasm, reloc, None);
let (on, _) = compile(wasm, reloc, Some("1"));
assert_eq!(
unset, on,
"{wasm} [{vname}]: default must equal explicit ON (flag is default-on since #846)"
);
let (off, _) = compile(wasm, reloc, Some("0"));
if off != unset {
optout_differs = true;
}
}
}
assert!(
optout_differs,
"SYNTH_SHIFT_MASK_ELIDE=0 never changed bytes — the opt-out rollback is vacuous"
);
}
#[test]
fn shift_mask_elide_686_per_function_no_grow_and_gust_mix_recovers() {
for &(vname, reloc) in VARIANTS {
for &wasm in CORPUS {
let (off_bytes, off) = compile(wasm, reloc, Some("0"));
let (on_bytes, on) = compile(wasm, reloc, Some("1"));
assert_eq!(
off.keys().collect::<Vec<_>>(),
on.keys().collect::<Vec<_>>(),
"{wasm} [{vname}]: the flag must not add/drop functions"
);
for (name, off_size) in &off {
let on_size = on[name];
assert!(
on_size <= *off_size,
"{wasm} [{vname}] {name}: GREW under elision ({off_size} -> {on_size} B) \
— the pass is removal/rewrite-only, growth is a leak"
);
}
assert!(
on_bytes.len() <= off_bytes.len(),
"{wasm} [{vname}]: .text grew under elision"
);
}
}
for &(vname, reloc) in VARIANTS {
let (off_bytes, _) = compile("gust_mix_686.wat", reloc, Some("0"));
let (on_bytes, _) = compile("gust_mix_686.wat", reloc, Some("1"));
assert!(
on_bytes.len() < off_bytes.len(),
"gust_mix [{vname}]: elision must strictly shrink the constant-shift \
function ({} -> {} B)",
off_bytes.len(),
on_bytes.len()
);
}
let (off_bytes, _) = compile("i32_shift_mask_682.wat", true, Some("0"));
let (on_bytes, _) = compile("i32_shift_mask_682.wat", true, Some("1"));
assert!(
on_bytes.len() < off_bytes.len(),
"i32_shift_mask_682 [relocatable]: const >= 32 amounts must now fold mod 32 \
({} -> {} B)",
off_bytes.len(),
on_bytes.len()
);
}