diff --git a/crates/fayalite/examples/blinky.rs b/crates/fayalite/examples/blinky.rs index 40b22dc..8682a33 100644 --- a/crates/fayalite/examples/blinky.rs +++ b/crates/fayalite/examples/blinky.rs @@ -44,7 +44,7 @@ struct ExtraArgs { impl ToArgs for ExtraArgs { fn to_args(&self, args: &mut (impl WriteArgs + ?Sized)) { let Self { clock_frequency } = self; - args.write_arg(format_args!("--clock-frequency={clock_frequency}")); + args.write_arg(format!("--clock-frequency={clock_frequency}")); } } diff --git a/crates/fayalite/src/build.rs b/crates/fayalite/src/build.rs index 96492cf..354d3b2 100644 --- a/crates/fayalite/src/build.rs +++ b/crates/fayalite/src/build.rs @@ -4,9 +4,9 @@ use crate::{ build::graph::JobGraph, bundle::{Bundle, BundleType}, - intern::{Intern, Interned}, + intern::{Intern, InternSlice, Interned}, module::Module, - util::job_server::AcquiredJob, + util::{job_server::AcquiredJob, os_str_strip_prefix}, }; use clap::ArgAction; use serde::{ @@ -18,9 +18,11 @@ use std::{ any::{Any, TypeId}, borrow::Cow, cmp::Ordering, - ffi::OsStr, + ffi::{OsStr, OsString}, fmt, hash::{Hash, Hasher}, + io::Write, + marker::PhantomData, path::{Path, PathBuf}, sync::{Arc, OnceLock}, }; @@ -46,46 +48,14 @@ pub(crate) fn built_in_job_kinds() -> impl IntoIterator { .chain(verilog::built_in_job_kinds()) } -#[track_caller] -fn intern_known_utf8_path_buf(v: PathBuf) -> Interned { - let Ok(v) = v.into_os_string().into_string().map(str::intern_owned) else { - unreachable!("known to be valid UTF-8"); - }; - v -} - -#[track_caller] -fn intern_known_utf8_str(v: impl AsRef) -> Interned { - let Some(v) = v.as_ref().to_str().map(str::intern) else { - unreachable!("known to be valid UTF-8"); - }; - v -} - -#[track_caller] -fn interned_known_utf8_method &OsStr>( - v: impl AsRef, - f: F, -) -> Interned { - intern_known_utf8_str(f(v.as_ref().as_ref())) -} - -#[track_caller] -fn interned_known_utf8_path_buf_method PathBuf>( - v: impl AsRef, - f: F, -) -> Interned { - intern_known_utf8_path_buf(f(v.as_ref().as_ref())) -} - #[derive(Clone, Hash, PartialEq, Eq, Debug)] #[non_exhaustive] pub enum JobItem { Path { - path: Interned, + path: Interned, }, DynamicPaths { - paths: Vec>, + paths: Vec>, source_job_name: Interned, }, } @@ -105,7 +75,7 @@ impl JobItem { #[derive(Copy, Clone, Hash, PartialEq, Eq, Debug, Serialize, Deserialize)] #[non_exhaustive] pub enum JobItemName { - Path { path: Interned }, + Path { path: Interned }, DynamicPaths { source_job_name: Interned }, } @@ -122,7 +92,7 @@ impl JobItemName { #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] enum JobItemNameRef<'a> { - Path { path: &'a str }, + Path { path: &'a Path }, DynamicPaths { source_job_name: &'a str }, } @@ -146,119 +116,191 @@ impl Ord for JobItemName { pub trait WriteArgs: for<'a> Extend<&'a str> + + for<'a> Extend<&'a OsStr> + + for<'a> Extend<&'a Path> + + for<'a> Extend> + + for<'a> Extend> + + for<'a> Extend> + Extend + + Extend + + Extend + Extend> - + for<'a> Extend> + + Extend> + + Extend> { - fn write_args(&mut self, args: impl IntoIterator) { - self.extend(args.into_iter().map(|v| format!("{v}"))); + fn write_display_args(&mut self, args: impl IntoIterator) { + self.extend(args.into_iter().map(|v| v.to_string())); } - fn write_string_args(&mut self, args: impl IntoIterator) { - self.extend(args); + fn write_owned_args(&mut self, args: impl IntoIterator>) { + self.extend(args.into_iter().map(Into::::into)) } - fn write_str_args<'a>(&mut self, args: impl IntoIterator) { - self.extend(args); + fn write_args<'a>(&mut self, args: impl IntoIterator>); + fn write_interned_args(&mut self, args: impl IntoIterator>>) { + self.extend(args.into_iter().map(Into::>::into)) } - fn write_interned_args(&mut self, args: impl IntoIterator>) { - self.extend(args); + fn write_display_arg(&mut self, arg: impl fmt::Display) { + self.write_display_args([arg]); } - fn write_arg(&mut self, arg: impl fmt::Display) { - self.extend([format_args!("{arg}")]); + fn write_owned_arg(&mut self, arg: impl Into) { + self.extend([arg.into()]); } - fn write_string_arg(&mut self, arg: String) { - self.extend([arg]); + fn write_arg(&mut self, arg: impl AsRef) { + self.extend([arg.as_ref()]); } - fn write_str_arg(&mut self, arg: &str) { - self.extend([arg]); + /// writes `--{name}={value}` + fn write_long_option_eq(&mut self, name: impl AsRef, value: impl AsRef) { + let name = name.as_ref(); + let value = value.as_ref(); + let mut option = + OsString::with_capacity(name.len().saturating_add(value.len()).saturating_add(3)); + option.push("--"); + option.push(name); + option.push("="); + option.push(value); + self.write_owned_arg(option); } - fn write_interned_arg(&mut self, arg: Interned) { - self.extend([arg]); + fn write_interned_arg(&mut self, arg: impl Into>) { + self.extend([arg.into()]); } /// finds the first option that is `--{option_name}={value}` and returns `value` - fn get_long_option_eq(&self, option_name: impl AsRef) -> Option<&str>; + fn get_long_option_eq(&self, option_name: impl AsRef) -> Option<&OsStr>; } -pub struct ArgsWriter(pub Vec); +pub trait ArgsWriterArg: + AsRef + + From> + + for<'a> From> + + for<'a> From<&'a OsStr> + + From +{ +} -impl Default for ArgsWriter { +impl ArgsWriterArg for Interned {} + +impl ArgsWriterArg for OsString {} + +pub struct ArgsWriter(pub Vec); + +impl Default for ArgsWriter { fn default() -> Self { Self(Default::default()) } } -impl> ArgsWriter { - fn get_long_option_eq_helper(&self, option_name: &str) -> Option<&str> { +impl ArgsWriter { + fn get_long_option_eq_helper(&self, option_name: &str) -> Option<&OsStr> { self.0.iter().find_map(|arg| { - arg.as_ref() - .strip_prefix("--") - .and_then(|arg| arg.strip_prefix(option_name)) - .and_then(|arg| arg.strip_prefix("=")) + os_str_strip_prefix(arg.as_ref(), "--") + .and_then(|arg| os_str_strip_prefix(arg, option_name)) + .and_then(|arg| os_str_strip_prefix(arg, "=")) }) } } -impl<'a, W> Extend> for ArgsWriter -where - Self: Extend, -{ - fn extend>>(&mut self, iter: T) { - self.extend(iter.into_iter().map(|v| v.to_string())) - } -} - -impl<'a> Extend<&'a str> for ArgsWriter> { +impl<'a, A: ArgsWriterArg> Extend<&'a str> for ArgsWriter { fn extend>(&mut self, iter: T) { - self.extend(iter.into_iter().map(str::intern)) + self.extend(iter.into_iter().map(AsRef::::as_ref)) } } -impl Extend for ArgsWriter> { +impl<'a, A: ArgsWriterArg> Extend<&'a OsStr> for ArgsWriter { + fn extend>(&mut self, iter: T) { + self.0.extend(iter.into_iter().map(Into::into)) + } +} + +impl<'a, A: ArgsWriterArg> Extend<&'a Path> for ArgsWriter { + fn extend>(&mut self, iter: T) { + self.extend(iter.into_iter().map(AsRef::::as_ref)) + } +} + +impl Extend for ArgsWriter { fn extend>(&mut self, iter: T) { - self.extend(iter.into_iter().map(str::intern_owned)) + self.extend(iter.into_iter().map(OsString::from)) } } -impl Extend> for ArgsWriter { +impl Extend for ArgsWriter { + fn extend>(&mut self, iter: T) { + self.0.extend(iter.into_iter().map(Into::into)) + } +} + +impl Extend for ArgsWriter { + fn extend>(&mut self, iter: T) { + self.extend(iter.into_iter().map(OsString::from)) + } +} + +impl Extend> for ArgsWriter { fn extend>>(&mut self, iter: T) { - self.extend(iter.into_iter().map(String::from)) + self.extend(iter.into_iter().map(Interned::::from)) } } -impl<'a> Extend<&'a str> for ArgsWriter { - fn extend>(&mut self, iter: T) { - self.extend(iter.into_iter().map(String::from)) +impl Extend> for ArgsWriter { + fn extend>>(&mut self, iter: T) { + self.0.extend(iter.into_iter().map(Into::into)) } } -impl Extend for ArgsWriter { - fn extend>(&mut self, iter: T) { - self.0.extend(iter); +impl Extend> for ArgsWriter { + fn extend>>(&mut self, iter: T) { + self.extend(iter.into_iter().map(Interned::::from)) } } -impl WriteArgs for ArgsWriter { - fn get_long_option_eq(&self, option_name: impl AsRef) -> Option<&str> { - self.get_long_option_eq_helper(option_name.as_ref()) +impl<'a, A: ArgsWriterArg> Extend> for ArgsWriter { + fn extend>>(&mut self, iter: T) { + self.0.extend(iter.into_iter().map(|v| { + match v { + Cow::Borrowed(v) => Cow::::Borrowed(v.as_ref()), + Cow::Owned(v) => Cow::Owned(v.into()), + } + .into() + })) } } -impl WriteArgs for ArgsWriter> { - fn get_long_option_eq(&self, option_name: impl AsRef) -> Option<&str> { +impl<'a, A: ArgsWriterArg> Extend> for ArgsWriter { + fn extend>>(&mut self, iter: T) { + self.0.extend(iter.into_iter().map(Into::into)) + } +} + +impl<'a, A: ArgsWriterArg> Extend> for ArgsWriter { + fn extend>>(&mut self, iter: T) { + self.0.extend(iter.into_iter().map(|v| { + match v { + Cow::Borrowed(v) => Cow::::Borrowed(v.as_ref()), + Cow::Owned(v) => Cow::Owned(v.into()), + } + .into() + })) + } +} + +impl WriteArgs for ArgsWriter { + fn write_args<'a>(&mut self, args: impl IntoIterator>) { + self.0.extend(args.into_iter().map(|v| v.as_ref().into())) + } + fn get_long_option_eq(&self, option_name: impl AsRef) -> Option<&OsStr> { self.get_long_option_eq_helper(option_name.as_ref()) } } pub trait ToArgs: clap::Args + 'static + Send + Sync + Hash + Eq + fmt::Debug + Clone { fn to_args(&self, args: &mut (impl WriteArgs + ?Sized)); - fn to_interned_args(&self) -> Interned<[Interned]> { + fn to_interned_args(&self) -> Interned<[Interned]> { Intern::intern_owned(self.to_interned_args_vec()) } - fn to_interned_args_vec(&self) -> Vec> { + fn to_interned_args_vec(&self) -> Vec> { let mut retval = ArgsWriter::default(); self.to_args(&mut retval); retval.0 } - fn to_string_args(&self) -> Vec { + fn to_os_string_args(&self) -> Vec { let mut retval = ArgsWriter::default(); self.to_args(&mut retval); retval.0 @@ -361,6 +403,15 @@ pub struct JobAndDependencies { pub dependencies: ::JobsAndKinds, } +impl JobAndDependencies { + pub fn get_job(&self) -> &J + where + Self: GetJob, + { + GetJob::get_job(self) + } +} + impl Clone for JobAndDependencies where K::Job: Clone, @@ -608,41 +659,44 @@ impl JobParams { #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] pub struct CommandParams { - pub command_line: Interned<[Interned]>, - pub current_dir: Option>, + pub command_line: Interned<[Interned]>, + pub current_dir: Option>, } impl CommandParams { - fn to_unix_shell_line( + fn to_unix_shell_line( self, - output: &mut W, - mut escape_arg: impl FnMut(&str, &mut W) -> fmt::Result, - ) -> fmt::Result { + output: &mut String, + mut escape_arg: impl FnMut(&OsStr, &mut String) -> Result<(), E>, + ) -> Result<(), E> { let Self { command_line, current_dir, } = self; let mut end = None; let mut separator = if let Some(current_dir) = current_dir { - output.write_str("(cd ")?; + output.push_str("(cd "); end = Some(")"); - if !current_dir.starts_with(|ch: char| { - ch.is_ascii_alphanumeric() || matches!(ch, '/' | '\\' | '.') - }) { - output.write_str("-- ")?; + if !current_dir + .as_os_str() + .as_encoded_bytes() + .first() + .is_some_and(|ch| ch.is_ascii_alphanumeric() || matches!(ch, b'/' | b'\\' | b'.')) + { + output.push_str("-- "); } - escape_arg(¤t_dir, output)?; + escape_arg(current_dir.as_ref(), output)?; "; exec -- " } else { "" }; for arg in command_line { - output.write_str(separator)?; + output.push_str(separator); separator = " "; escape_arg(&arg, output)?; } if let Some(end) = end { - output.write_str(end)?; + output.push_str(end); } Ok(()) } @@ -999,7 +1053,7 @@ trait DynJobArgsTrait: 'static + Send + Sync + fmt::Debug { fn eq_dyn(&self, other: &dyn DynJobArgsTrait) -> bool; fn hash_dyn(&self, state: &mut dyn Hasher); fn kind(&self) -> DynJobKind; - fn to_args_extend_vec(&self, args: Vec>) -> Vec>; + fn to_args_extend_vec(&self, args: Vec>) -> Vec>; fn clone_into_arc(&self) -> Arc; fn update_from_arg_matches( &mut self, @@ -1041,7 +1095,7 @@ impl DynJobArgsTrait for DynJobArgsInner { DynJobKind::new(self.0.kind) } - fn to_args_extend_vec(&self, args: Vec>) -> Vec> { + fn to_args_extend_vec(&self, args: Vec>) -> Vec> { let mut writer = ArgsWriter(args); self.0.args.to_args(&mut writer); writer.0 @@ -1101,10 +1155,10 @@ impl DynJobArgs { pub fn kind(&self) -> DynJobKind { DynJobArgsTrait::kind(&*self.0) } - pub fn to_args_vec(&self) -> Vec> { + pub fn to_args_vec(&self) -> Vec> { self.to_args_extend_vec(Vec::new()) } - pub fn to_args_extend_vec(&self, args: Vec>) -> Vec> { + pub fn to_args_extend_vec(&self, args: Vec>) -> Vec> { DynJobArgsTrait::to_args_extend_vec(&*self.0, args) } fn make_mut(&mut self) -> &mut dyn DynJobArgsTrait { @@ -1329,8 +1383,8 @@ impl DynJob { #[track_caller] pub fn internal_command_params_with_program_prefix( &self, - internal_program_prefix: &[Interned], - extra_args: &[Interned], + internal_program_prefix: &[Interned], + extra_args: &[Interned], ) -> CommandParams { let mut command_line = internal_program_prefix.to_vec(); let command_line = match RunSingleJob::try_add_subcommand(self, &mut command_line) { @@ -1346,7 +1400,7 @@ impl DynJob { } } #[track_caller] - pub fn internal_command_params(&self, extra_args: &[Interned]) -> CommandParams { + pub fn internal_command_params(&self, extra_args: &[Interned]) -> CommandParams { self.internal_command_params_with_program_prefix( &[program_name_for_internal_jobs()], extra_args, @@ -1355,8 +1409,8 @@ impl DynJob { #[track_caller] pub fn command_params_with_internal_program_prefix( &self, - internal_program_prefix: &[Interned], - extra_args: &[Interned], + internal_program_prefix: &[Interned], + extra_args: &[Interned], ) -> CommandParams { match self.external_command_params() { Some(v) => v, @@ -1365,7 +1419,7 @@ impl DynJob { } } #[track_caller] - pub fn command_params(&self, extra_args: &[Interned]) -> CommandParams { + pub fn command_params(&self, extra_args: &[Interned]) -> CommandParams { self.command_params_with_internal_program_prefix( &[program_name_for_internal_jobs()], extra_args, @@ -1490,14 +1544,16 @@ impl RunSingleJob { pub const SUBCOMMAND_NAME: &'static str = "run-single-job"; fn try_add_subcommand( job: &DynJob, - subcommand_line: &mut Vec>, + subcommand_line: &mut Vec>, ) -> serde_json::Result<()> { let mut json = job.serialize_to_json_ascii()?; json.insert_str(0, "--json="); subcommand_line.extend([ - Self::SUBCOMMAND_NAME.intern(), - Intern::intern_owned(format!("--name={}", job.kind().name())), - Intern::intern_owned(json), + Interned::::from(Self::SUBCOMMAND_NAME.intern()), + format!("--name={}", job.kind().name()) + .intern_deref() + .into(), + json.intern_deref().into(), ]); Ok(()) } @@ -1623,10 +1679,11 @@ impl RunBuild for Completions { F: FnOnce(NoArgs) -> eyre::Result, { let Self::Completions { shell } = self; - let bin_name = cmd - .get_bin_name() - .map(str::intern) - .unwrap_or_else(|| program_name_for_internal_jobs()); + let bin_name = cmd.get_bin_name().map(str::intern).unwrap_or_else(|| { + program_name_for_internal_jobs() + .to_interned_str() + .expect("program name is invalid UTF-8") + }); clap_complete::aot::generate( shell, &mut cmd, @@ -1667,6 +1724,7 @@ pub enum BuildCli { RunSingleJob(RunSingleJob), #[clap(flatten)] Completions(Completions), + #[cfg(unix)] #[clap(flatten)] CreateUnixShellScript(CreateUnixShellScript), } @@ -1680,11 +1738,13 @@ impl RunBuild for BuildCli { BuildCli::Job(v) => v.run(make_params, cmd), BuildCli::RunSingleJob(v) => v.run(make_params, cmd), BuildCli::Completions(v) => v.run(|NoArgs {}| unreachable!(), cmd), + #[cfg(unix)] BuildCli::CreateUnixShellScript(v) => v.run(make_params, cmd), } } } +#[cfg(unix)] #[derive(Clone, PartialEq, Eq, Hash, Debug, clap::Subcommand)] enum CreateUnixShellScriptInner { CreateUnixShellScript { @@ -1717,16 +1777,17 @@ impl RunBuild for CreateUnixShellScript { let (job, dependencies) = args.args_to_jobs(dependencies_args, ¶ms)?; let mut job_graph = JobGraph::new(); job_graph.add_jobs([job].into_iter().chain(dependencies)); - println!( - "{}", - job_graph.to_unix_shell_script_with_internal_program_prefix( - &[cmd - .get_bin_name() - .map(str::intern) - .unwrap_or_else(|| program_name_for_internal_jobs())], - &extra_args, - ) - ); + std::io::stdout().write_all( + job_graph + .to_unix_shell_script_with_internal_program_prefix( + &[cmd + .get_bin_name() + .map(|v| OsStr::new(v).intern()) + .unwrap_or_else(|| program_name_for_internal_jobs())], + &extra_args, + ) + .as_bytes(), + )?; Ok(()) } } @@ -1749,6 +1810,7 @@ impl clap::FromArgMatches for CreateUnixShellScript { } } +#[cfg(unix)] impl clap::Subcommand for CreateUnixShellScript { fn augment_subcommands(cmd: clap::Command) -> clap::Command { CreateUnixShellScriptInner::::augment_subcommands(cmd) @@ -1912,10 +1974,14 @@ impl RunBuild for AnyJobSubcommand { } } -pub fn program_name_for_internal_jobs() -> Interned { - static PROGRAM_NAME: OnceLock> = OnceLock::new(); - *PROGRAM_NAME - .get_or_init(|| str::intern_owned(std::env::args().next().expect("can't get program name"))) +pub fn program_name_for_internal_jobs() -> Interned { + static PROGRAM_NAME: OnceLock> = OnceLock::new(); + *PROGRAM_NAME.get_or_init(|| { + std::env::args_os() + .next() + .expect("can't get program name") + .intern_deref() + }) } #[derive(clap::Args, PartialEq, Eq, Hash, Debug, Clone)] @@ -1923,7 +1989,7 @@ pub fn program_name_for_internal_jobs() -> Interned { pub struct CreateOutputDirArgs { /// the directory to put the generated main output file and associated files in #[arg(short, long, value_hint = clap::ValueHint::DirPath)] - pub output: Option, + pub output: Option, #[arg(long, env = "FAYALITE_KEEP_TEMP_DIR")] pub keep_temp_dir: bool, } @@ -1935,17 +2001,17 @@ impl ToArgs for CreateOutputDirArgs { keep_temp_dir, } = self; if let Some(output) = output { - args.write_arg(format_args!("--output={output}")); + args.write_long_option_eq("output", output); } if *keep_temp_dir { - args.write_str_arg("--keep-temp-dir"); + args.write_arg("--keep-temp-dir"); } } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CreateOutputDir { - output_dir: Interned, + output_dir: Interned, #[serde(skip)] temp_dir: Option>, } @@ -1998,20 +2064,7 @@ impl JobKind for CreateOutputDirJobKind { // we create the temp dir here rather than in run so other // jobs can have their paths based on the chosen temp dir let temp_dir = TempDir::new()?; - let output_dir = temp_dir - .path() - .as_os_str() - .to_str() - .ok_or_else(|| { - std::io::Error::new( - std::io::ErrorKind::InvalidFilename, - format!( - "temporary directory path is not valid UTF-8: {:?}", - temp_dir.path() - ), - ) - })? - .intern(); + let output_dir = temp_dir.path().intern(); let temp_dir = if keep_temp_dir { // use TempDir::into_path() to no longer automatically delete the temp dir let temp_dir_path = temp_dir.into_path(); @@ -2041,8 +2094,8 @@ impl JobKind for CreateOutputDirJobKind { fn outputs(self, job: &Self::Job) -> Interned<[JobItemName]> { [JobItemName::Path { path: job.output_dir, - }][..] - .intern() + }] + .intern_slice() } fn name(self) -> Interned { @@ -2052,12 +2105,12 @@ impl JobKind for CreateOutputDirJobKind { fn external_command_params(self, job: &Self::Job) -> Option { Some(CommandParams { command_line: [ - "mkdir".intern(), - "-p".intern(), - "--".intern(), - job.output_dir, - ][..] - .intern(), + "mkdir".intern().into(), + "-p".intern().into(), + "--".intern().into(), + job.output_dir.into(), + ] + .intern_slice(), current_dir: None, }) } @@ -2084,10 +2137,10 @@ impl JobKind for CreateOutputDirJobKind { } impl CreateOutputDir { - pub fn output_dir(&self) -> Interned { + pub fn output_dir(&self) -> Interned { self.output_dir } - fn compare_key(&self) -> (&str, bool) { + fn compare_key(&self) -> (&Path, bool) { let Self { output_dir, temp_dir, @@ -2105,7 +2158,7 @@ pub struct BaseJobArgs { pub create_output_dir_args: CreateOutputDirArgs, /// the stem of the generated main output file, e.g. to get foo.v, pass --file-stem=foo #[arg(long)] - pub file_stem: Option, + pub file_stem: Option, /// run commands even if their results are already cached #[arg(long, env = Self::RUN_EVEN_IF_CACHED_ENV_NAME)] pub run_even_if_cached: bool, @@ -2113,7 +2166,7 @@ pub struct BaseJobArgs { impl BaseJobArgs { pub const RUN_EVEN_IF_CACHED_ENV_NAME: &'static str = "FAYALITE_RUN_EVEN_IF_CACHED"; - pub fn from_output_dir_and_env(output: String) -> Self { + pub fn from_output_dir_and_env(output: PathBuf) -> Self { Self { create_output_dir_args: CreateOutputDirArgs { output: Some(output), @@ -2134,10 +2187,10 @@ impl ToArgs for BaseJobArgs { } = self; create_output_dir_args.to_args(args); if let Some(file_stem) = file_stem { - args.write_arg(format_args!("--file-stem={file_stem}")); + args.write_long_option_eq("file-stem", file_stem); } if *run_even_if_cached { - args.write_str_arg("--run-even-if-cached"); + args.write_arg("--run-even-if-cached"); } } } @@ -2147,21 +2200,21 @@ pub struct BaseJob { /// rather than having CreateOutputDir be a normal dependency, it's nested in BaseJob to avoid a cyclic dependency #[serde(flatten)] create_output_dir: CreateOutputDir, - file_stem: Interned, + file_stem: Interned, run_even_if_cached: bool, } impl BaseJob { - pub fn output_dir(&self) -> Interned { + pub fn output_dir(&self) -> Interned { self.create_output_dir.output_dir() } - pub fn file_stem(&self) -> Interned { + pub fn file_stem(&self) -> Interned { self.file_stem } - pub fn file_with_ext(&self, ext: &str) -> Interned { - let mut retval = std::path::Path::new(&self.output_dir()).join(self.file_stem()); + pub fn file_with_ext(&self, ext: impl AsRef) -> Interned { + let mut retval = self.output_dir().join(self.file_stem()); retval.set_extension(ext); - intern_known_utf8_path_buf(retval) + retval.intern_deref() } pub fn run_even_if_cached(&self) -> bool { self.run_even_if_cached @@ -2195,8 +2248,8 @@ impl JobKind for BaseJobKind { }; let create_output_dir = create_output_dir_args.args_to_jobs((), params)?.job.job; let file_stem = file_stem - .map(Intern::intern_owned) - .unwrap_or(params.main_module().name()); + .map(Intern::intern_deref) + .unwrap_or(params.main_module().name().into()); Ok(JobAndDependencies { job: JobAndKind { kind: BaseJobKind, @@ -2241,64 +2294,91 @@ impl JobKind for BaseJobKind { } } -pub trait GetBaseJob { - fn base_job(&self) -> &BaseJob; +pub trait GetJob { + fn get_job(this: &Self) -> &J; } -impl GetBaseJob for &'_ T { - fn base_job(&self) -> &BaseJob { - T::base_job(self) +impl> GetJob for &'_ T { + fn get_job(this: &Self) -> &J { + T::get_job(this) } } -impl GetBaseJob for &'_ mut T { - fn base_job(&self) -> &BaseJob { - T::base_job(self) +impl> GetJob for &'_ mut T { + fn get_job(this: &Self) -> &J { + T::get_job(this) } } -impl GetBaseJob for Box { - fn base_job(&self) -> &BaseJob { - T::base_job(self) +impl> GetJob for Box { + fn get_job(this: &Self) -> &J { + T::get_job(this) } } -impl GetBaseJob for BaseJob { - fn base_job(&self) -> &BaseJob { - self +pub struct GetJobPositionDependencies(PhantomData); + +impl Default for GetJobPositionDependencies { + fn default() -> Self { + Self(Default::default()) } } -impl GetBaseJob for JobAndKind { - fn base_job(&self) -> &BaseJob { - &self.job +impl fmt::Debug for GetJobPositionDependencies { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "GetJobPositionDependencies<{}>", + std::any::type_name::() + ) } } -impl GetBaseJob for JobAndDependencies { - fn base_job(&self) -> &BaseJob { - &self.job.job +impl Hash for GetJobPositionDependencies { + fn hash(&self, _state: &mut H) {} +} + +impl Ord for GetJobPositionDependencies { + fn cmp(&self, _other: &Self) -> Ordering { + Ordering::Equal } } -impl GetBaseJob for JobAndDependencies -where - K::Dependencies: JobDependencies, - ::JobsAndKinds: GetBaseJob, +impl PartialOrd for GetJobPositionDependencies { + fn partial_cmp(&self, _other: &Self) -> Option { + Some(Ordering::Equal) + } +} + +impl Eq for GetJobPositionDependencies {} + +impl PartialEq for GetJobPositionDependencies { + fn eq(&self, _other: &Self) -> bool { + true + } +} + +impl Clone for GetJobPositionDependencies { + fn clone(&self) -> Self { + Self(PhantomData) + } +} + +impl Copy for GetJobPositionDependencies {} + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)] +pub struct GetJobPositionJob; + +impl>>> + GetJob> for JobAndDependencies { - fn base_job(&self) -> &BaseJob { - self.dependencies.base_job() + fn get_job(this: &Self) -> &J { + GetJob::get_job(&this.dependencies) } } -impl GetBaseJob for (T, U) { - fn base_job(&self) -> &BaseJob { - self.0.base_job() - } -} - -impl GetBaseJob for (T, U, V) { - fn base_job(&self) -> &BaseJob { - self.0.base_job() +impl GetJob for JobAndDependencies { + fn get_job(this: &Self) -> &K::Job { + &this.job.job } } diff --git a/crates/fayalite/src/build/external.rs b/crates/fayalite/src/build/external.rs index 021d63d..a6936e5 100644 --- a/crates/fayalite/src/build/external.rs +++ b/crates/fayalite/src/build/external.rs @@ -3,9 +3,9 @@ use crate::{ build::{ - ArgsWriter, CommandParams, GetBaseJob, JobAndDependencies, JobAndKind, + ArgsWriter, BaseJob, CommandParams, GetJob, JobAndDependencies, JobAndKind, JobArgsAndDependencies, JobDependencies, JobItem, JobItemName, JobKind, JobKindAndArgs, - JobParams, ToArgs, WriteArgs, intern_known_utf8_path_buf, + JobParams, ToArgs, WriteArgs, }, intern::{Intern, Interned}, util::{job_server::AcquiredJob, streaming_read_utf8::streaming_read_utf8}, @@ -55,6 +55,25 @@ impl MaybeUtf8 { MaybeUtf8::Binary(v) => v, } } + pub fn as_os_str(&self) -> &OsStr { + #![allow(unreachable_code)] + #[cfg(unix)] + { + return std::os::unix::ffi::OsStrExt::from_bytes(self.as_bytes()); + } + #[cfg(target_os = "wasi")] + { + return std::os::wasi::ffi::OsStrExt::from_bytes(self.as_bytes()); + } + // implementing WTF-8 is too much of a pain so don't have a special case for windows + if let Ok(s) = str::from_utf8(self.as_bytes()) { + return OsStr::new(s); + } + panic!("invalid UTF-8 conversion to OsStr is not implemented on this platform"); + } + pub fn as_path(&self) -> &Path { + Path::new(self.as_os_str()) + } } #[derive(Serialize, Deserialize)] @@ -107,31 +126,80 @@ impl From for MaybeUtf8 { } } +impl From for MaybeUtf8 { + fn from(value: PathBuf) -> Self { + Self::from(value.into_os_string().into_encoded_bytes()) + } +} + +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Serialize, Deserialize)] +#[serde(rename = "File")] +pub struct ExternalJobCacheV2File<'a> { + pub name: MaybeUtf8, + pub contents: Cow<'a, MaybeUtf8>, +} + +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] +pub struct ExternalJobCacheV2Files(pub BTreeMap); + +impl Serialize for ExternalJobCacheV2Files { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.collect_seq( + self.0 + .iter() + .map(|(name, contents)| ExternalJobCacheV2File { + name: name.clone().into(), + contents: Cow::Borrowed(contents), + }), + ) + } +} + +impl<'de> Deserialize<'de> for ExternalJobCacheV2Files { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + Ok(Self( + Vec::deserialize(deserializer)? + .into_iter() + .map(|ExternalJobCacheV2File { name, contents }| { + (name.as_path().to_path_buf(), contents.into_owned()) + }) + .collect(), + )) + } +} + #[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] #[serde(rename = "ExternalJobCache")] pub struct ExternalJobCacheV2 { pub version: ExternalJobCacheVersion, pub inputs_hash: blake3::Hash, pub stdout_stderr: String, - pub result: Result, String>, + pub result: Result, } impl ExternalJobCacheV2 { - fn read_from_file(cache_json_path: Interned) -> eyre::Result { + fn read_from_file(cache_json_path: Interned) -> eyre::Result { let cache_str = std::fs::read_to_string(&*cache_json_path) - .wrap_err_with(|| format!("can't read {cache_json_path}"))?; - serde_json::from_str(&cache_str).wrap_err_with(|| format!("can't decode {cache_json_path}")) + .wrap_err_with(|| format!("can't read {cache_json_path:?}"))?; + serde_json::from_str(&cache_str) + .wrap_err_with(|| format!("can't decode {cache_json_path:?}")) } - fn write_to_file(&self, cache_json_path: Interned) -> eyre::Result<()> { + fn write_to_file(&self, cache_json_path: Interned) -> eyre::Result<()> { let cache_str = serde_json::to_string_pretty(&self).expect("serialization can't fail"); std::fs::write(&*cache_json_path, cache_str) - .wrap_err_with(|| format!("can't write {cache_json_path}")) + .wrap_err_with(|| format!("can't write {cache_json_path:?}")) } } #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] pub struct ExternalJobCaching { - cache_json_path: Interned, + cache_json_path: Interned, run_even_if_cached: bool, } @@ -148,8 +216,8 @@ impl JobCacheHasher { self.hash_size(bytes.len()); self.0.update(bytes); } - fn hash_sized_str(&mut self, s: &str) { - self.hash_sized_bytes(s.as_bytes()); + fn hash_sized_os_str(&mut self, s: &OsStr) { + self.hash_sized_bytes(s.as_encoded_bytes()); } fn hash_iter>( &mut self, @@ -193,8 +261,8 @@ fn write_file_atomically_no_clobber C, C: AsRef<[u8]>>( } impl ExternalJobCaching { - pub fn get_cache_dir_from_output_dir(output_dir: &str) -> PathBuf { - Path::join(output_dir.as_ref(), ".fayalite-job-cache") + pub fn get_cache_dir_from_output_dir(output_dir: impl AsRef) -> PathBuf { + output_dir.as_ref().join(".fayalite-job-cache") } pub fn make_cache_dir( cache_dir: impl AsRef, @@ -218,19 +286,18 @@ impl ExternalJobCaching { }) } pub fn new( - output_dir: &str, + output_dir: impl AsRef, application_name: &str, - json_file_stem: &str, + json_file_stem: impl AsRef, run_even_if_cached: bool, ) -> std::io::Result { let cache_dir = Self::get_cache_dir_from_output_dir(output_dir); Self::make_cache_dir(&cache_dir, application_name)?; let mut cache_json_path = cache_dir; - cache_json_path.push(json_file_stem); + cache_json_path.push(json_file_stem.as_ref()); cache_json_path.set_extension("json"); - let cache_json_path = intern_known_utf8_path_buf(cache_json_path); Ok(Self { - cache_json_path, + cache_json_path: Path::intern_owned(cache_json_path), run_even_if_cached, }) } @@ -249,7 +316,7 @@ impl ExternalJobCaching { fn run_from_cache( self, inputs_hash: blake3::Hash, - output_file_paths: impl IntoIterator>, + output_file_paths: impl IntoIterator>, ) -> Result, ()> { if self.run_even_if_cached { return Err(()); @@ -269,7 +336,7 @@ impl ExternalJobCaching { match result { Ok(outputs) => { for output_file_path in output_file_paths { - let Some(output_data) = outputs.get(&*output_file_path) else { + let Some(output_data) = outputs.0.get(&*output_file_path) else { if let Ok(true) = std::fs::exists(&*output_file_path) { // assume the existing file is the correct one continue; @@ -290,7 +357,7 @@ impl ExternalJobCaching { } } fn make_command( - command_line: Interned<[Interned]>, + command_line: Interned<[Interned]>, ) -> eyre::Result { ensure!(!command_line.is_empty(), "command line must not be empty"); let mut cmd = std::process::Command::new(&*command_line[0]); @@ -300,26 +367,26 @@ impl ExternalJobCaching { } pub fn run eyre::Result<()>>( self, - command_line: Interned<[Interned]>, - input_file_paths: impl IntoIterator>, - output_file_paths: impl IntoIterator> + Clone, + command_line: Interned<[Interned]>, + input_file_paths: impl IntoIterator>, + output_file_paths: impl IntoIterator> + Clone, run_fn: F, ) -> eyre::Result<()> { let mut hasher = JobCacheHasher::default(); hasher.hash_iter(command_line.iter(), |hasher, arg| { - hasher.hash_sized_str(arg) + hasher.hash_sized_os_str(arg) }); let mut input_file_paths = - Vec::<&str>::from_iter(input_file_paths.into_iter().map(Interned::into_inner)); + Vec::<&Path>::from_iter(input_file_paths.into_iter().map(Interned::into_inner)); input_file_paths.sort_unstable(); input_file_paths.dedup(); hasher.try_hash_iter( &input_file_paths, |hasher, input_file_path| -> eyre::Result<()> { - hasher.hash_sized_str(input_file_path); + hasher.hash_sized_os_str(input_file_path.as_ref()); hasher.hash_sized_bytes( &std::fs::read(input_file_path).wrap_err_with(|| { - format!("can't read job input file: {input_file_path}") + format!("can't read job input file: {input_file_path:?}") })?, ); Ok(()) @@ -338,7 +405,7 @@ impl ExternalJobCaching { let mut stdout_stderr = String::new(); let result = std::thread::scope(|scope| { std::thread::Builder::new() - .name(format!("stdout:{}", command_line[0])) + .name(format!("stdout:{}", command_line[0].display())) .spawn_scoped(scope, || { let _ = streaming_read_utf8(std::io::BufReader::new(pipe_reader), |s| { stdout_stderr.push_str(s); @@ -358,17 +425,19 @@ impl ExternalJobCaching { inputs_hash, stdout_stderr, result: match &result { - Ok(()) => Ok(Result::from_iter(output_file_paths.into_iter().map( - |output_file_path: Interned| -> eyre::Result<_> { - let output_file_path = &*output_file_path; - Ok(( - String::from(output_file_path), - MaybeUtf8::from(std::fs::read(output_file_path).wrap_err_with( - || format!("can't read job output file: {output_file_path}"), - )?), - )) - }, - ))?), + Ok(()) => Ok(ExternalJobCacheV2Files(Result::from_iter( + output_file_paths.into_iter().map( + |output_file_path: Interned| -> eyre::Result<_> { + let output_file_path = &*output_file_path; + Ok(( + PathBuf::from(output_file_path), + MaybeUtf8::from(std::fs::read(output_file_path).wrap_err_with( + || format!("can't read job output file: {output_file_path:?}"), + )?), + )) + }, + ), + )?)), Err(e) => Err(format!("{e:#}")), }, } @@ -377,9 +446,9 @@ impl ExternalJobCaching { } pub fn run_maybe_cached eyre::Result<()>>( this: Option, - command_line: Interned<[Interned]>, - input_file_paths: impl IntoIterator>, - output_file_paths: impl IntoIterator> + Clone, + command_line: Interned<[Interned]>, + input_file_paths: impl IntoIterator>, + output_file_paths: impl IntoIterator> + Clone, run_fn: F, ) -> eyre::Result<()> { match this { @@ -437,31 +506,22 @@ fn parse_which_result( which_result: which::Result, program_name: impl Into, program_path_arg_name: impl FnOnce() -> String, -) -> Result, ResolveProgramPathError> { +) -> Result, ResolveProgramPathError> { let which_result = match which_result { Ok(v) => v, - Err(e) => { + Err(inner) => { return Err(ResolveProgramPathError { - inner: ResolveProgramPathErrorInner::Which(e), + inner, program_name: program_name.into(), program_path_arg_name: program_path_arg_name(), }); } }; - Ok(str::intern_owned( - which_result - .into_os_string() - .into_string() - .map_err(|_| ResolveProgramPathError { - inner: ResolveProgramPathErrorInner::NotValidUtf8, - program_name: program_name.into(), - program_path_arg_name: program_path_arg_name(), - })?, - )) + Ok(which_result.intern_deref()) } impl clap::builder::TypedValueParser for ExternalProgramPathValueParser { - type Value = Interned; + type Value = Interned; fn parse_ref( &self, @@ -495,34 +555,10 @@ pub struct ExternalCommandArgs { pub additional_args: T::AdditionalArgs, } -#[derive(Clone)] -enum ResolveProgramPathErrorInner { - Which(which::Error), - NotValidUtf8, -} - -impl fmt::Debug for ResolveProgramPathErrorInner { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Which(v) => v.fmt(f), - Self::NotValidUtf8 => f.write_str("NotValidUtf8"), - } - } -} - -impl fmt::Display for ResolveProgramPathErrorInner { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Which(v) => v.fmt(f), - Self::NotValidUtf8 => f.write_str("path is not valid UTF-8"), - } - } -} - #[derive(Clone, Debug)] pub struct ResolveProgramPathError { - inner: ResolveProgramPathErrorInner, - program_name: std::ffi::OsString, + inner: which::Error, + program_name: OsString, program_path_arg_name: String, } @@ -546,7 +582,7 @@ pub fn resolve_program_path( program_name: Option<&OsStr>, default_program_name: impl AsRef, program_path_env_var_name: Option<&OsStr>, -) -> Result, ResolveProgramPathError> { +) -> Result, ResolveProgramPathError> { let default_program_name = default_program_name.as_ref(); let owned_program_name; let program_name = if let Some(program_name) = program_name { @@ -564,7 +600,7 @@ pub fn resolve_program_path( impl ExternalCommandArgs { pub fn with_resolved_program_path( - program_path: Interned, + program_path: Interned, additional_args: T::AdditionalArgs, ) -> Self { Self::new( @@ -602,7 +638,7 @@ impl ToArgs for ExternalCommandArgs { } = *self; program_path.to_args(args); if run_even_if_cached { - args.write_arg(format_args!("--{}", T::run_even_if_cached_arg_name())); + args.write_display_arg(format_args!("--{}", T::run_even_if_cached_arg_name())); } additional_args.to_args(args); } @@ -613,13 +649,13 @@ struct ExternalCommandJobParams { command_params: CommandParams, inputs: Interned<[JobItemName]>, outputs: Interned<[JobItemName]>, - output_paths: Interned<[Interned]>, + output_paths: Interned<[Interned]>, } impl ExternalCommandJobParams { fn new(job: &ExternalCommandJob) -> Self { let output_paths = T::output_paths(job); - let mut command_line = ArgsWriter(vec![job.program_path]); + let mut command_line = ArgsWriter(vec![job.program_path.as_interned_os_str()]); T::command_line_args(job, &mut command_line); Self { command_params: CommandParams { @@ -639,8 +675,8 @@ impl ExternalCommandJobParams { #[derive(Deserialize, Serialize)] pub struct ExternalCommandJob { additional_job_data: T::AdditionalJobData, - program_path: Interned, - output_dir: Interned, + program_path: Interned, + output_dir: Interned, run_even_if_cached: bool, #[serde(skip)] params_cache: OnceLock, @@ -722,10 +758,10 @@ impl ExternalCommandJob { pub fn additional_job_data(&self) -> &T::AdditionalJobData { &self.additional_job_data } - pub fn program_path(&self) -> Interned { + pub fn program_path(&self) -> Interned { self.program_path } - pub fn output_dir(&self) -> Interned { + pub fn output_dir(&self) -> Interned { self.output_dir } pub fn run_even_if_cached(&self) -> bool { @@ -741,7 +777,7 @@ impl ExternalCommandJob { pub fn inputs(&self) -> Interned<[JobItemName]> { self.params().inputs } - pub fn output_paths(&self) -> Interned<[Interned]> { + pub fn output_paths(&self) -> Interned<[Interned]> { self.params().output_paths } pub fn outputs(&self) -> Interned<[JobItemName]> { @@ -751,12 +787,12 @@ impl ExternalCommandJob { #[derive(Copy, Clone, PartialEq, Eq, Hash)] pub struct ExternalProgramPath { - program_path: Interned, + program_path: Interned, _phantom: PhantomData, } impl ExternalProgramPath { - pub fn with_resolved_program_path(program_path: Interned) -> Self { + pub fn with_resolved_program_path(program_path: Interned) -> Self { Self { program_path, _phantom: PhantomData, @@ -780,7 +816,7 @@ impl ExternalProgramPath { _phantom: PhantomData, }) } - pub fn program_path(&self) -> Interned { + pub fn program_path(&self) -> Interned { self.program_path } } @@ -874,8 +910,8 @@ impl ToArgs for ExternalProgramPath { program_path, _phantom: _, } = self; - if args.get_long_option_eq(program_path_arg_name) != Some(&**program_path) { - args.write_arg(format_args!("--{program_path_arg_name}={program_path}")); + if args.get_long_option_eq(program_path_arg_name) != Some(program_path.as_os_str()) { + args.write_long_option_eq(program_path_arg_name, program_path); } } } @@ -953,7 +989,8 @@ pub trait ExternalCommand: 'static + Send + Sync + Hash + Eq + fmt::Debug + Size + fmt::Debug + Serialize + DeserializeOwned; - type Dependencies: JobDependencies; + type BaseJobPosition; + type Dependencies: JobDependencies>; type ExternalProgram: ExternalProgramTrait; fn dependencies() -> Self::Dependencies; fn args_to_jobs( @@ -964,9 +1001,9 @@ pub trait ExternalCommand: 'static + Send + Sync + Hash + Eq + fmt::Debug + Size ::JobsAndKinds, )>; fn inputs(job: &ExternalCommandJob) -> Interned<[JobItemName]>; - fn output_paths(job: &ExternalCommandJob) -> Interned<[Interned]>; + fn output_paths(job: &ExternalCommandJob) -> Interned<[Interned]>; fn command_line_args(job: &ExternalCommandJob, args: &mut W); - fn current_dir(job: &ExternalCommandJob) -> Option>; + fn current_dir(job: &ExternalCommandJob) -> Option>; fn job_kind_name() -> Interned; fn args_group_id() -> clap::Id { Interned::into_inner(Self::job_kind_name()).into() @@ -1006,7 +1043,7 @@ impl JobKind for ExternalCommandJobKind { }, } = args.args; let (additional_job_data, dependencies) = T::args_to_jobs(args, params)?; - let base_job = dependencies.base_job(); + let base_job = GetJob::::get_job(&dependencies); let job = ExternalCommandJob { additional_job_data, program_path, diff --git a/crates/fayalite/src/build/firrtl.rs b/crates/fayalite/src/build/firrtl.rs index 62657f8..a04739d 100644 --- a/crates/fayalite/src/build/firrtl.rs +++ b/crates/fayalite/src/build/firrtl.rs @@ -8,12 +8,12 @@ use crate::{ ToArgs, WriteArgs, }, firrtl::{ExportOptions, FileBackend}, - intern::{Intern, Interned}, + intern::{Intern, InternSlice, Interned}, util::job_server::AcquiredJob, }; use clap::Args; use serde::{Deserialize, Serialize}; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)] pub struct FirrtlJobKind; @@ -43,11 +43,11 @@ impl Firrtl { fn make_firrtl_file_backend(&self) -> FileBackend { FileBackend { dir_path: PathBuf::from(&*self.base.output_dir()), - top_fir_file_stem: Some(String::from(&*self.base.file_stem())), + top_fir_file_stem: Some(self.base.file_stem().into()), circuit_name: None, } } - pub fn firrtl_file(&self) -> Interned { + pub fn firrtl_file(&self) -> Interned { self.base.file_with_ext("fir") } } @@ -69,7 +69,7 @@ impl JobKind for FirrtlJobKind { params, |_kind, FirrtlArgs { export_options }, dependencies| { Ok(Firrtl { - base: dependencies.job.job.clone(), + base: dependencies.get_job::().clone(), export_options, }) }, @@ -79,15 +79,15 @@ impl JobKind for FirrtlJobKind { fn inputs(self, job: &Self::Job) -> Interned<[JobItemName]> { [JobItemName::Path { path: job.base.output_dir(), - }][..] - .intern() + }] + .intern_slice() } fn outputs(self, job: &Self::Job) -> Interned<[JobItemName]> { [JobItemName::Path { path: job.firrtl_file(), - }][..] - .intern() + }] + .intern_slice() } fn name(self) -> Interned { diff --git a/crates/fayalite/src/build/formal.rs b/crates/fayalite/src/build/formal.rs index a289c81..02515f2 100644 --- a/crates/fayalite/src/build/formal.rs +++ b/crates/fayalite/src/build/formal.rs @@ -3,23 +3,26 @@ use crate::{ build::{ - CommandParams, DynJobKind, GetBaseJob, JobAndDependencies, JobArgsAndDependencies, - JobDependencies, JobItem, JobItemName, JobKind, JobKindAndDependencies, JobParams, ToArgs, - WriteArgs, + BaseJob, CommandParams, DynJobKind, GetJobPositionDependencies, JobAndDependencies, + JobArgsAndDependencies, JobDependencies, JobItem, JobItemName, JobKind, + JobKindAndDependencies, JobParams, ToArgs, WriteArgs, external::{ ExternalCommand, ExternalCommandJob, ExternalCommandJobKind, ExternalProgramTrait, }, - interned_known_utf8_method, - verilog::{VerilogDialect, VerilogJob, VerilogJobKind}, + verilog::{UnadjustedVerilog, VerilogDialect, VerilogJob, VerilogJobKind}, }, - intern::{Intern, Interned}, + intern::{Intern, InternSlice, Interned}, module::NameId, util::job_server::AcquiredJob, }; use clap::{Args, ValueEnum}; use eyre::Context; use serde::{Deserialize, Serialize}; -use std::fmt; +use std::{ + ffi::{OsStr, OsString}, + fmt::{self, Write}, + path::Path, +}; #[derive(ValueEnum, Copy, Clone, Debug, PartialEq, Eq, Hash, Default, Deserialize, Serialize)] #[non_exhaustive] @@ -52,7 +55,7 @@ impl fmt::Display for FormalMode { #[non_exhaustive] pub struct FormalArgs { #[arg(long = "sby-extra-arg", value_name = "ARG")] - pub sby_extra_args: Vec, + pub sby_extra_args: Vec, #[arg(long, default_value_t)] pub formal_mode: FormalMode, #[arg(long, default_value_t = Self::DEFAULT_DEPTH)] @@ -60,7 +63,7 @@ pub struct FormalArgs { #[arg(long, default_value = Self::DEFAULT_SOLVER)] pub formal_solver: String, #[arg(long = "smtbmc-extra-arg", value_name = "ARG")] - pub smtbmc_extra_args: Vec, + pub smtbmc_extra_args: Vec, } impl FormalArgs { @@ -77,21 +80,17 @@ impl ToArgs for FormalArgs { formal_solver, smtbmc_extra_args, } = self; - args.extend( - sby_extra_args - .iter() - .map(|v| format!("--sby-extra-arg={v}")), - ); - args.extend([ + for arg in sby_extra_args { + args.write_long_option_eq("sby-extra-arg", arg); + } + args.write_display_args([ format_args!("--formal-mode={formal_mode}"), format_args!("--formal-depth={formal_depth}"), format_args!("--formal-solver={formal_solver}"), ]); - args.extend( - smtbmc_extra_args - .iter() - .map(|v| format!("--smtbmc-extra-arg={v}")), - ); + for arg in smtbmc_extra_args { + args.write_long_option_eq("smtbmc-extra-arg", arg); + } } } @@ -100,18 +99,18 @@ pub struct WriteSbyFileJobKind; #[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)] pub struct WriteSbyFileJob { - sby_extra_args: Interned<[Interned]>, + sby_extra_args: Interned<[Interned]>, formal_mode: FormalMode, formal_depth: u64, formal_solver: Interned, - smtbmc_extra_args: Interned<[Interned]>, - sby_file: Interned, - output_dir: Interned, - main_verilog_file: Interned, + smtbmc_extra_args: Interned<[Interned]>, + sby_file: Interned, + output_dir: Interned, + main_verilog_file: Interned, } impl WriteSbyFileJob { - pub fn sby_extra_args(&self) -> Interned<[Interned]> { + pub fn sby_extra_args(&self) -> Interned<[Interned]> { self.sby_extra_args } pub fn formal_mode(&self) -> FormalMode { @@ -123,24 +122,24 @@ impl WriteSbyFileJob { pub fn formal_solver(&self) -> Interned { self.formal_solver } - pub fn smtbmc_extra_args(&self) -> Interned<[Interned]> { + pub fn smtbmc_extra_args(&self) -> Interned<[Interned]> { self.smtbmc_extra_args } - pub fn sby_file(&self) -> Interned { + pub fn sby_file(&self) -> Interned { self.sby_file } - pub fn output_dir(&self) -> Interned { + pub fn output_dir(&self) -> Interned { self.output_dir } - pub fn main_verilog_file(&self) -> Interned { + pub fn main_verilog_file(&self) -> Interned { self.main_verilog_file } - fn write_sby( + fn write_sby( &self, - output: &mut W, - additional_files: &[Interned], + output: &mut OsString, + additional_files: &[Interned], main_module_name_id: NameId, - ) -> Result, fmt::Error> { + ) -> eyre::Result<()> { let Self { sby_extra_args: _, formal_mode, @@ -160,23 +159,21 @@ impl WriteSbyFileJob { \n\ [engines]\n\ smtbmc {formal_solver} -- --" - )?; + ) + .expect("writing to OsString can't fail"); for i in smtbmc_extra_args { - output.write_str(" ")?; - output.write_str(i)?; + output.push(" "); + output.push(i); } - output.write_str( + output.push( "\n\ \n\ [script]\n", - )?; - let all_verilog_files = - match VerilogJob::all_verilog_files(*main_verilog_file, additional_files) { - Ok(v) => v, - Err(e) => return Ok(Err(e)), - }; - for verilog_file in all_verilog_files { - writeln!(output, "read_verilog -sv -formal \"{verilog_file}\"")?; + ); + for verilog_file in VerilogJob::all_verilog_files(*main_verilog_file, additional_files)? { + output.push("read_verilog -sv -formal \""); + output.push(verilog_file); + output.push("\"\n"); } let circuit_name = crate::firrtl::get_circuit_name(main_module_name_id); // workaround for wires disappearing -- set `keep` on all wires @@ -186,8 +183,9 @@ impl WriteSbyFileJob { proc\n\ setattr -set keep 1 w:\\*\n\ prep", - )?; - Ok(Ok(())) + ) + .expect("writing to OsString can't fail"); + Ok(()) } } @@ -219,18 +217,16 @@ impl JobKind for WriteSbyFileJobKind { formal_solver, smtbmc_extra_args, } = args; + let base_job = dependencies.get_job::(); Ok(WriteSbyFileJob { - sby_extra_args: sby_extra_args.into_iter().map(str::intern_owned).collect(), + sby_extra_args: sby_extra_args.into_iter().map(Interned::from).collect(), formal_mode, formal_depth, - formal_solver: str::intern_owned(formal_solver), - smtbmc_extra_args: smtbmc_extra_args - .into_iter() - .map(str::intern_owned) - .collect(), - sby_file: dependencies.base_job().file_with_ext("sby"), - output_dir: dependencies.base_job().output_dir(), - main_verilog_file: dependencies.job.job.main_verilog_file(), + formal_solver: formal_solver.intern_deref(), + smtbmc_extra_args: smtbmc_extra_args.into_iter().map(Interned::from).collect(), + sby_file: base_job.file_with_ext("sby"), + output_dir: base_job.output_dir(), + main_verilog_file: dependencies.get_job::().main_verilog_file(), }) }) } @@ -238,12 +234,12 @@ impl JobKind for WriteSbyFileJobKind { fn inputs(self, _job: &Self::Job) -> Interned<[JobItemName]> { [JobItemName::DynamicPaths { source_job_name: VerilogJobKind.name(), - }][..] - .intern() + }] + .intern_slice() } fn outputs(self, job: &Self::Job) -> Interned<[JobItemName]> { - [JobItemName::Path { path: job.sby_file }][..].intern() + [JobItemName::Path { path: job.sby_file }].intern_slice() } fn name(self) -> Interned { @@ -266,18 +262,16 @@ impl JobKind for WriteSbyFileJobKind { unreachable!(); }; let additional_files = VerilogJob::unwrap_additional_files(additional_files); - let mut contents = String::new(); - match job.write_sby( + let mut contents = OsString::new(); + job.write_sby( &mut contents, additional_files, params.main_module().name_id(), - ) { - Ok(result) => result?, - Err(fmt::Error) => unreachable!("writing to String can't fail"), - } - std::fs::write(job.sby_file, contents) - .wrap_err_with(|| format!("writing {} failed", job.sby_file))?; - Ok(vec![JobItem::Path { path: job.sby_file }]) + )?; + let path = job.sby_file; + std::fs::write(path, contents.as_encoded_bytes()) + .wrap_err_with(|| format!("writing {path:?} failed"))?; + Ok(vec![JobItem::Path { path }]) } fn subcommand_hidden(self) -> bool { @@ -289,7 +283,7 @@ impl JobKind for WriteSbyFileJobKind { pub struct Formal { #[serde(flatten)] write_sby_file: WriteSbyFileJob, - sby_file_name: Interned, + sby_file_name: Interned, } impl fmt::Debug for Formal { @@ -342,6 +336,11 @@ impl ToArgs for FormalAdditionalArgs { impl ExternalCommand for Formal { type AdditionalArgs = FormalAdditionalArgs; type AdditionalJobData = Formal; + type BaseJobPosition = GetJobPositionDependencies< + GetJobPositionDependencies< + GetJobPositionDependencies<::BaseJobPosition>, + >, + >; type Dependencies = JobKindAndDependencies; type ExternalProgram = Symbiyosys; @@ -358,11 +357,13 @@ impl ExternalCommand for Formal { )> { args.args_to_jobs_external_simple(params, |args, dependencies| { let FormalAdditionalArgs {} = args.additional_args; + let write_sby_file = dependencies.get_job::().clone(); Ok(Formal { - write_sby_file: dependencies.job.job.clone(), - sby_file_name: interned_known_utf8_method(dependencies.job.job.sby_file(), |v| { - v.file_name().expect("known to have file name") - }), + sby_file_name: write_sby_file + .sby_file() + .interned_file_name() + .expect("known to have file name"), + write_sby_file, }) }) } @@ -378,22 +379,22 @@ impl ExternalCommand for Formal { JobItemName::DynamicPaths { source_job_name: VerilogJobKind.name(), }, - ][..] - .intern() + ] + .intern_slice() } - fn output_paths(_job: &ExternalCommandJob) -> Interned<[Interned]> { + fn output_paths(_job: &ExternalCommandJob) -> Interned<[Interned]> { Interned::default() } fn command_line_args(job: &ExternalCommandJob, args: &mut W) { // args.write_str_arg("-j1"); // sby seems not to respect job count in parallel mode - args.write_str_arg("-f"); + args.write_arg("-f"); args.write_interned_arg(job.additional_job_data().sby_file_name); args.write_interned_args(job.additional_job_data().write_sby_file.sby_extra_args()); } - fn current_dir(job: &ExternalCommandJob) -> Option> { + fn current_dir(job: &ExternalCommandJob) -> Option> { Some(job.output_dir()) } diff --git a/crates/fayalite/src/build/graph.rs b/crates/fayalite/src/build/graph.rs index 0cf54d5..b727715 100644 --- a/crates/fayalite/src/build/graph.rs +++ b/crates/fayalite/src/build/graph.rs @@ -16,9 +16,12 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error, ser::Se use std::{ cell::OnceCell, collections::{BTreeMap, BTreeSet, VecDeque}, + convert::Infallible, + ffi::OsStr, fmt::{self, Write}, panic, rc::Rc, + str::Utf8Error, sync::mpsc, thread::{self, ScopedJoinHandle}, }; @@ -138,8 +141,8 @@ impl<'a> fmt::Display for EscapeForUnixShell<'a> { } impl<'a> EscapeForUnixShell<'a> { - pub fn new(s: &'a str) -> Self { - Self::from_bytes(s.as_bytes()) + pub fn new(s: &'a (impl ?Sized + AsRef)) -> Self { + Self::from_bytes(s.as_ref().as_encoded_bytes()) } fn make_prefix(bytes: &[u8]) -> [u8; 3] { let mut prefix = [0; 3]; @@ -262,7 +265,7 @@ pub enum UnixMakefileEscapeKind { #[derive(Copy, Clone)] pub struct EscapeForUnixMakefile<'a> { - s: &'a str, + s: &'a OsStr, kind: UnixMakefileEscapeKind, } @@ -274,9 +277,13 @@ impl<'a> fmt::Debug for EscapeForUnixMakefile<'a> { impl<'a> fmt::Display for EscapeForUnixMakefile<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.do_write(f, fmt::Write::write_str, fmt::Write::write_char, |_, _| { - Ok(()) - }) + self.do_write( + f, + fmt::Write::write_str, + fmt::Write::write_char, + |_, _| Ok(()), + |_| unreachable!("already checked that the input causes no UTF-8 errors"), + ) } } @@ -287,6 +294,7 @@ impl<'a> EscapeForUnixMakefile<'a> { write_str: impl Fn(&mut S, &str) -> Result<(), E>, write_char: impl Fn(&mut S, char) -> Result<(), E>, add_variable: impl Fn(&mut S, &'static str) -> Result<(), E>, + utf8_error: impl Fn(Utf8Error) -> E, ) -> Result<(), E> { let escape_recipe_char = |c| match c { '$' => write_str(state, "$$"), @@ -296,24 +304,30 @@ impl<'a> EscapeForUnixMakefile<'a> { _ => write_char(state, c), }; match self.kind { - UnixMakefileEscapeKind::NonRecipe => self.s.chars().try_for_each(|c| match c { - '=' => { - add_variable(state, "EQUALS = =")?; - write_str(state, "$(EQUALS)") - } - ';' => panic!("can't escape a semicolon (;) for Unix Makefile"), - '$' => write_str(state, "$$"), - '\\' | ' ' | '#' | ':' | '%' | '*' | '?' | '[' | ']' | '~' => { - write_char(state, '\\')?; - write_char(state, c) - } - '\0'..='\x1F' | '\x7F' => { - panic!("can't escape a control character for Unix Makefile: {c:?}"); - } - _ => write_char(state, c), - }), + UnixMakefileEscapeKind::NonRecipe => str::from_utf8(self.s.as_encoded_bytes()) + .map_err(&utf8_error)? + .chars() + .try_for_each(|c| match c { + '=' => { + add_variable(state, "EQUALS = =")?; + write_str(state, "$(EQUALS)") + } + ';' => panic!("can't escape a semicolon (;) for Unix Makefile"), + '$' => write_str(state, "$$"), + '\\' | ' ' | '#' | ':' | '%' | '*' | '?' | '[' | ']' | '~' => { + write_char(state, '\\')?; + write_char(state, c) + } + '\0'..='\x1F' | '\x7F' => { + panic!("can't escape a control character for Unix Makefile: {c:?}"); + } + _ => write_char(state, c), + }), UnixMakefileEscapeKind::RecipeWithoutShellEscaping => { - self.s.chars().try_for_each(escape_recipe_char) + str::from_utf8(self.s.as_encoded_bytes()) + .map_err(&utf8_error)? + .chars() + .try_for_each(escape_recipe_char) } UnixMakefileEscapeKind::RecipeWithShellEscaping => { EscapeForUnixShell::new(self.s).try_for_each(escape_recipe_char) @@ -321,21 +335,23 @@ impl<'a> EscapeForUnixMakefile<'a> { } } pub fn new( - s: &'a str, + s: &'a (impl ?Sized + AsRef), kind: UnixMakefileEscapeKind, needed_variables: &mut BTreeSet<&'static str>, - ) -> Self { + ) -> Result { + let s = s.as_ref(); let retval = Self { s, kind }; - let Ok(()) = retval.do_write( + retval.do_write( needed_variables, |_, _| Ok(()), |_, _| Ok(()), - |needed_variables, variable| -> Result<(), std::convert::Infallible> { + |needed_variables, variable| { needed_variables.insert(variable); Ok(()) }, - ); - retval + |e| e, + )?; + Ok(retval) } } @@ -473,7 +489,7 @@ impl JobGraph { Err(e) => panic!("error: {e}"), } } - pub fn to_unix_makefile(&self, extra_args: &[Interned]) -> String { + pub fn to_unix_makefile(&self, extra_args: &[Interned]) -> Result { self.to_unix_makefile_with_internal_program_prefix( &[program_name_for_internal_jobs()], extra_args, @@ -481,9 +497,9 @@ impl JobGraph { } pub fn to_unix_makefile_with_internal_program_prefix( &self, - internal_program_prefix: &[Interned], - extra_args: &[Interned], - ) -> String { + internal_program_prefix: &[Interned], + extra_args: &[Interned], + ) -> Result { let mut retval = String::new(); let mut needed_variables = BTreeSet::new(); let mut phony_targets = BTreeSet::new(); @@ -502,10 +518,10 @@ impl JobGraph { retval, "{} ", EscapeForUnixMakefile::new( - &path, + &str::from_utf8(path.as_os_str().as_encoded_bytes())?, UnixMakefileEscapeKind::NonRecipe, &mut needed_variables - ) + )? ); } JobItemName::DynamicPaths { source_job_name } => { @@ -516,7 +532,7 @@ impl JobGraph { &source_job_name, UnixMakefileEscapeKind::NonRecipe, &mut needed_variables - ) + )? ); phony_targets.insert(Interned::into_inner(source_job_name)); } @@ -535,10 +551,10 @@ impl JobGraph { retval, " {}", EscapeForUnixMakefile::new( - &path, + &str::from_utf8(path.as_os_str().as_encoded_bytes())?, UnixMakefileEscapeKind::NonRecipe, &mut needed_variables - ) + )? ); } JobItemName::DynamicPaths { source_job_name } => { @@ -549,7 +565,7 @@ impl JobGraph { &source_job_name, UnixMakefileEscapeKind::NonRecipe, &mut needed_variables - ) + )? ); phony_targets.insert(Interned::into_inner(source_job_name)); } @@ -558,17 +574,17 @@ impl JobGraph { retval.push_str("\n\t"); job.command_params_with_internal_program_prefix(internal_program_prefix, extra_args) .to_unix_shell_line(&mut retval, |arg, output| { - write!( + write_str!( output, "{}", EscapeForUnixMakefile::new( arg, UnixMakefileEscapeKind::RecipeWithShellEscaping, &mut needed_variables - ) - ) - }) - .expect("writing to String never fails"); + )? + ); + Ok(()) + })?; retval.push_str("\n\n"); } if !phony_targets.is_empty() { @@ -581,7 +597,7 @@ impl JobGraph { phony_target, UnixMakefileEscapeKind::NonRecipe, &mut needed_variables - ) + )? ); } retval.push_str("\n"); @@ -592,9 +608,9 @@ impl JobGraph { &String::from_iter(needed_variables.into_iter().map(|v| format!("{v}\n"))), ); } - retval + Ok(retval) } - pub fn to_unix_shell_script(&self, extra_args: &[Interned]) -> String { + pub fn to_unix_shell_script(&self, extra_args: &[Interned]) -> String { self.to_unix_shell_script_with_internal_program_prefix( &[program_name_for_internal_jobs()], extra_args, @@ -602,8 +618,8 @@ impl JobGraph { } pub fn to_unix_shell_script_with_internal_program_prefix( &self, - internal_program_prefix: &[Interned], - extra_args: &[Interned], + internal_program_prefix: &[Interned], + extra_args: &[Interned], ) -> String { let mut retval = String::from( "#!/bin/sh\n\ @@ -613,11 +629,12 @@ impl JobGraph { let JobGraphNode::Job(job) = &self.graph[node_id] else { continue; }; - job.command_params_with_internal_program_prefix(internal_program_prefix, extra_args) - .to_unix_shell_line(&mut retval, |arg, output| { - write!(output, "{}", EscapeForUnixShell::new(&arg)) - }) - .expect("writing to String never fails"); + let Ok(()) = job + .command_params_with_internal_program_prefix(internal_program_prefix, extra_args) + .to_unix_shell_line(&mut retval, |arg, output| -> Result<(), Infallible> { + write_str!(output, "{}", EscapeForUnixShell::new(&arg)); + Ok(()) + }); retval.push_str("\n"); } retval diff --git a/crates/fayalite/src/build/vendor/xilinx/yosys_nextpnr_prjxray.rs b/crates/fayalite/src/build/vendor/xilinx/yosys_nextpnr_prjxray.rs index 3db256a..6691915 100644 --- a/crates/fayalite/src/build/vendor/xilinx/yosys_nextpnr_prjxray.rs +++ b/crates/fayalite/src/build/vendor/xilinx/yosys_nextpnr_prjxray.rs @@ -4,18 +4,18 @@ use crate::{ annotations::Annotation, build::{ - CommandParams, DynJobKind, GetBaseJob, JobAndDependencies, JobArgsAndDependencies, - JobDependencies, JobItem, JobItemName, JobKind, JobKindAndDependencies, ToArgs, WriteArgs, + BaseJob, CommandParams, DynJobKind, GetJobPositionDependencies, JobAndDependencies, + JobArgsAndDependencies, JobDependencies, JobItem, JobItemName, JobKind, + JobKindAndDependencies, ToArgs, WriteArgs, external::{ ExternalCommand, ExternalCommandJob, ExternalCommandJobKind, ExternalProgramTrait, }, - interned_known_utf8_method, interned_known_utf8_path_buf_method, vendor::xilinx::{XdcIOStandardAnnotation, XdcLocationAnnotation}, - verilog::{VerilogDialect, VerilogJob, VerilogJobKind}, + verilog::{UnadjustedVerilog, VerilogDialect, VerilogJob, VerilogJobKind}, }, bundle::Bundle, firrtl::{ScalarizedModuleABI, ScalarizedModuleABIAnnotations, ScalarizedModuleABIPort}, - intern::{Intern, Interned}, + intern::{Intern, InternSlice, Interned}, module::{Module, NameId}, prelude::JobParams, util::job_server::AcquiredJob, @@ -23,7 +23,12 @@ use crate::{ use clap::ValueEnum; use eyre::Context; use serde::{Deserialize, Serialize}; -use std::{fmt, ops::ControlFlow}; +use std::{ + ffi::{OsStr, OsString}, + fmt::{self, Write}, + ops::ControlFlow, + path::{Path, PathBuf}, +}; #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash, Default)] pub struct YosysNextpnrXrayWriteYsFileJobKind; @@ -39,52 +44,52 @@ impl ToArgs for YosysNextpnrXrayWriteYsFileArgs { #[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)] pub struct YosysNextpnrXrayWriteYsFile { - main_verilog_file: Interned, - ys_file: Interned, - json_file: Interned, - json_file_name: Interned, + main_verilog_file: Interned, + ys_file: Interned, + json_file: Interned, + json_file_name: Interned, } impl YosysNextpnrXrayWriteYsFile { - pub fn main_verilog_file(&self) -> Interned { + pub fn main_verilog_file(&self) -> Interned { self.main_verilog_file } - pub fn ys_file(&self) -> Interned { + pub fn ys_file(&self) -> Interned { self.ys_file } - pub fn json_file(&self) -> Interned { + pub fn json_file(&self) -> Interned { self.json_file } - pub fn json_file_name(&self) -> Interned { + pub fn json_file_name(&self) -> Interned { self.json_file_name } - fn write_ys( + fn write_ys( &self, - output: &mut W, - additional_files: &[Interned], + output: &mut OsString, + additional_files: &[Interned], main_module_name_id: NameId, - ) -> Result, fmt::Error> { + ) -> eyre::Result<()> { let Self { main_verilog_file, ys_file: _, json_file: _, json_file_name, } = self; - let all_verilog_files = - match VerilogJob::all_verilog_files(*main_verilog_file, additional_files) { - Ok(v) => v, - Err(e) => return Ok(Err(e)), - }; - for verilog_file in all_verilog_files { - writeln!(output, "read_verilog -sv \"{verilog_file}\"")?; + for verilog_file in VerilogJob::all_verilog_files(*main_verilog_file, additional_files)? { + output.push("read_verilog -sv \""); + output.push(verilog_file); + output.push("\"\n"); } let circuit_name = crate::firrtl::get_circuit_name(main_module_name_id); writeln!( output, "synth_xilinx -flatten -abc9 -nobram -arch xc7 -top {circuit_name}" - )?; - writeln!(output, "write_json \"{json_file_name}\"")?; - Ok(Ok(())) + ) + .expect("writing to OsString can't fail"); + output.push("write_json \""); + output.push(json_file_name); + output.push("\"\n"); + Ok(()) } } @@ -110,14 +115,16 @@ impl JobKind for YosysNextpnrXrayWriteYsFileJobKind { .get_or_insert(VerilogDialect::Yosys); args.args_to_jobs_simple(params, |_kind, args, dependencies| { let YosysNextpnrXrayWriteYsFileArgs {} = args; - let json_file = dependencies.base_job().file_with_ext("json"); + let base_job = dependencies.get_job::(); + let verilog_job = dependencies.get_job::(); + let json_file = base_job.file_with_ext("json"); Ok(YosysNextpnrXrayWriteYsFile { - main_verilog_file: dependencies.job.job.main_verilog_file(), - ys_file: dependencies.base_job().file_with_ext("ys"), + main_verilog_file: verilog_job.main_verilog_file(), + ys_file: base_job.file_with_ext("ys"), json_file, - json_file_name: interned_known_utf8_method(json_file, |v| { - v.file_name().expect("known to have file name") - }), + json_file_name: json_file + .interned_file_name() + .expect("known to have file name"), }) }) } @@ -125,12 +132,12 @@ impl JobKind for YosysNextpnrXrayWriteYsFileJobKind { fn inputs(self, _job: &Self::Job) -> Interned<[JobItemName]> { [JobItemName::DynamicPaths { source_job_name: VerilogJobKind.name(), - }][..] - .intern() + }] + .intern_slice() } fn outputs(self, job: &Self::Job) -> Interned<[JobItemName]> { - [JobItemName::Path { path: job.ys_file }][..].intern() + [JobItemName::Path { path: job.ys_file }].intern_slice() } fn name(self) -> Interned { @@ -153,18 +160,16 @@ impl JobKind for YosysNextpnrXrayWriteYsFileJobKind { unreachable!(); }; let additional_files = VerilogJob::unwrap_additional_files(additional_files); - let mut contents = String::new(); - match job.write_ys( + let mut contents = OsString::new(); + job.write_ys( &mut contents, additional_files, params.main_module().name_id(), - ) { - Ok(result) => result?, - Err(fmt::Error) => unreachable!("writing to String can't fail"), - } - std::fs::write(job.ys_file, contents) - .wrap_err_with(|| format!("writing {} failed", job.ys_file))?; - Ok(vec![JobItem::Path { path: job.ys_file }]) + )?; + let path = job.ys_file; + std::fs::write(path, contents.as_encoded_bytes()) + .wrap_err_with(|| format!("writing {path:?} failed"))?; + Ok(vec![JobItem::Path { path }]) } fn subcommand_hidden(self) -> bool { @@ -185,7 +190,7 @@ impl ToArgs for YosysNextpnrXraySynthArgs { pub struct YosysNextpnrXraySynth { #[serde(flatten)] write_ys_file: YosysNextpnrXrayWriteYsFile, - ys_file_name: Interned, + ys_file_name: Interned, } impl fmt::Debug for YosysNextpnrXraySynth { @@ -211,19 +216,19 @@ impl fmt::Debug for YosysNextpnrXraySynth { } impl YosysNextpnrXraySynth { - pub fn main_verilog_file(&self) -> Interned { + pub fn main_verilog_file(&self) -> Interned { self.write_ys_file.main_verilog_file() } - pub fn ys_file(&self) -> Interned { + pub fn ys_file(&self) -> Interned { self.write_ys_file.ys_file() } - pub fn ys_file_name(&self) -> Interned { + pub fn ys_file_name(&self) -> Interned { self.ys_file_name } - pub fn json_file(&self) -> Interned { + pub fn json_file(&self) -> Interned { self.write_ys_file.json_file() } - pub fn json_file_name(&self) -> Interned { + pub fn json_file_name(&self) -> Interned { self.write_ys_file.json_file_name() } } @@ -240,6 +245,11 @@ impl ExternalProgramTrait for Yosys { impl ExternalCommand for YosysNextpnrXraySynth { type AdditionalArgs = YosysNextpnrXraySynthArgs; type AdditionalJobData = Self; + type BaseJobPosition = GetJobPositionDependencies< + GetJobPositionDependencies< + GetJobPositionDependencies<::BaseJobPosition>, + >, + >; type Dependencies = JobKindAndDependencies; type ExternalProgram = Yosys; @@ -258,9 +268,12 @@ impl ExternalCommand for YosysNextpnrXraySynth { let YosysNextpnrXraySynthArgs {} = args.additional_args; Ok(Self { write_ys_file: dependencies.job.job.clone(), - ys_file_name: interned_known_utf8_method(dependencies.job.job.ys_file(), |v| { - v.file_name().expect("known to have file name") - }), + ys_file_name: dependencies + .job + .job + .ys_file() + .interned_file_name() + .expect("known to have file name"), }) }) } @@ -276,20 +289,20 @@ impl ExternalCommand for YosysNextpnrXraySynth { JobItemName::DynamicPaths { source_job_name: VerilogJobKind.name(), }, - ][..] - .intern() + ] + .intern_slice() } - fn output_paths(job: &ExternalCommandJob) -> Interned<[Interned]> { - [job.additional_job_data().json_file()][..].intern() + fn output_paths(job: &ExternalCommandJob) -> Interned<[Interned]> { + [job.additional_job_data().json_file()].intern_slice() } fn command_line_args(job: &ExternalCommandJob, args: &mut W) { - args.write_str_arg("-s"); + args.write_arg("-s"); args.write_interned_arg(job.additional_job_data().ys_file_name()); } - fn current_dir(job: &ExternalCommandJob) -> Option> { + fn current_dir(job: &ExternalCommandJob) -> Option> { Some(job.output_dir()) } @@ -317,8 +330,8 @@ impl ToArgs for YosysNextpnrXrayWriteXdcFileArgs { #[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)] pub struct YosysNextpnrXrayWriteXdcFile { firrtl_export_options: crate::firrtl::ExportOptions, - output_dir: Interned, - xdc_file: Interned, + output_dir: Interned, + xdc_file: Interned, } struct WriteXdcContentsError(eyre::Report); @@ -424,10 +437,11 @@ impl JobKind for YosysNextpnrXrayWriteXdcFileJobKind { .export_options; args.args_to_jobs_simple(params, |_kind, args, dependencies| { let YosysNextpnrXrayWriteXdcFileArgs {} = args; + let base_job = dependencies.get_job::(); Ok(YosysNextpnrXrayWriteXdcFile { firrtl_export_options, - output_dir: dependencies.base_job().output_dir(), - xdc_file: dependencies.base_job().file_with_ext("xdc"), + output_dir: base_job.output_dir(), + xdc_file: base_job.file_with_ext("xdc"), }) }) } @@ -435,12 +449,12 @@ impl JobKind for YosysNextpnrXrayWriteXdcFileJobKind { fn inputs(self, job: &Self::Job) -> Interned<[JobItemName]> { [JobItemName::Path { path: job.output_dir, - }][..] - .intern() + }] + .intern_slice() } fn outputs(self, job: &Self::Job) -> Interned<[JobItemName]> { - [JobItemName::Path { path: job.xdc_file }][..].intern() + [JobItemName::Path { path: job.xdc_file }].intern_slice() } fn name(self) -> Interned { @@ -611,7 +625,7 @@ impl fmt::Display for Device { #[derive(Clone, PartialEq, Eq, Hash, Debug, clap::Args)] pub struct YosysNextpnrXrayRunNextpnrArgs { #[arg(long, env = "CHIPDB_DIR", value_hint = clap::ValueHint::DirPath)] - pub nextpnr_xilinx_chipdb_dir: String, + pub nextpnr_xilinx_chipdb_dir: PathBuf, #[arg(long)] pub device: Device, #[arg(long, default_value_t = 0)] @@ -625,42 +639,43 @@ impl ToArgs for YosysNextpnrXrayRunNextpnrArgs { device, nextpnr_xilinx_seed, } = self; - args.write_args([ - format_args!("--nextpnr-xilinx-chipdb-dir={nextpnr_xilinx_chipdb_dir}"), - format_args!("--device={device}"), - format_args!("--nextpnr-xilinx-seed={nextpnr_xilinx_seed}"), - ]); + args.write_long_option_eq("nextpnr-xilinx-chipdb-dir", nextpnr_xilinx_chipdb_dir); + args.write_long_option_eq("device", device.as_str()); + args.write_display_arg(format_args!("--nextpnr-xilinx-seed={nextpnr_xilinx_seed}")); } } #[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)] pub struct YosysNextpnrXrayRunNextpnr { - nextpnr_xilinx_chipdb_dir: Interned, + nextpnr_xilinx_chipdb_dir: Interned, device: Device, nextpnr_xilinx_seed: i32, - xdc_file: Interned, - xdc_file_name: Interned, - json_file: Interned, - json_file_name: Interned, - routed_json_file: Interned, - routed_json_file_name: Interned, - fasm_file: Interned, - fasm_file_name: Interned, + xdc_file: Interned, + xdc_file_name: Interned, + json_file: Interned, + json_file_name: Interned, + routed_json_file: Interned, + routed_json_file_name: Interned, + fasm_file: Interned, + fasm_file_name: Interned, } impl YosysNextpnrXrayRunNextpnr { - fn chipdb_file(&self) -> Interned { - interned_known_utf8_path_buf_method(self.nextpnr_xilinx_chipdb_dir, |chipdb_dir| { - let mut retval = chipdb_dir.join(self.device.xray_device()); - retval.set_extension("bin"); - retval - }) + fn chipdb_file(&self) -> Interned { + let mut retval = self + .nextpnr_xilinx_chipdb_dir + .join(self.device.xray_device()); + retval.set_extension("bin"); + retval.intern_deref() } } impl ExternalCommand for YosysNextpnrXrayRunNextpnr { type AdditionalArgs = YosysNextpnrXrayRunNextpnrArgs; type AdditionalJobData = Self; + type BaseJobPosition = GetJobPositionDependencies< + GetJobPositionDependencies<::BaseJobPosition>, + >; type Dependencies = JobKindAndDependencies; type ExternalProgram = NextpnrXilinx; @@ -681,37 +696,30 @@ impl ExternalCommand for YosysNextpnrXrayRunNextpnr { device, nextpnr_xilinx_seed, } = args.additional_args; - let xdc_file = dependencies.job.job.xdc_file; - let routed_json_file = dependencies.base_job().file_with_ext("routed.json"); - let fasm_file = dependencies.base_job().file_with_ext("fasm"); + let base_job = dependencies.get_job::(); + let write_xdc_file = dependencies.get_job::(); + let synth = dependencies.get_job::, _>(); + let routed_json_file = base_job.file_with_ext("routed.json"); + let fasm_file = base_job.file_with_ext("fasm"); Ok(Self { - nextpnr_xilinx_chipdb_dir: str::intern_owned(nextpnr_xilinx_chipdb_dir), + nextpnr_xilinx_chipdb_dir: nextpnr_xilinx_chipdb_dir.intern_deref(), device, nextpnr_xilinx_seed, - xdc_file, - xdc_file_name: interned_known_utf8_method(xdc_file, |v| { - v.file_name().expect("known to have file name") - }), - json_file: dependencies - .dependencies - .job - .job - .additional_job_data() - .json_file(), - json_file_name: dependencies - .dependencies - .job - .job - .additional_job_data() - .json_file_name(), + xdc_file: write_xdc_file.xdc_file, + xdc_file_name: write_xdc_file + .xdc_file + .interned_file_name() + .expect("known to have file name"), + json_file: synth.additional_job_data().json_file(), + json_file_name: synth.additional_job_data().json_file_name(), routed_json_file, - routed_json_file_name: interned_known_utf8_method(routed_json_file, |v| { - v.file_name().expect("known to have file name") - }), + routed_json_file_name: routed_json_file + .interned_file_name() + .expect("known to have file name"), fasm_file, - fasm_file_name: interned_known_utf8_method(fasm_file, |v| { - v.file_name().expect("known to have file name") - }), + fasm_file_name: fasm_file + .interned_file_name() + .expect("known to have file name"), }) }) } @@ -724,16 +732,16 @@ impl ExternalCommand for YosysNextpnrXrayRunNextpnr { JobItemName::Path { path: job.additional_job_data().xdc_file, }, - ][..] - .intern() + ] + .intern_slice() } - fn output_paths(job: &ExternalCommandJob) -> Interned<[Interned]> { + fn output_paths(job: &ExternalCommandJob) -> Interned<[Interned]> { [ job.additional_job_data().routed_json_file, job.additional_job_data().fasm_file, - ][..] - .intern() + ] + .intern_slice() } fn command_line_args(job: &ExternalCommandJob, args: &mut W) { @@ -745,17 +753,15 @@ impl ExternalCommand for YosysNextpnrXrayRunNextpnr { fasm_file_name, .. } = job.additional_job_data(); - args.write_args([ - format_args!("--chipdb={}", job_data.chipdb_file()), - format_args!("--xdc={xdc_file_name}"), - format_args!("--json={json_file_name}"), - format_args!("--write={routed_json_file_name}"), - format_args!("--fasm={fasm_file_name}"), - format_args!("--seed={nextpnr_xilinx_seed}"), - ]); + args.write_long_option_eq("chipdb", job_data.chipdb_file()); + args.write_long_option_eq("xdc", xdc_file_name); + args.write_long_option_eq("json", json_file_name); + args.write_long_option_eq("write", routed_json_file_name); + args.write_long_option_eq("fasm", fasm_file_name); + args.write_display_arg(format_args!("--seed={nextpnr_xilinx_seed}")); } - fn current_dir(job: &ExternalCommandJob) -> Option> { + fn current_dir(job: &ExternalCommandJob) -> Option> { Some(job.output_dir()) } @@ -780,47 +786,48 @@ impl ExternalProgramTrait for Xcfasm { #[derive(Clone, PartialEq, Eq, Hash, Debug, clap::Args)] pub struct YosysNextpnrXrayArgs { #[arg(long, env = "DB_DIR", value_hint = clap::ValueHint::DirPath)] - pub prjxray_db_dir: String, + pub prjxray_db_dir: PathBuf, } impl ToArgs for YosysNextpnrXrayArgs { fn to_args(&self, args: &mut (impl WriteArgs + ?Sized)) { let Self { prjxray_db_dir } = self; - args.write_arg(format_args!("--prjxray-db-dir={prjxray_db_dir}")); + args.write_long_option_eq("prjxray-db-dir", prjxray_db_dir); } } #[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)] pub struct YosysNextpnrXray { - prjxray_db_dir: Interned, + prjxray_db_dir: Interned, device: Device, - fasm_file: Interned, - fasm_file_name: Interned, - frames_file: Interned, - frames_file_name: Interned, - bit_file: Interned, - bit_file_name: Interned, + fasm_file: Interned, + fasm_file_name: Interned, + frames_file: Interned, + frames_file_name: Interned, + bit_file: Interned, + bit_file_name: Interned, } impl YosysNextpnrXray { - fn db_root(&self) -> Interned { - interned_known_utf8_path_buf_method(self.prjxray_db_dir, |prjxray_db_dir| { - prjxray_db_dir.join(self.device.xray_family()) - }) + fn db_root(&self) -> Interned { + self.prjxray_db_dir + .join(self.device.xray_family()) + .intern_deref() } - fn part_file(&self) -> Interned { - interned_known_utf8_path_buf_method(self.prjxray_db_dir, |prjxray_db_dir| { - let mut retval = prjxray_db_dir.join(self.device.xray_family()); - retval.push(self.device.xray_part()); - retval.push("part.yaml"); - retval - }) + fn part_file(&self) -> Interned { + let mut retval = self.prjxray_db_dir.join(self.device.xray_family()); + retval.push(self.device.xray_part()); + retval.push("part.yaml"); + retval.intern_deref() } } impl ExternalCommand for YosysNextpnrXray { type AdditionalArgs = YosysNextpnrXrayArgs; type AdditionalJobData = Self; + type BaseJobPosition = GetJobPositionDependencies< + ::BaseJobPosition, + >; type Dependencies = JobKindAndDependencies>; type ExternalProgram = Xcfasm; @@ -837,21 +844,22 @@ impl ExternalCommand for YosysNextpnrXray { )> { args.args_to_jobs_external_simple(params, |args, dependencies| { let YosysNextpnrXrayArgs { prjxray_db_dir } = args.additional_args; - let frames_file = dependencies.base_job().file_with_ext("frames"); - let bit_file = dependencies.base_job().file_with_ext("bit"); + let base_job = dependencies.get_job::(); + let frames_file = base_job.file_with_ext("frames"); + let bit_file = base_job.file_with_ext("bit"); Ok(Self { - prjxray_db_dir: str::intern_owned(prjxray_db_dir), + prjxray_db_dir: prjxray_db_dir.intern_deref(), device: dependencies.job.job.additional_job_data().device, fasm_file: dependencies.job.job.additional_job_data().fasm_file, fasm_file_name: dependencies.job.job.additional_job_data().fasm_file_name, frames_file, - frames_file_name: interned_known_utf8_method(frames_file, |v| { - v.file_name().expect("known to have file name") - }), + frames_file_name: frames_file + .interned_file_name() + .expect("known to have file name"), bit_file, - bit_file_name: interned_known_utf8_method(bit_file, |v| { - v.file_name().expect("known to have file name") - }), + bit_file_name: bit_file + .interned_file_name() + .expect("known to have file name"), }) }) } @@ -859,16 +867,16 @@ impl ExternalCommand for YosysNextpnrXray { fn inputs(job: &ExternalCommandJob) -> Interned<[JobItemName]> { [JobItemName::Path { path: job.additional_job_data().fasm_file, - }][..] - .intern() + }] + .intern_slice() } - fn output_paths(job: &ExternalCommandJob) -> Interned<[Interned]> { + fn output_paths(job: &ExternalCommandJob) -> Interned<[Interned]> { [ job.additional_job_data().frames_file, job.additional_job_data().bit_file, - ][..] - .intern() + ] + .intern_slice() } fn command_line_args(job: &ExternalCommandJob, args: &mut W) { @@ -879,18 +887,16 @@ impl ExternalCommand for YosysNextpnrXray { bit_file_name, .. } = job.additional_job_data(); - args.write_args([ - format_args!("--sparse"), - format_args!("--db-root={}", job_data.db_root()), - format_args!("--part={}", device.xray_part()), - format_args!("--part_file={}", job_data.part_file()), - format_args!("--fn_in={fasm_file_name}"), - format_args!("--frm_out={frames_file_name}"), - format_args!("--bit_out={bit_file_name}"), - ]); + args.write_arg("--sparse"); + args.write_long_option_eq("db-root", job_data.db_root()); + args.write_long_option_eq("part", device.xray_part()); + args.write_long_option_eq("part_file", job_data.part_file()); + args.write_long_option_eq("fn_in", fasm_file_name); + args.write_long_option_eq("frm_out", frames_file_name); + args.write_long_option_eq("bit_out", bit_file_name); } - fn current_dir(job: &ExternalCommandJob) -> Option> { + fn current_dir(job: &ExternalCommandJob) -> Option> { Some(job.output_dir()) } diff --git a/crates/fayalite/src/build/verilog.rs b/crates/fayalite/src/build/verilog.rs index 39334f2..6ecae2c 100644 --- a/crates/fayalite/src/build/verilog.rs +++ b/crates/fayalite/src/build/verilog.rs @@ -3,22 +3,25 @@ use crate::{ build::{ - CommandParams, DynJobKind, GetBaseJob, JobAndDependencies, JobArgsAndDependencies, - JobDependencies, JobItem, JobItemName, JobKind, JobKindAndDependencies, JobParams, ToArgs, - WriteArgs, + BaseJob, CommandParams, DynJobKind, GetJobPositionDependencies, GetJobPositionJob, + JobAndDependencies, JobArgsAndDependencies, JobDependencies, JobItem, JobItemName, JobKind, + JobKindAndDependencies, JobParams, ToArgs, WriteArgs, external::{ ExternalCommand, ExternalCommandJob, ExternalCommandJobKind, ExternalProgramTrait, }, - firrtl::FirrtlJobKind, - interned_known_utf8_method, interned_known_utf8_path_buf_method, + firrtl::{Firrtl, FirrtlJobKind}, }, - intern::{Intern, Interned}, + intern::{Intern, InternSlice, Interned}, util::job_server::AcquiredJob, }; use clap::Args; use eyre::{Context, bail}; use serde::{Deserialize, Serialize}; -use std::{fmt, mem}; +use std::{ + ffi::{OsStr, OsString}, + fmt, mem, + path::Path, +}; /// based on [LLVM Circt's recommended lowering options][lowering-options] /// @@ -70,7 +73,7 @@ impl VerilogDialect { #[non_exhaustive] pub struct UnadjustedVerilogArgs { #[arg(long = "firtool-extra-arg", value_name = "ARG")] - pub firtool_extra_args: Vec, + pub firtool_extra_args: Vec, /// adapt the generated Verilog for a particular toolchain #[arg(long)] pub verilog_dialect: Option, @@ -85,16 +88,14 @@ impl ToArgs for UnadjustedVerilogArgs { verilog_dialect, verilog_debug, } = *self; - args.extend( - firtool_extra_args - .iter() - .map(|arg| format!("--firtool-extra-arg={arg}")), - ); + for arg in firtool_extra_args { + args.write_long_option_eq("firtool-extra-arg", arg); + } if let Some(verilog_dialect) = verilog_dialect { - args.write_arg(format_args!("--verilog-dialect={verilog_dialect}")); + args.write_long_option_eq("verilog-dialect", verilog_dialect.as_str()); } if verilog_debug { - args.write_str_arg("--verilog-debug"); + args.write_arg("--verilog-debug"); } } } @@ -110,23 +111,23 @@ impl ExternalProgramTrait for Firtool { #[derive(Clone, PartialEq, Eq, Hash, Debug, Deserialize, Serialize)] pub struct UnadjustedVerilog { - firrtl_file: Interned, - firrtl_file_name: Interned, - unadjusted_verilog_file: Interned, - unadjusted_verilog_file_name: Interned, - firtool_extra_args: Interned<[Interned]>, + firrtl_file: Interned, + firrtl_file_name: Interned, + unadjusted_verilog_file: Interned, + unadjusted_verilog_file_name: Interned, + firtool_extra_args: Interned<[Interned]>, verilog_dialect: Option, verilog_debug: bool, } impl UnadjustedVerilog { - pub fn firrtl_file(&self) -> Interned { + pub fn firrtl_file(&self) -> Interned { self.firrtl_file } - pub fn unadjusted_verilog_file(&self) -> Interned { + pub fn unadjusted_verilog_file(&self) -> Interned { self.unadjusted_verilog_file } - pub fn firtool_extra_args(&self) -> Interned<[Interned]> { + pub fn firtool_extra_args(&self) -> Interned<[Interned]> { self.firtool_extra_args } pub fn verilog_dialect(&self) -> Option { @@ -140,6 +141,7 @@ impl UnadjustedVerilog { impl ExternalCommand for UnadjustedVerilog { type AdditionalArgs = UnadjustedVerilogArgs; type AdditionalJobData = UnadjustedVerilog; + type BaseJobPosition = GetJobPositionDependencies; type Dependencies = JobKindAndDependencies; type ExternalProgram = Firtool; @@ -165,21 +167,18 @@ impl ExternalCommand for UnadjustedVerilog { .job .job .file_with_ext("unadjusted.v"); + let firrtl_job = dependencies.get_job::(); Ok(UnadjustedVerilog { - firrtl_file: dependencies.job.job.firrtl_file(), - firrtl_file_name: interned_known_utf8_method( - dependencies.job.job.firrtl_file(), - |v| v.file_name().expect("known to have file name"), - ), + firrtl_file: firrtl_job.firrtl_file(), + firrtl_file_name: firrtl_job + .firrtl_file() + .interned_file_name() + .expect("known to have file name"), unadjusted_verilog_file, - unadjusted_verilog_file_name: interned_known_utf8_method( - unadjusted_verilog_file, - |v| v.file_name().expect("known to have file name"), - ), - firtool_extra_args: firtool_extra_args - .into_iter() - .map(str::intern_owned) - .collect(), + unadjusted_verilog_file_name: unadjusted_verilog_file + .interned_file_name() + .expect("known to have file name"), + firtool_extra_args: firtool_extra_args.into_iter().map(Interned::from).collect(), verilog_dialect, verilog_debug, }) @@ -189,12 +188,12 @@ impl ExternalCommand for UnadjustedVerilog { fn inputs(job: &ExternalCommandJob) -> Interned<[JobItemName]> { [JobItemName::Path { path: job.additional_job_data().firrtl_file, - }][..] - .intern() + }] + .intern_slice() } - fn output_paths(job: &ExternalCommandJob) -> Interned<[Interned]> { - [job.additional_job_data().unadjusted_verilog_file][..].intern() + fn output_paths(job: &ExternalCommandJob) -> Interned<[Interned]> { + [job.additional_job_data().unadjusted_verilog_file].intern_slice() } fn command_line_args(job: &ExternalCommandJob, args: &mut W) { @@ -208,18 +207,18 @@ impl ExternalCommand for UnadjustedVerilog { verilog_debug, } = *job.additional_job_data(); args.write_interned_arg(firrtl_file_name); - args.write_str_arg("-o"); + args.write_arg("-o"); args.write_interned_arg(unadjusted_verilog_file_name); if verilog_debug { - args.write_str_args(["-g", "--preserve-values=all"]); + args.write_args(["-g", "--preserve-values=all"]); } if let Some(dialect) = verilog_dialect { - args.write_str_args(dialect.firtool_extra_args().iter().copied()); + args.write_args(dialect.firtool_extra_args().iter().copied()); } args.write_interned_args(firtool_extra_args); } - fn current_dir(job: &ExternalCommandJob) -> Option> { + fn current_dir(job: &ExternalCommandJob) -> Option> { Some(job.output_dir()) } @@ -251,23 +250,23 @@ impl ToArgs for VerilogJobArgs { #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct VerilogJob { - output_dir: Interned, - unadjusted_verilog_file: Interned, - main_verilog_file: Interned, + output_dir: Interned, + unadjusted_verilog_file: Interned, + main_verilog_file: Interned, } impl VerilogJob { - pub fn output_dir(&self) -> Interned { + pub fn output_dir(&self) -> Interned { self.output_dir } - pub fn unadjusted_verilog_file(&self) -> Interned { + pub fn unadjusted_verilog_file(&self) -> Interned { self.unadjusted_verilog_file } - pub fn main_verilog_file(&self) -> Interned { + pub fn main_verilog_file(&self) -> Interned { self.main_verilog_file } #[track_caller] - pub fn unwrap_additional_files(additional_files: &JobItem) -> &[Interned] { + pub fn unwrap_additional_files(additional_files: &JobItem) -> &[Interned] { match additional_files { JobItem::DynamicPaths { paths, @@ -277,31 +276,31 @@ impl VerilogJob { } } pub fn all_verilog_files( - main_verilog_file: Interned, - additional_files: &[Interned], - ) -> eyre::Result]>> { + main_verilog_file: Interned, + additional_files: &[Interned], + ) -> eyre::Result]>> { let mut retval = Vec::with_capacity(additional_files.len().saturating_add(1)); for verilog_file in [main_verilog_file].iter().chain(additional_files) { - if !(verilog_file.ends_with(".v") || verilog_file.ends_with(".sv")) { + if !["v", "sv"] + .iter() + .any(|extension| verilog_file.extension() == Some(extension.as_ref())) + { continue; } - let verilog_file = std::path::absolute(verilog_file) - .and_then(|v| { - v.into_os_string().into_string().map_err(|_| { - std::io::Error::new(std::io::ErrorKind::Other, "path is not valid UTF-8") - }) - }) - .wrap_err_with(|| { - format!("converting {verilog_file:?} to an absolute path failed") - })?; - if verilog_file.contains(|ch: char| { - (ch != ' ' && ch != '\t' && ch.is_ascii_whitespace()) || ch == '"' - }) { + let verilog_file = std::path::absolute(verilog_file).wrap_err_with(|| { + format!("converting {verilog_file:?} to an absolute path failed") + })?; + if verilog_file + .as_os_str() + .as_encoded_bytes() + .iter() + .any(|&ch| (ch != b' ' && ch != b'\t' && ch.is_ascii_whitespace()) || ch == b'"') + { bail!("verilog file path contains characters that aren't permitted"); } - retval.push(str::intern_owned(verilog_file)); + retval.push(verilog_file.intern_deref()); } - Ok(Intern::intern_owned(retval)) + Ok(retval.intern_slice()) } } @@ -320,14 +319,15 @@ impl JobKind for VerilogJobKind { ) -> eyre::Result> { args.args_to_jobs_simple(params, |_kind, args, dependencies| { let VerilogJobArgs {} = args; + let base_job = dependencies.get_job::(); Ok(VerilogJob { - output_dir: dependencies.base_job().output_dir(), + output_dir: base_job.output_dir(), unadjusted_verilog_file: dependencies .job .job .additional_job_data() .unadjusted_verilog_file(), - main_verilog_file: dependencies.base_job().file_with_ext("v"), + main_verilog_file: base_job.file_with_ext("v"), }) }) } @@ -335,8 +335,8 @@ impl JobKind for VerilogJobKind { fn inputs(self, job: &Self::Job) -> Interned<[JobItemName]> { [JobItemName::Path { path: job.unadjusted_verilog_file, - }][..] - .intern() + }] + .intern_slice() } fn outputs(self, job: &Self::Job) -> Interned<[JobItemName]> { @@ -347,8 +347,8 @@ impl JobKind for VerilogJobKind { JobItemName::DynamicPaths { source_job_name: self.name(), }, - ][..] - .intern() + ] + .intern_slice() } fn name(self) -> Interned { @@ -384,8 +384,7 @@ impl JobKind for VerilogJobKind { ); }; input = rest; - let next_file_name = - interned_known_utf8_path_buf_method(job.output_dir, |v| v.join(next_file_name)); + let next_file_name = job.output_dir.join(next_file_name).intern_deref(); additional_outputs.push(next_file_name); (chunk, Some(next_file_name)) } else { diff --git a/crates/fayalite/src/bundle.rs b/crates/fayalite/src/bundle.rs index 55843ea..a0de189 100644 --- a/crates/fayalite/src/bundle.rs +++ b/crates/fayalite/src/bundle.rs @@ -7,7 +7,7 @@ use crate::{ ops::{ArrayLiteral, BundleLiteral, ExprPartialEq}, }, int::{Bool, DynSize}, - intern::{Intern, Interned}, + intern::{Intern, InternSlice, Interned}, sim::value::{SimValue, SimValuePartialEq, ToSimValue, ToSimValueWithType}, source_location::SourceLocation, ty::{ @@ -549,7 +549,7 @@ macro_rules! impl_tuples { type FilledBuilder = TupleBuilder<($(Expr<$T>,)*)>; fn fields(&self) -> Interned<[BundleField]> { let ($($var,)*) = self; - [$(BundleField { name: stringify!($num).intern(), flipped: false, ty: $var.canonical() }),*][..].intern() + [$(BundleField { name: stringify!($num).intern(), flipped: false, ty: $var.canonical() }),*].intern_slice() } } impl<$($T: Type,)*> TypeWithDeref for ($($T,)*) { @@ -580,7 +580,7 @@ macro_rules! impl_tuples { $(let $var = $var.to_expr();)* let ty = ($(Expr::ty($var),)*); let field_values = [$(Expr::canonical($var)),*]; - BundleLiteral::new(ty, field_values[..].intern()).to_expr() + BundleLiteral::new(ty, field_values.intern_slice()).to_expr() } } impl<$($T: Type,)*> ToExpr for TupleBuilder<($(Expr<$T>,)*)> { @@ -590,7 +590,7 @@ macro_rules! impl_tuples { let ($($var,)*) = self.0; let ty = ($(Expr::ty($var),)*); let field_values = [$(Expr::canonical($var)),*]; - BundleLiteral::new(ty, field_values[..].intern()).to_expr() + BundleLiteral::new(ty, field_values.intern_slice()).to_expr() } } impl<$($T: ToSimValueWithType,)*> ToSimValueWithType for ($($T,)*) { diff --git a/crates/fayalite/src/firrtl.rs b/crates/fayalite/src/firrtl.rs index a83f269..4bb71c0 100644 --- a/crates/fayalite/src/firrtl.rs +++ b/crates/fayalite/src/firrtl.rs @@ -49,6 +49,7 @@ use std::{ cmp::Ordering, collections::{BTreeMap, VecDeque}, error::Error, + ffi::OsString, fmt::{self, Write}, fs, hash::Hash, @@ -2452,7 +2453,7 @@ impl FileBackendTrait for &'_ mut T { pub struct FileBackend { pub dir_path: PathBuf, pub circuit_name: Option, - pub top_fir_file_stem: Option, + pub top_fir_file_stem: Option, } impl FileBackend { @@ -2501,7 +2502,7 @@ impl FileBackendTrait for FileBackend { ) -> Result<(), Self::Error> { let top_fir_file_stem = self .top_fir_file_stem - .get_or_insert_with(|| circuit_name.clone()); + .get_or_insert_with(|| circuit_name.clone().into()); self.circuit_name = Some(circuit_name); let mut path = self.dir_path.join(top_fir_file_stem); if let Some(parent) = path.parent().filter(|v| !v.as_os_str().is_empty()) { @@ -2777,7 +2778,7 @@ impl ToArgs for ExportOptions { __private: ExportOptionsPrivate(()), } = *self; if !simplify_memories { - args.write_str_arg("--no-simplify-memories"); + args.write_arg("--no-simplify-memories"); } let simplify_enums = simplify_enums.map(|v| { clap::ValueEnum::to_possible_value(&v).expect("there are no skipped variants") @@ -2786,7 +2787,7 @@ impl ToArgs for ExportOptions { None => OptionSimplifyEnumsKindValueParser::NONE_NAME, Some(v) => v.get_name(), }; - args.write_arg(format_args!("--simplify-enums={simplify_enums}")); + args.write_long_option_eq("simplify-enums", simplify_enums); } } diff --git a/crates/fayalite/src/int/uint_in_range.rs b/crates/fayalite/src/int/uint_in_range.rs index 5ddd38c..39f4051 100644 --- a/crates/fayalite/src/int/uint_in_range.rs +++ b/crates/fayalite/src/int/uint_in_range.rs @@ -8,7 +8,7 @@ use crate::{ ops::{ExprCastTo, ExprPartialEq, ExprPartialOrd}, }, int::{Bool, DynSize, KnownSize, Size, SizeType, UInt, UIntType}, - intern::{Intern, Interned}, + intern::{Intern, InternSlice, Interned}, phantom_const::PhantomConst, sim::value::{SimValue, SimValuePartialEq, ToSimValueWithType}, source_location::SourceLocation, @@ -112,8 +112,8 @@ impl BundleType for UIntInRangeMaskType { flipped: false, ty: range.canonical(), }, - ][..] - .intern() + ] + .intern_slice() } } @@ -409,8 +409,8 @@ macro_rules! define_uint_in_range_type { flipped: false, ty: range.canonical(), }, - ][..] - .intern() + ] + .intern_slice() } } diff --git a/crates/fayalite/src/intern.rs b/crates/fayalite/src/intern.rs index 9d57146..b68140b 100644 --- a/crates/fayalite/src/intern.rs +++ b/crates/fayalite/src/intern.rs @@ -9,13 +9,13 @@ use std::{ any::{Any, TypeId}, borrow::{Borrow, Cow}, cmp::Ordering, - ffi::OsStr, + ffi::{OsStr, OsString}, fmt, hash::{BuildHasher, Hash, Hasher}, iter::FusedIterator, marker::PhantomData, ops::Deref, - path::Path, + path::{Path, PathBuf}, sync::{Mutex, RwLock}, }; @@ -289,15 +289,266 @@ impl InternedCompare for BitSlice { } } -impl InternedCompare for str { - type InternedCompareKey = PtrEqWithMetadata; - fn interned_compare_key_ref(this: &Self) -> Self::InternedCompareKey { - PtrEqWithMetadata(this) +/// Safety: `as_bytes` and `from_bytes_unchecked` must return the same pointer as the input. +/// all values returned by `as_bytes` must be valid to pass to `from_bytes_unchecked`. +/// `into_bytes` must return the exact same thing as `as_bytes`. +/// `Interned` must contain the exact same references as `Interned<[u8]>`, +/// so they can be safely interconverted without needing re-interning. +unsafe trait InternStrLike: ToOwned { + fn as_bytes(this: &Self) -> &[u8]; + fn into_bytes(this: Self::Owned) -> Vec; + /// Safety: `bytes` must be a valid sequence of bytes for this type. All UTF-8 sequences are valid. + unsafe fn from_bytes_unchecked(bytes: &[u8]) -> &Self; +} + +macro_rules! impl_intern_str_like { + ($ty:ty, owned = $Owned:ty) => { + impl InternedCompare for $ty { + type InternedCompareKey = PtrEqWithMetadata<[u8]>; + fn interned_compare_key_ref(this: &Self) -> Self::InternedCompareKey { + PtrEqWithMetadata(InternStrLike::as_bytes(this)) + } + } + impl Intern for $ty { + fn intern(&self) -> Interned { + Self::intern_cow(Cow::Borrowed(self)) + } + fn intern_cow(this: Cow<'_, Self>) -> Interned { + Interned::cast_unchecked( + <[u8]>::intern_cow(match this { + Cow::Borrowed(v) => Cow::Borrowed(::as_bytes(v)), + Cow::Owned(v) => { + // verify $Owned is correct + let v: $Owned = v; + Cow::Owned(::into_bytes(v)) + } + }), + // Safety: guaranteed safe because we got the bytes from `as_bytes`/`into_bytes` + |v| unsafe { ::from_bytes_unchecked(v) }, + ) + } + } + impl Default for Interned<$ty> { + fn default() -> Self { + // Safety: safe because the empty sequence is valid UTF-8 + unsafe { <$ty as InternStrLike>::from_bytes_unchecked(&[]) }.intern() + } + } + impl<'de> Deserialize<'de> for Interned<$ty> { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + Cow::<'de, $ty>::deserialize(deserializer).map(Intern::intern_cow) + } + } + impl From<$Owned> for Interned<$ty> { + fn from(v: $Owned) -> Self { + v.intern_deref() + } + } + impl From> for $Owned { + fn from(v: Interned<$ty>) -> Self { + Interned::into_inner(v).into() + } + } + impl From> for Box<$ty> { + fn from(v: Interned<$ty>) -> Self { + Interned::into_inner(v).into() + } + } + }; +} + +// Safety: satisfies `InternStrLike`'s requirements where the valid sequences for `from_bytes_unchecked` matches `str` +unsafe impl InternStrLike for str { + fn as_bytes(this: &Self) -> &[u8] { + this.as_bytes() + } + fn into_bytes(this: Self::Owned) -> Vec { + this.into_bytes() + } + unsafe fn from_bytes_unchecked(bytes: &[u8]) -> &Self { + // Safety: `bytes` is guaranteed UTF-8 by the caller + unsafe { str::from_utf8_unchecked(bytes) } + } +} + +impl_intern_str_like!(str, owned = String); + +// Safety: satisfies `InternStrLike`'s requirements where the valid sequences for `from_bytes_unchecked` matches `OsStr` +unsafe impl InternStrLike for OsStr { + fn as_bytes(this: &Self) -> &[u8] { + this.as_encoded_bytes() + } + fn into_bytes(this: Self::Owned) -> Vec { + this.into_encoded_bytes() + } + unsafe fn from_bytes_unchecked(bytes: &[u8]) -> &Self { + // Safety: `bytes` is guaranteed valid for `OsStr` by the caller + unsafe { OsStr::from_encoded_bytes_unchecked(bytes) } + } +} + +impl_intern_str_like!(OsStr, owned = OsString); + +// Safety: satisfies `InternStrLike`'s requirements where the valid sequences for `from_bytes_unchecked` matches `OsStr` +unsafe impl InternStrLike for Path { + fn as_bytes(this: &Self) -> &[u8] { + this.as_os_str().as_encoded_bytes() + } + fn into_bytes(this: Self::Owned) -> Vec { + this.into_os_string().into_encoded_bytes() + } + unsafe fn from_bytes_unchecked(bytes: &[u8]) -> &Self { + // Safety: `bytes` is guaranteed valid for `OsStr` by the caller + unsafe { Path::new(OsStr::from_encoded_bytes_unchecked(bytes)) } + } +} + +impl_intern_str_like!(Path, owned = PathBuf); + +impl Interned { + pub fn from_utf8(v: Interned<[u8]>) -> Result { + Interned::try_cast_unchecked(v, str::from_utf8) + } + pub fn as_interned_bytes(self) -> Interned<[u8]> { + Interned::cast_unchecked(self, str::as_bytes) + } + pub fn as_interned_os_str(self) -> Interned { + Interned::cast_unchecked(self, AsRef::as_ref) + } + pub fn as_interned_path(self) -> Interned { + Interned::cast_unchecked(self, AsRef::as_ref) + } +} + +impl From> for Interned { + fn from(value: Interned) -> Self { + value.as_interned_os_str() + } +} + +impl From> for Interned { + fn from(value: Interned) -> Self { + value.as_interned_path() + } +} + +impl Interned { + pub fn as_interned_encoded_bytes(self) -> Interned<[u8]> { + Interned::cast_unchecked(self, OsStr::as_encoded_bytes) + } + pub fn to_interned_str(self) -> Option> { + Interned::try_cast_unchecked(self, |v| v.to_str().ok_or(())).ok() + } + pub fn display(self) -> std::ffi::os_str::Display<'static> { + Self::into_inner(self).display() + } + pub fn as_interned_path(self) -> Interned { + Interned::cast_unchecked(self, AsRef::as_ref) + } +} + +impl From> for Interned { + fn from(value: Interned) -> Self { + value.as_interned_path() + } +} + +impl Interned { + pub fn as_interned_os_str(self) -> Interned { + Interned::cast_unchecked(self, AsRef::as_ref) + } + pub fn to_interned_str(self) -> Option> { + Interned::try_cast_unchecked(self, |v| v.to_str().ok_or(())).ok() + } + pub fn display(self) -> std::path::Display<'static> { + Self::into_inner(self).display() + } + pub fn interned_file_name(self) -> Option> { + Some(self.file_name()?.intern()) + } +} + +impl From> for Interned { + fn from(value: Interned) -> Self { + value.as_interned_os_str() + } +} + +pub trait InternSlice: Sized { + type Element: 'static + Send + Sync + Clone + Hash + Eq; + fn intern_slice(self) -> Interned<[Self::Element]>; +} + +impl InternSlice for Box<[T]> { + type Element = T; + fn intern_slice(self) -> Interned<[Self::Element]> { + self.into_vec().intern_slice() + } +} + +impl InternSlice for Vec { + type Element = T; + fn intern_slice(self) -> Interned<[Self::Element]> { + self.intern_deref() + } +} + +impl InternSlice for &'_ [T] { + type Element = T; + fn intern_slice(self) -> Interned<[Self::Element]> { + self.intern() + } +} + +impl InternSlice for &'_ mut [T] { + type Element = T; + fn intern_slice(self) -> Interned<[Self::Element]> { + self.intern() + } +} + +impl InternSlice for [T; N] { + type Element = T; + fn intern_slice(self) -> Interned<[Self::Element]> { + (&self).intern_slice() + } +} + +impl InternSlice for Box<[T; N]> { + type Element = T; + fn intern_slice(self) -> Interned<[Self::Element]> { + let this: Box<[T]> = self; + this.intern_slice() + } +} + +impl InternSlice for &'_ [T; N] { + type Element = T; + fn intern_slice(self) -> Interned<[Self::Element]> { + let this: &[T] = self; + this.intern() + } +} + +impl InternSlice for &'_ mut [T; N] { + type Element = T; + fn intern_slice(self) -> Interned<[Self::Element]> { + let this: &[T] = self; + this.intern() } } pub trait Intern: Any + Send + Sync { fn intern(&self) -> Interned; + fn intern_deref(self) -> Interned + where + Self: Sized + Deref>, + { + Self::Target::intern_owned(self) + } fn intern_sized(self) -> Interned where Self: Clone, @@ -318,6 +569,30 @@ pub trait Intern: Any + Send + Sync { } } +impl From> for Interned { + fn from(value: Cow<'_, T>) -> Self { + Intern::intern_cow(value) + } +} + +impl From<&'_ T> for Interned { + fn from(value: &'_ T) -> Self { + Intern::intern(value) + } +} + +impl From for Interned { + fn from(value: T) -> Self { + Intern::intern_sized(value) + } +} + +impl From> for Cow<'_, T> { + fn from(value: Interned) -> Self { + Cow::Borrowed(Interned::into_inner(value)) + } +} + struct InternerState { table: HashTable<&'static T>, hasher: DefaultBuildHasher, @@ -383,12 +658,6 @@ impl Interner { } } -impl Interner { - fn intern_str(&self, value: Cow<'_, str>) -> Interned { - self.intern(|value| value.into_owned().leak(), value) - } -} - pub struct Interned { inner: &'static T, } @@ -418,9 +687,9 @@ forward_fmt_trait!(Pointer); forward_fmt_trait!(UpperExp); forward_fmt_trait!(UpperHex); -impl AsRef for Interned { - fn as_ref(&self) -> &T { - self +impl, U: ?Sized> AsRef for Interned { + fn as_ref(&self) -> &U { + T::as_ref(self) } } @@ -498,19 +767,25 @@ where String: FromIterator, { fn from_iter>(iter: T) -> Self { - str::intern_owned(FromIterator::from_iter(iter)) + String::from_iter(iter).intern_deref() } } -impl AsRef for Interned { - fn as_ref(&self) -> &OsStr { - str::as_ref(self) +impl FromIterator for Interned +where + PathBuf: FromIterator, +{ + fn from_iter>(iter: T) -> Self { + PathBuf::from_iter(iter).intern_deref() } } -impl AsRef for Interned { - fn as_ref(&self) -> &Path { - str::as_ref(self) +impl FromIterator for Interned +where + OsString: FromIterator, +{ + fn from_iter>(iter: T) -> Self { + OsString::from_iter(iter).intern_deref() } } @@ -550,24 +825,12 @@ impl From> for Box<[T]> { } } -impl From> for String { - fn from(value: Interned) -> Self { - String::from(&*value) - } -} - impl Default for Interned<[I]> where [I]: Intern, { fn default() -> Self { - [][..].intern() - } -} - -impl Default for Interned { - fn default() -> Self { - "".intern() + Intern::intern(&[]) } } @@ -698,15 +961,6 @@ impl<'de> Deserialize<'de> for Interned { } } -impl<'de> Deserialize<'de> for Interned { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - String::deserialize(deserializer).map(Intern::intern_owned) - } -} - impl Intern for T { fn intern(&self) -> Interned { Self::intern_cow(Cow::Borrowed(self)) @@ -767,26 +1021,6 @@ impl Intern for BitSlice { } } -impl Intern for str { - fn intern(&self) -> Interned { - Self::intern_cow(Cow::Borrowed(self)) - } - - fn intern_owned(this: ::Owned) -> Interned - where - Self: ToOwned, - { - Self::intern_cow(Cow::Owned(this)) - } - - fn intern_cow(this: Cow<'_, Self>) -> Interned - where - Self: ToOwned, - { - Interner::get().intern_str(this) - } -} - pub trait MemoizeGeneric: 'static + Send + Sync + Hash + Eq + Copy { type InputRef<'a>: 'a + Send + Sync + Hash + Copy; type InputOwned: 'static + Send + Sync; diff --git a/crates/fayalite/src/module/transform/simplify_enums.rs b/crates/fayalite/src/module/transform/simplify_enums.rs index 0839881..bd5f7d5 100644 --- a/crates/fayalite/src/module/transform/simplify_enums.rs +++ b/crates/fayalite/src/module/transform/simplify_enums.rs @@ -10,7 +10,7 @@ use crate::{ }, hdl, int::UInt, - intern::{Intern, Interned, Memoize}, + intern::{Intern, InternSlice, Interned, Memoize}, memory::{DynPortType, Mem, MemPort}, module::{ Block, Id, Module, NameId, ScopedNameId, Stmt, StmtConnect, StmtIf, StmtMatch, StmtWire, @@ -620,7 +620,7 @@ fn match_int_tag( block, Block { memories: Default::default(), - stmts: [Stmt::from(retval)][..].intern(), + stmts: [Stmt::from(retval)].intern_slice(), }, ], }; diff --git a/crates/fayalite/src/sim.rs b/crates/fayalite/src/sim.rs index 1717aec..fabe6d8 100644 --- a/crates/fayalite/src/sim.rs +++ b/crates/fayalite/src/sim.rs @@ -12,7 +12,9 @@ use crate::{ }, }, int::BoolOrIntType, - intern::{Intern, Interned, InternedCompare, PtrEqWithTypeId, SupportsPtrEqWithTypeId}, + intern::{ + Intern, InternSlice, Interned, InternedCompare, PtrEqWithTypeId, SupportsPtrEqWithTypeId, + }, module::{ ModuleIO, transform::visit::{Fold, Folder, Visit, Visitor}, @@ -262,7 +264,7 @@ impl_trace_decl! { }), Instance(TraceInstance { fn children(self) -> _ { - [self.instance_io.into(), self.module.into()][..].intern() + [self.instance_io.into(), self.module.into()].intern_slice() } name: Interned, instance_io: TraceBundle, @@ -282,7 +284,7 @@ impl_trace_decl! { }), MemPort(TraceMemPort { fn children(self) -> _ { - [self.bundle.into()][..].intern() + [self.bundle.into()].intern_slice() } name: Interned, bundle: TraceBundle, @@ -290,7 +292,7 @@ impl_trace_decl! { }), Wire(TraceWire { fn children(self) -> _ { - [*self.child][..].intern() + [*self.child].intern_slice() } name: Interned, child: Interned, @@ -298,7 +300,7 @@ impl_trace_decl! { }), Reg(TraceReg { fn children(self) -> _ { - [*self.child][..].intern() + [*self.child].intern_slice() } name: Interned, child: Interned, @@ -306,7 +308,7 @@ impl_trace_decl! { }), ModuleIO(TraceModuleIO { fn children(self) -> _ { - [*self.child][..].intern() + [*self.child].intern_slice() } name: Interned, child: Interned, diff --git a/crates/fayalite/src/sim/compiler.rs b/crates/fayalite/src/sim/compiler.rs index 2b291be..fbede7b 100644 --- a/crates/fayalite/src/sim/compiler.rs +++ b/crates/fayalite/src/sim/compiler.rs @@ -14,7 +14,7 @@ use crate::{ }, }, int::BoolOrIntType, - intern::{Intern, Interned, Memoize}, + intern::{Intern, InternSlice, Interned, Memoize}, memory::PortKind, module::{ AnnotatedModuleIO, Block, ExternModuleBody, Id, InstantiatedModule, ModuleBody, NameId, @@ -3950,8 +3950,8 @@ impl Compiler { [Cond { body: CondBody::IfTrue { cond }, source_location: reg.source_location(), - }][..] - .intern(), + }] + .intern_slice(), lhs, init, reg.source_location(), diff --git a/crates/fayalite/src/testing.rs b/crates/fayalite/src/testing.rs index 47c7cfa..30a8243 100644 --- a/crates/fayalite/src/testing.rs +++ b/crates/fayalite/src/testing.rs @@ -14,7 +14,6 @@ use crate::{ module::Module, util::HashMap, }; -use eyre::eyre; use serde::Deserialize; use std::{ fmt::Write, @@ -107,12 +106,7 @@ fn make_assert_formal_args( ) -> eyre::Result>> { let args = JobKindAndArgs { kind: BaseJobKind, - args: BaseJobArgs::from_output_dir_and_env( - get_assert_formal_target_path(&test_name) - .into_os_string() - .into_string() - .map_err(|_| eyre!("path is not valid UTF-8"))?, - ), + args: BaseJobArgs::from_output_dir_and_env(get_assert_formal_target_path(&test_name)), }; let dependencies = JobArgsAndDependencies { args, diff --git a/crates/fayalite/src/util.rs b/crates/fayalite/src/util.rs index 23a6852..cc8f8b0 100644 --- a/crates/fayalite/src/util.rs +++ b/crates/fayalite/src/util.rs @@ -38,9 +38,9 @@ pub(crate) use misc::chain; pub use misc::{ BitSliceWriteWithBase, DebugAsDisplay, DebugAsRawString, MakeMutSlice, RcWriter, SerdeJsonEscapeIf, SerdeJsonEscapeIfFormatter, SerdeJsonEscapeIfTest, - SerdeJsonEscapeIfTestResult, interned_bit, iter_eq_by, serialize_to_json_ascii, - serialize_to_json_ascii_pretty, serialize_to_json_ascii_pretty_with_indent, slice_range, - try_slice_range, + SerdeJsonEscapeIfTestResult, interned_bit, iter_eq_by, os_str_strip_prefix, + os_str_strip_suffix, serialize_to_json_ascii, serialize_to_json_ascii_pretty, + serialize_to_json_ascii_pretty_with_indent, slice_range, try_slice_range, }; pub mod job_server; diff --git a/crates/fayalite/src/util/misc.rs b/crates/fayalite/src/util/misc.rs index 6c9acee..bd5c53f 100644 --- a/crates/fayalite/src/util/misc.rs +++ b/crates/fayalite/src/util/misc.rs @@ -4,6 +4,7 @@ use crate::intern::{Intern, Interned}; use bitvec::{bits, order::Lsb0, slice::BitSlice, view::BitView}; use std::{ cell::Cell, + ffi::OsStr, fmt::{self, Debug, Write}, io, ops::{Bound, Range, RangeBounds}, @@ -564,3 +565,23 @@ pub fn serialize_to_json_ascii_pretty_with_indent( serde_json::ser::PrettyFormatter::with_indent(indent.as_bytes()), ) } + +pub fn os_str_strip_prefix<'a>(os_str: &'a OsStr, prefix: impl AsRef) -> Option<&'a OsStr> { + os_str + .as_encoded_bytes() + .strip_prefix(prefix.as_ref().as_bytes()) + .map(|bytes| { + // Safety: we removed a UTF-8 prefix so bytes starts with a valid boundary + unsafe { OsStr::from_encoded_bytes_unchecked(bytes) } + }) +} + +pub fn os_str_strip_suffix<'a>(os_str: &'a OsStr, suffix: impl AsRef) -> Option<&'a OsStr> { + os_str + .as_encoded_bytes() + .strip_suffix(suffix.as_ref().as_bytes()) + .map(|bytes| { + // Safety: we removed a UTF-8 suffix so bytes ends with a valid boundary + unsafe { OsStr::from_encoded_bytes_unchecked(bytes) } + }) +} diff --git a/crates/fayalite/tests/ui/simvalue_is_not_internable.stderr b/crates/fayalite/tests/ui/simvalue_is_not_internable.stderr index 03c62bf..1fd291c 100644 --- a/crates/fayalite/tests/ui/simvalue_is_not_internable.stderr +++ b/crates/fayalite/tests/ui/simvalue_is_not_internable.stderr @@ -156,7 +156,7 @@ note: required by a bound in `intern_sized` | | pub trait Intern: Any + Send + Sync { | ^^^^ required by this bound in `Intern::intern_sized` - | fn intern(&self) -> Interned; +... | fn intern_sized(self) -> Interned | ------------ required by a bound in this associated function help: consider dereferencing here @@ -188,7 +188,7 @@ note: required by a bound in `intern_sized` | | pub trait Intern: Any + Send + Sync { | ^^^^ required by this bound in `Intern::intern_sized` - | fn intern(&self) -> Interned; +... | fn intern_sized(self) -> Interned | ------------ required by a bound in this associated function help: consider dereferencing here @@ -255,7 +255,7 @@ note: required by a bound in `intern_sized` | | pub trait Intern: Any + Send + Sync { | ^^^^ required by this bound in `Intern::intern_sized` - | fn intern(&self) -> Interned; +... | fn intern_sized(self) -> Interned | ------------ required by a bound in this associated function help: consider dereferencing here