#![feature(pattern)] mod assert_instr; mod big_endian; mod context; mod expression; mod fn_suffix; mod input; mod intrinsic; mod load_store_tests; mod matching; mod predicate_forms; mod typekinds; mod wildcards; mod wildstring; use intrinsic::Test; use itertools::Itertools; use quote::quote; use std::fs::File; use std::io::Write; use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; use walkdir::WalkDir; fn main() -> Result<(), String> { parse_args() .into_iter() .map(|(filepath, out)| { File::open(&filepath) .map(|f| (f, filepath, out)) .map_err(|e| format!("could not read input file: {e}")) }) .map(|res| { let (file, filepath, out) = res?; serde_yaml::from_reader(file) .map(|input: input::GeneratorInput| (input, filepath, out)) .map_err(|e| format!("could not parse input file: {e}")) }) .collect::, _>>()? .into_iter() .map(|(input, filepath, out)| { let intrinsics = input.intrinsics.into_iter() .map(|intrinsic| { intrinsic.generate_variants(&input.ctx) }) .try_collect() .map(|mut vv: Vec<_>| { vv.sort_by_cached_key(|variants| { variants.first().map_or_else(String::default, |variant| { variant.signature.fn_name().to_string() }) }); vv.into_iter().flatten().collect_vec() })?; if filepath.ends_with("sve.spec.yml") || filepath.ends_with("sve2.spec.yml") { let loads = intrinsics.iter() .filter_map(|i| { if matches!(i.test, Test::Load(..)) { Some(i.clone()) } else { None } }).collect(); let stores = intrinsics.iter() .filter_map(|i| { if matches!(i.test, Test::Store(..)) { Some(i.clone()) } else { None } }).collect(); load_store_tests::generate_load_store_tests(loads, stores, out.as_ref().map(|o| make_tests_filepath(&filepath, o)).as_ref())?; } Ok(( input::GeneratorInput { intrinsics, ctx: input.ctx, }, filepath, out, )) }) .try_for_each( |result: context::Result<(input::GeneratorInput, PathBuf, Option)>| -> context::Result { let (generated, filepath, out) = result?; let w = match out { Some(out) => Box::new( File::create(make_output_filepath(&filepath, &out)) .map_err(|e| format!("could not create output file: {e}"))?, ) as Box, None => Box::new(std::io::stdout()) as Box, }; generate_file(generated, w) .map_err(|e| format!("could not generate output file: {e}")) }, ) } fn parse_args() -> Vec<(PathBuf, Option)> { let mut args_it = std::env::args().skip(1); assert!( 1 <= args_it.len() && args_it.len() <= 2, "Usage: cargo run -p stdarch-gen-arm -- INPUT_DIR [OUTPUT_DIR]\n\ where:\n\ - INPUT_DIR contains a tree like: INPUT_DIR//.spec.yml\n\ - OUTPUT_DIR is a directory like: crates/core_arch/src/" ); let in_path = Path::new(args_it.next().unwrap().as_str()).to_path_buf(); assert!( in_path.exists() && in_path.is_dir(), "invalid path {in_path:#?} given" ); let out_dir = if let Some(dir) = args_it.next() { let out_path = Path::new(dir.as_str()).to_path_buf(); assert!( out_path.exists() && out_path.is_dir(), "invalid path {out_path:#?} given" ); Some(out_path) } else { std::env::current_exe() .map(|mut f| { f.pop(); f.push("../../crates/core_arch/src/"); f.exists().then_some(f) }) .ok() .flatten() }; WalkDir::new(in_path) .into_iter() .filter_map(Result::ok) .filter(|f| f.file_type().is_file()) .map(|f| (f.into_path(), out_dir.clone())) .collect() } fn generate_file( generated_input: input::GeneratorInput, mut out: Box, ) -> std::io::Result<()> { write!( out, r#"// This code is automatically generated. DO NOT MODIFY. // // Instead, modify `crates/stdarch-gen-arm/spec/` and run the following command to re-generate this file: // // ``` // cargo run --bin=stdarch-gen-arm -- crates/stdarch-gen-arm/spec // ``` #![allow(improper_ctypes)] #[cfg(test)] use stdarch_test::assert_instr; use super::*;{uses_neon} "#, uses_neon = if generated_input.ctx.uses_neon_types { "\nuse crate::core_arch::arch::aarch64::*;" } else { "" }, )?; let intrinsics = generated_input.intrinsics; format_code(out, quote! { #(#intrinsics)* })?; Ok(()) } pub fn format_code( mut output: impl std::io::Write, input: impl std::fmt::Display, ) -> std::io::Result<()> { let proc = Command::new("rustfmt") .stdin(Stdio::piped()) .stdout(Stdio::piped()) .spawn()?; write!(proc.stdin.as_ref().unwrap(), "{input}")?; output.write_all(proc.wait_with_output()?.stdout.as_slice()) } /// Derive an output file path from an input file path and an output directory. /// /// `in_filepath` is expected to have a structure like: /// ...//.spec.yml /// /// The resulting output path will have a structure like: /// ///generated.rs /// /// Panics if the resulting name is empty, or if file_name() is not UTF-8. fn make_output_filepath(in_filepath: &Path, out_dirpath: &Path) -> PathBuf { make_filepath(in_filepath, out_dirpath, |_name: &str| { "generated.rs".to_owned() }) } fn make_tests_filepath(in_filepath: &Path, out_dirpath: &Path) -> PathBuf { make_filepath(in_filepath, out_dirpath, |name: &str| { format!("ld_st_tests_{name}.rs") }) } fn make_filepath String>( in_filepath: &Path, out_dirpath: &Path, name_formatter: F, ) -> PathBuf { let mut parts = in_filepath.components().rev().map(|f| { f.as_os_str() .to_str() .expect("Inputs must have valid, UTF-8 file_name()") }); let yml = parts.next().expect("Not enough input path elements."); let feature = parts.next().expect("Not enough input path elements."); let arch = yml .strip_suffix(".yml") .expect("Expected .yml file input.") .strip_suffix(".spec") .expect("Expected .spec.yml file input."); if arch.is_empty() { panic!("Extended ARCH.spec.yml file input."); } let mut output = out_dirpath.to_path_buf(); output.push(arch); output.push(feature); output.push(name_formatter(arch)); output } #[cfg(test)] mod tests { use super::*; #[test] fn infer_output_file() { macro_rules! t { ($src:expr, $outdir:expr, $dst:expr, $ldst:expr) => { let src: PathBuf = $src.iter().collect(); let outdir: PathBuf = $outdir.iter().collect(); let dst: PathBuf = $dst.iter().collect(); let ldst: PathBuf = $ldst.iter().collect(); assert_eq!(make_output_filepath(&src, &outdir), dst); assert_eq!(make_tests_filepath(&src, &outdir), ldst); }; } // Documented usage. t!( ["FEAT", "ARCH.spec.yml"], [""], ["ARCH", "FEAT", "generated.rs"], ["ARCH", "FEAT", "ld_st_tests_ARCH.rs"] ); t!( ["x", "y", "FEAT", "ARCH.spec.yml"], ["out"], ["out", "ARCH", "FEAT", "generated.rs"], ["out", "ARCH", "FEAT", "ld_st_tests_ARCH.rs"] ); t!( ["p", "q", "FEAT", "ARCH.spec.yml"], ["a", "b"], ["a", "b", "ARCH", "FEAT", "generated.rs"], ["a", "b", "ARCH", "FEAT", "ld_st_tests_ARCH.rs"] ); // Extra extensions get treated as part of the stem. t!( ["FEAT", "ARCH.variant.spec.yml"], ["out"], ["out", "ARCH.variant", "FEAT", "generated.rs"], ["out", "ARCH.variant", "FEAT", "ld_st_tests_ARCH.variant.rs"] ); } #[test] #[should_panic] fn infer_output_file_no_stem() { let src = PathBuf::from("FEAT/.spec.yml"); make_output_filepath(&src, Path::new("")); } #[test] #[should_panic] fn infer_output_file_no_feat() { let src = PathBuf::from("ARCH.spec.yml"); make_output_filepath(&src, Path::new("")); } #[test] #[should_panic] fn infer_output_file_ldst_no_stem() { let src = PathBuf::from("FEAT/.spec.yml"); make_tests_filepath(&src, Path::new("")); } #[test] #[should_panic] fn infer_output_file_ldst_no_feat() { let src = PathBuf::from("ARCH.spec.yml"); make_tests_filepath(&src, Path::new("")); } }