diff --git a/Cargo.lock b/Cargo.lock index a1c31fa9f0..d4a4a279ec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -268,6 +268,22 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "fastrand" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -445,6 +461,12 @@ version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + [[package]] name = "log" version = "0.4.29" @@ -457,6 +479,12 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + [[package]] name = "once_cell_polyfill" version = "1.70.2" @@ -648,6 +676,19 @@ version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + [[package]] name = "ryu" version = "1.0.23" @@ -787,11 +828,19 @@ dependencies = [ "walkdir", ] +[[package]] +name = "stdarch-gen-common" +version = "0.1.0" +dependencies = [ + "tempfile", +] + [[package]] name = "stdarch-gen-hexagon" version = "0.1.0" dependencies = [ "regex", + "stdarch-gen-common", ] [[package]] @@ -864,6 +913,19 @@ version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43d0e35dc7d73976a53c7e6d7d177ef804a0c0ee774ec77bcc520c2216fd7cbe" +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.2", + "once_cell", + "rustix", + "windows-sys", +] + [[package]] name = "termcolor" version = "1.4.1" diff --git a/crates/stdarch-gen-common/Cargo.toml b/crates/stdarch-gen-common/Cargo.toml new file mode 100644 index 0000000000..691be14971 --- /dev/null +++ b/crates/stdarch-gen-common/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "stdarch-gen-common" +version = "0.1.0" +edition = "2024" + +[dependencies] +tempfile = "3" \ No newline at end of file diff --git a/crates/stdarch-gen-common/src/lib.rs b/crates/stdarch-gen-common/src/lib.rs new file mode 100644 index 0000000000..8125282fb1 --- /dev/null +++ b/crates/stdarch-gen-common/src/lib.rs @@ -0,0 +1,309 @@ +//! Shared check/bless harness for stdarch generators. + +use std::error::Error as StdError; +use std::fmt; +use std::fs; +use std::io; +use std::path::{Path, PathBuf}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Mode { + Write, + Check, + Bless, +} + +impl Mode { + pub fn from_env() -> Self { + match std::env::var("STDARCH_GEN_MODE").as_deref() { + Ok("check") => Mode::Check, + Ok("bless") => Mode::Bless, + _ => Mode::Write, + } + } +} + +#[derive(Debug)] +pub enum Error { + Io(io::Error), + Mismatch { path: PathBuf, kind: MismatchKind }, + Generator(Box), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum MismatchKind { + MissingInCommitted, + ExtraInCommitted, + ContentsDiffer, + MissingFromGenerated, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Error::Io(e) => write!(f, "I/O error: {e}"), + Error::Mismatch { path, kind } => match kind { + MismatchKind::MissingInCommitted => { + write!(f, "{}: generated but not committed", path.display()) + } + MismatchKind::ExtraInCommitted => { + write!(f, "{}: committed but no longer generated", path.display()) + } + MismatchKind::ContentsDiffer => write!(f, "{}: contents differ", path.display()), + MismatchKind::MissingFromGenerated => write!( + f, + "{}: declared owned but generator did not produce it", + path.display() + ), + }, + Error::Generator(e) => write!(f, "generator failed: {e}"), + } + } +} + +impl StdError for Error { + fn source(&self) -> Option<&(dyn StdError + 'static)> { + match self { + Error::Io(e) => Some(e), + Error::Generator(e) => Some(&**e), + _ => None, + } + } +} + +impl From for Error { + fn from(e: io::Error) -> Self { + Error::Io(e) + } +} + +pub type Result = std::result::Result; + +// owned is the list of relative paths the generator manages. Files in +// committed not listed here are left untouched by Bless and ignored by Check. +pub fn run(committed: &Path, owned: &[&str], mode: Mode, generate: F) -> Result<()> +where + F: FnOnce(&Path) -> std::result::Result<(), E>, + E: Into>, +{ + match mode { + Mode::Write => { + fs::create_dir_all(committed)?; + generate(committed).map_err(|e| Error::Generator(e.into())) + } + Mode::Check => { + let scratch = tempfile::tempdir()?; + generate(scratch.path()).map_err(|e| Error::Generator(e.into()))?; + compare(scratch.path(), committed, owned) + } + Mode::Bless => { + let scratch = tempfile::tempdir()?; + generate(scratch.path()).map_err(|e| Error::Generator(e.into()))?; + apply_bless(scratch.path(), committed, owned) + } + } +} + +fn compare(generated: &Path, committed: &Path, owned: &[&str]) -> Result<()> { + for rel in owned { + let rel_path = PathBuf::from(rel); + let gen_path = generated.join(&rel_path); + let comm_path = committed.join(&rel_path); + match (gen_path.exists(), comm_path.exists()) { + (true, false) => { + return Err(Error::Mismatch { + path: rel_path, + kind: MismatchKind::MissingInCommitted, + }); + } + (false, true) => { + return Err(Error::Mismatch { + path: rel_path, + kind: MismatchKind::ExtraInCommitted, + }); + } + (false, false) => continue, + (true, true) => { + let g = fs::read(&gen_path)?; + let c = fs::read(&comm_path)?; + if g != c { + return Err(Error::Mismatch { + path: rel_path, + kind: MismatchKind::ContentsDiffer, + }); + } + } + } + } + Ok(()) +} + +fn apply_bless(scratch: &Path, committed: &Path, owned: &[&str]) -> Result<()> { + fs::create_dir_all(committed)?; + for rel in owned { + let rel_path = PathBuf::from(rel); + let from = scratch.join(&rel_path); + if !from.exists() { + return Err(Error::Mismatch { + path: rel_path, + kind: MismatchKind::MissingFromGenerated, + }); + } + let to = committed.join(&rel_path); + if let Some(parent) = to.parent() { + fs::create_dir_all(parent)?; + } + fs::copy(&from, &to)?; + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn write(p: &Path, b: &[u8]) { + if let Some(d) = p.parent() { + fs::create_dir_all(d).unwrap(); + } + fs::write(p, b).unwrap(); + } + + #[test] + fn write_mode_creates_committed() { + let tmp = tempfile::tempdir().unwrap(); + let committed = tmp.path().join("c"); + run( + &committed, + &["a.txt"], + Mode::Write, + |out| -> std::result::Result<(), io::Error> { + write(&out.join("a.txt"), b"hi"); + Ok(()) + }, + ) + .unwrap(); + assert_eq!(fs::read(committed.join("a.txt")).unwrap(), b"hi"); + } + + #[test] + fn check_passes_when_identical() { + let tmp = tempfile::tempdir().unwrap(); + let committed = tmp.path().join("c"); + write(&committed.join("a.txt"), b"hi"); + run( + &committed, + &["a.txt"], + Mode::Check, + |out| -> std::result::Result<(), io::Error> { + write(&out.join("a.txt"), b"hi"); + Ok(()) + }, + ) + .unwrap(); + } + + #[test] + fn check_fails_on_byte_diff() { + let tmp = tempfile::tempdir().unwrap(); + let committed = tmp.path().join("c"); + write(&committed.join("a.txt"), b"hi"); + let e = run( + &committed, + &["a.txt"], + Mode::Check, + |out| -> std::result::Result<(), io::Error> { + write(&out.join("a.txt"), b"HI"); + Ok(()) + }, + ) + .unwrap_err(); + assert!(matches!( + e, + Error::Mismatch { + kind: MismatchKind::ContentsDiffer, + .. + } + )); + } + + #[test] + fn check_ignores_unowned_committed_files() { + let tmp = tempfile::tempdir().unwrap(); + let committed = tmp.path().join("c"); + write(&committed.join("mod.rs"), b"hand-written"); + write(&committed.join("a.txt"), b"hi"); + run( + &committed, + &["a.txt"], + Mode::Check, + |out| -> std::result::Result<(), io::Error> { + write(&out.join("a.txt"), b"hi"); + Ok(()) + }, + ) + .unwrap(); + } + + #[test] + fn check_fails_when_owned_file_missing_from_generated() { + let tmp = tempfile::tempdir().unwrap(); + let committed = tmp.path().join("c"); + write(&committed.join("a.txt"), b"hi"); + let e = run( + &committed, + &["a.txt"], + Mode::Check, + |_| -> std::result::Result<(), io::Error> { Ok(()) }, + ) + .unwrap_err(); + assert!(matches!( + e, + Error::Mismatch { + kind: MismatchKind::ExtraInCommitted, + .. + } + )); + } + + #[test] + fn bless_preserves_unowned_files() { + let tmp = tempfile::tempdir().unwrap(); + let committed = tmp.path().join("c"); + write(&committed.join("mod.rs"), b"hand-written"); + write(&committed.join("old.txt"), b"old"); + run( + &committed, + &["new.txt"], + Mode::Bless, + |out| -> std::result::Result<(), io::Error> { + write(&out.join("new.txt"), b"new"); + Ok(()) + }, + ) + .unwrap(); + assert_eq!(fs::read(committed.join("mod.rs")).unwrap(), b"hand-written"); + assert_eq!(fs::read(committed.join("old.txt")).unwrap(), b"old"); + assert_eq!(fs::read(committed.join("new.txt")).unwrap(), b"new"); + } + + #[test] + fn bless_fails_when_generator_drops_owned_file() { + let tmp = tempfile::tempdir().unwrap(); + let committed = tmp.path().join("c"); + let e = run( + &committed, + &["a.txt"], + Mode::Bless, + |_| -> std::result::Result<(), io::Error> { Ok(()) }, + ) + .unwrap_err(); + assert!(matches!( + e, + Error::Mismatch { + kind: MismatchKind::MissingFromGenerated, + .. + } + )); + } +} diff --git a/crates/stdarch-gen-hexagon/Cargo.toml b/crates/stdarch-gen-hexagon/Cargo.toml index 397c7816f8..c7dfce2c0f 100644 --- a/crates/stdarch-gen-hexagon/Cargo.toml +++ b/crates/stdarch-gen-hexagon/Cargo.toml @@ -7,3 +7,4 @@ edition = "2021" [dependencies] regex = "1.10" +stdarch-gen-common = { path = "../stdarch-gen-common" } diff --git a/crates/stdarch-gen-hexagon/src/main.rs b/crates/stdarch-gen-hexagon/src/main.rs index 7a1c3030c0..a40e934482 100644 --- a/crates/stdarch-gen-hexagon/src/main.rs +++ b/crates/stdarch-gen-hexagon/src/main.rs @@ -21,6 +21,7 @@ use std::collections::{HashMap, HashSet}; use std::fs::File; use std::io::Write; use std::path::Path; +use stdarch_gen_common::{run, Mode}; /// Mappings from HVX intrinsics to architecture-independent SIMD intrinsics. /// These intrinsics have equivalent semantics and can be lowered to the generic form. @@ -1695,19 +1696,25 @@ fn main() -> Result<(), String> { .nth(1) .map(std::path::PathBuf::from) .unwrap_or_else(|| crate_dir.join("../core_arch/src/hexagon")); - std::fs::create_dir_all(&hexagon_dir).map_err(|e| e.to_string())?; - - // Generate v64.rs (64-byte vector mode) - let v64_path = hexagon_dir.join("v64.rs"); - println!("\nStep 3: Generating v64.rs (64-byte mode)..."); - generate_module_file(&intrinsics, &v64_path, VectorMode::V64)?; - println!(" Output: {}", v64_path.display()); - - // Generate v128.rs (128-byte vector mode) - let v128_path = hexagon_dir.join("v128.rs"); - println!("\nStep 4: Generating v128.rs (128-byte mode)..."); - generate_module_file(&intrinsics, &v128_path, VectorMode::V128)?; - println!(" Output: {}", v128_path.display()); + + let mode = Mode::from_env(); + println!("\nStep 3: Generating v64.rs and v128.rs (mode: {mode:?})..."); + run( + &hexagon_dir, + &["v64.rs", "v128.rs"], + mode, + |out_dir| -> Result<(), String> { + let v64_path = out_dir.join("v64.rs"); + generate_module_file(&intrinsics, &v64_path, VectorMode::V64)?; + println!(" Output: {}", v64_path.display()); + + let v128_path = out_dir.join("v128.rs"); + generate_module_file(&intrinsics, &v128_path, VectorMode::V128)?; + println!(" Output: {}", v128_path.display()); + Ok(()) + }, + ) + .map_err(|e| e.to_string())?; println!("\n=== Results ==="); println!(