reduce parallelism to fit within the number of available cpus even when running sby in prove mode (which likes to run 2 smt solvers in parallel)
	
		
			
	
		
	
	
		
	
		
			Some checks failed
		
		
	
	
		
			
				
	
				/ test (push) Has been cancelled
				
			
		
		
	
	
				
					
				
			
		
			Some checks failed
		
		
	
	/ test (push) Has been cancelled
				
			This commit is contained in:
		
							parent
							
								
									487af07154
								
							
						
					
					
						commit
						b7f1101164
					
				
					 1 changed files with 162 additions and 20 deletions
				
			
		|  | @ -17,8 +17,10 @@ use std::{ | |||
|     ffi::OsString, | ||||
|     fmt::{self, Write}, | ||||
|     fs, io, mem, | ||||
|     num::NonZeroUsize, | ||||
|     path::{Path, PathBuf}, | ||||
|     process, | ||||
|     sync::{Condvar, Mutex, OnceLock}, | ||||
| }; | ||||
| use tempfile::TempDir; | ||||
| 
 | ||||
|  | @ -46,9 +48,109 @@ impl From<io::Error> for CliError { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug)] | ||||
| pub struct AcquiredJob { | ||||
|     job_count: NonZeroUsize, | ||||
| } | ||||
| 
 | ||||
| impl Drop for AcquiredJob { | ||||
|     fn drop(&mut self) { | ||||
|         Self::change_job_count(Some(self.job_count), None); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl AcquiredJob { | ||||
|     pub fn max_available_job_count() -> NonZeroUsize { | ||||
|         static RETVAL: OnceLock<NonZeroUsize> = OnceLock::new(); | ||||
|         *RETVAL.get_or_init(|| { | ||||
|             std::thread::available_parallelism().unwrap_or(NonZeroUsize::new(1).unwrap()) | ||||
|         }) | ||||
|     } | ||||
|     fn change_job_count(released: Option<NonZeroUsize>, acquired: Option<NonZeroUsize>) { | ||||
|         static AVAILABLE_JOB_COUNT: OnceLock<Mutex<usize>> = OnceLock::new(); | ||||
|         static COND_VAR: Condvar = Condvar::new(); | ||||
|         let mut available_job_count_lock = AVAILABLE_JOB_COUNT | ||||
|             .get_or_init(|| Mutex::new(Self::max_available_job_count().get())) | ||||
|             .lock() | ||||
|             .unwrap(); | ||||
|         if let Some(released) = released { | ||||
|             *available_job_count_lock = available_job_count_lock | ||||
|                 .checked_add(released.get()) | ||||
|                 .expect("tried to release too many jobs"); | ||||
|             COND_VAR.notify_all(); | ||||
|         } | ||||
|         if let Some(acquired) = acquired { | ||||
|             loop { | ||||
|                 match available_job_count_lock.checked_sub(acquired.get()) { | ||||
|                     Some(jobs_left) => { | ||||
|                         *available_job_count_lock = jobs_left; | ||||
|                         break; | ||||
|                     } | ||||
|                     None => { | ||||
|                         available_job_count_lock = COND_VAR.wait(available_job_count_lock).unwrap() | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     pub fn job_count(&self) -> NonZeroUsize { | ||||
|         self.job_count | ||||
|     } | ||||
|     pub fn increase_job_count<R>( | ||||
|         &mut self, | ||||
|         new_minimum_count: NonZeroUsize, | ||||
|         f: impl FnOnce(&mut AcquiredJob) -> R, | ||||
|     ) -> R { | ||||
|         if new_minimum_count <= self.job_count { | ||||
|             return f(self); | ||||
|         } | ||||
|         struct ReleaseOnDrop<'a> { | ||||
|             acquired_job: &'a mut AcquiredJob, | ||||
|             old_job_count: NonZeroUsize, | ||||
|         } | ||||
|         impl Drop for ReleaseOnDrop<'_> { | ||||
|             fn drop(&mut self) { | ||||
|                 AcquiredJob::change_job_count( | ||||
|                     NonZeroUsize::new(self.acquired_job.job_count.get() - self.old_job_count.get()), | ||||
|                     None, | ||||
|                 ); | ||||
|                 self.acquired_job.job_count = self.old_job_count; | ||||
|             } | ||||
|         } | ||||
|         let release_on_drop = ReleaseOnDrop { | ||||
|             old_job_count: self.job_count, | ||||
|             acquired_job: self, | ||||
|         }; | ||||
|         // release our current jobs when acquiring new jobs to avoid deadlock
 | ||||
|         Self::change_job_count(Some(release_on_drop.old_job_count), Some(new_minimum_count)); | ||||
|         release_on_drop.acquired_job.job_count = new_minimum_count; | ||||
|         let retval = f(release_on_drop.acquired_job); | ||||
|         drop(release_on_drop); | ||||
|         retval | ||||
|     } | ||||
|     pub fn acquire_jobs(job_count: NonZeroUsize) -> Self { | ||||
|         Self::change_job_count(None, Some(job_count)); | ||||
|         Self { job_count } | ||||
|     } | ||||
|     pub fn run_command<R>( | ||||
|         &mut self, | ||||
|         mut cmd: std::process::Command, | ||||
|         f: impl FnOnce(&mut std::process::Command) -> std::io::Result<R>, | ||||
|     ) -> std::io::Result<R> { | ||||
|         // TODO: if we implement a make job server, add the proper env vars to cmd
 | ||||
|         f(&mut cmd) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub trait RunPhase<Arg> { | ||||
|     type Output; | ||||
|     fn run(&self, arg: Arg) -> Result<Self::Output>; | ||||
|     fn run(&self, arg: Arg) -> Result<Self::Output> { | ||||
|         self.run_with_job( | ||||
|             arg, | ||||
|             &mut AcquiredJob::acquire_jobs(NonZeroUsize::new(1).unwrap()), | ||||
|         ) | ||||
|     } | ||||
|     fn run_with_job(&self, arg: Arg, acquired_job: &mut AcquiredJob) -> Result<Self::Output>; | ||||
| } | ||||
| 
 | ||||
| #[derive(Parser, Debug, Clone)] | ||||
|  | @ -93,6 +195,7 @@ impl BaseArgs { | |||
|     /// handles possibly redirecting the command's output for Rust tests
 | ||||
|     pub fn run_external_command( | ||||
|         &self, | ||||
|         _acquired_job: &mut AcquiredJob, | ||||
|         mut command: process::Command, | ||||
|     ) -> io::Result<process::ExitStatus> { | ||||
|         if self.redirect_output_for_rust_test { | ||||
|  | @ -150,7 +253,11 @@ impl FirrtlOutput { | |||
| } | ||||
| 
 | ||||
| impl FirrtlArgs { | ||||
|     fn run_impl(&self, top_module: Module<Bundle>) -> Result<FirrtlOutput> { | ||||
|     fn run_impl( | ||||
|         &self, | ||||
|         top_module: Module<Bundle>, | ||||
|         _acquired_job: &mut AcquiredJob, | ||||
|     ) -> Result<FirrtlOutput> { | ||||
|         let (file_backend, temp_dir) = self.base.make_firrtl_file_backend()?; | ||||
|         let firrtl::FileBackend { | ||||
|             top_fir_file_stem, | ||||
|  | @ -170,15 +277,23 @@ impl FirrtlArgs { | |||
| 
 | ||||
| impl<T: BundleType> RunPhase<Module<T>> for FirrtlArgs { | ||||
|     type Output = FirrtlOutput; | ||||
|     fn run(&self, top_module: Module<T>) -> Result<Self::Output> { | ||||
|         self.run_impl(top_module.canonical()) | ||||
|     fn run_with_job( | ||||
|         &self, | ||||
|         top_module: Module<T>, | ||||
|         acquired_job: &mut AcquiredJob, | ||||
|     ) -> Result<Self::Output> { | ||||
|         self.run_impl(top_module.canonical(), acquired_job) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl<T: BundleType> RunPhase<Interned<Module<T>>> for FirrtlArgs { | ||||
|     type Output = FirrtlOutput; | ||||
|     fn run(&self, top_module: Interned<Module<T>>) -> Result<Self::Output> { | ||||
|         self.run(*top_module) | ||||
|     fn run_with_job( | ||||
|         &self, | ||||
|         top_module: Interned<Module<T>>, | ||||
|         acquired_job: &mut AcquiredJob, | ||||
|     ) -> Result<Self::Output> { | ||||
|         self.run_with_job(*top_module, acquired_job) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | @ -298,7 +413,11 @@ impl VerilogArgs { | |||
|         } | ||||
|         Ok(output) | ||||
|     } | ||||
|     fn run_impl(&self, firrtl_output: FirrtlOutput) -> Result<VerilogOutput> { | ||||
|     fn run_impl( | ||||
|         &self, | ||||
|         firrtl_output: FirrtlOutput, | ||||
|         acquired_job: &mut AcquiredJob, | ||||
|     ) -> Result<VerilogOutput> { | ||||
|         let Self { | ||||
|             firrtl, | ||||
|             firtool, | ||||
|  | @ -323,7 +442,7 @@ impl VerilogArgs { | |||
|         } | ||||
|         cmd.args(firtool_extra_args); | ||||
|         cmd.current_dir(&output.firrtl.output_dir); | ||||
|         let status = firrtl.base.run_external_command(cmd)?; | ||||
|         let status = firrtl.base.run_external_command(acquired_job, cmd)?; | ||||
|         if status.success() { | ||||
|             self.process_unadjusted_verilog_file(output) | ||||
|         } else { | ||||
|  | @ -340,9 +459,9 @@ where | |||
|     FirrtlArgs: RunPhase<Arg, Output = FirrtlOutput>, | ||||
| { | ||||
|     type Output = VerilogOutput; | ||||
|     fn run(&self, arg: Arg) -> Result<Self::Output> { | ||||
|         let firrtl_output = self.firrtl.run(arg)?; | ||||
|         self.run_impl(firrtl_output) | ||||
|     fn run_with_job(&self, arg: Arg, acquired_job: &mut AcquiredJob) -> Result<Self::Output> { | ||||
|         let firrtl_output = self.firrtl.run_with_job(arg, acquired_job)?; | ||||
|         self.run_impl(firrtl_output, acquired_job) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | @ -365,6 +484,14 @@ impl FormalMode { | |||
|             FormalMode::Cover => "cover", | ||||
|         } | ||||
|     } | ||||
|     fn needs_extra_job(self) -> bool { | ||||
|         match self { | ||||
|             FormalMode::BMC => false, | ||||
|             FormalMode::Prove => true, | ||||
|             FormalMode::Live => false, | ||||
|             FormalMode::Cover => false, | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl fmt::Display for FormalMode { | ||||
|  | @ -508,7 +635,11 @@ impl FormalArgs { | |||
|         writeln!(retval, "prep -top {top_module}").unwrap(); | ||||
|         Ok(retval) | ||||
|     } | ||||
|     fn run_impl(&self, verilog_output: VerilogOutput) -> Result<FormalOutput> { | ||||
|     fn run_impl( | ||||
|         &self, | ||||
|         verilog_output: VerilogOutput, | ||||
|         acquired_job: &mut AcquiredJob, | ||||
|     ) -> Result<FormalOutput> { | ||||
|         let output = FormalOutput { | ||||
|             verilog: verilog_output, | ||||
|         }; | ||||
|  | @ -519,7 +650,18 @@ impl FormalArgs { | |||
|         cmd.arg(sby_file.file_name().unwrap()); | ||||
|         cmd.args(&self.sby_extra_args); | ||||
|         cmd.current_dir(&output.verilog.firrtl.output_dir); | ||||
|         let status = self.verilog.firrtl.base.run_external_command(cmd)?; | ||||
|         let new_minimum_count = if self.mode.needs_extra_job() { | ||||
|             NonZeroUsize::new(2).unwrap() | ||||
|         } else { | ||||
|             NonZeroUsize::new(1).unwrap() | ||||
|         }; | ||||
|         let new_minimum_count = AcquiredJob::max_available_job_count().min(new_minimum_count); | ||||
|         let status = acquired_job.increase_job_count(new_minimum_count, |acquired_job| { | ||||
|             self.verilog | ||||
|                 .firrtl | ||||
|                 .base | ||||
|                 .run_external_command(acquired_job, cmd) | ||||
|         })?; | ||||
|         if status.success() { | ||||
|             Ok(output) | ||||
|         } else { | ||||
|  | @ -536,9 +678,9 @@ where | |||
|     VerilogArgs: RunPhase<Arg, Output = VerilogOutput>, | ||||
| { | ||||
|     type Output = FormalOutput; | ||||
|     fn run(&self, arg: Arg) -> Result<Self::Output> { | ||||
|         let verilog_output = self.verilog.run(arg)?; | ||||
|         self.run_impl(verilog_output) | ||||
|     fn run_with_job(&self, arg: Arg, acquired_job: &mut AcquiredJob) -> Result<Self::Output> { | ||||
|         let verilog_output = self.verilog.run_with_job(arg, acquired_job)?; | ||||
|         self.run_impl(verilog_output, acquired_job) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | @ -627,16 +769,16 @@ where | |||
|     FirrtlArgs: RunPhase<T, Output = FirrtlOutput>, | ||||
| { | ||||
|     type Output = (); | ||||
|     fn run(&self, arg: T) -> Result<Self::Output> { | ||||
|     fn run_with_job(&self, arg: T, acquired_job: &mut AcquiredJob) -> Result<Self::Output> { | ||||
|         match &self.subcommand { | ||||
|             CliCommand::Firrtl(c) => { | ||||
|                 c.run(arg)?; | ||||
|                 c.run_with_job(arg, acquired_job)?; | ||||
|             } | ||||
|             CliCommand::Verilog(c) => { | ||||
|                 c.run(arg)?; | ||||
|                 c.run_with_job(arg, acquired_job)?; | ||||
|             } | ||||
|             CliCommand::Formal(c) => { | ||||
|                 c.run(arg)?; | ||||
|                 c.run_with_job(arg, acquired_job)?; | ||||
|             } | ||||
|         } | ||||
|         Ok(()) | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue