Skip to content

Commit 14336e3

Browse files
cap thread pool size at # of harnesses
* thread pools are now dynamically sized * that size is set based on a const var capped by the # of harnesses
1 parent a50fbaa commit 14336e3

File tree

2 files changed

+37
-18
lines changed

2 files changed

+37
-18
lines changed

kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ use stable_mir::CrateDef;
4343
use stable_mir::mir::mono::{Instance, MonoItem};
4444
use stable_mir::rustc_internal;
4545
use std::any::Any;
46+
use std::cmp::min;
4647
use std::collections::BTreeMap;
4748
use std::fmt::Write;
4849
use std::fs::File;
@@ -74,15 +75,15 @@ impl GotocCodegenBackend {
7475
///
7576
/// Invariant: iff `check_contract.is_some()` then `return.2.is_some()`
7677
#[allow(clippy::too_many_arguments)]
77-
fn codegen_items<'tcx, const NUM_THREADS: usize>(
78+
fn codegen_items<'tcx>(
7879
&self,
7980
tcx: TyCtxt<'tcx>,
8081
starting_items: &[MonoItem],
8182
symtab_goto: &Path,
8283
machine_model: &MachineModel,
8384
check_contract: Option<InternalDefId>,
8485
mut transformer: BodyTransformation,
85-
thread_pool: &ThreadPool<NUM_THREADS>,
86+
thread_pool: &ThreadPool,
8687
) -> (GotocCtx<'tcx>, Vec<MonoItem>, Option<AssignsContract>) {
8788
// This runs reachability analysis before global passes are applied.
8889
//
@@ -349,13 +350,24 @@ impl CodegenBackend for GotocCodegenBackend {
349350
return codegen_results(tcx, &results.machine_model);
350351
}
351352

352-
let export_thread_pool = ThreadPool::<NUM_FILE_EXPORT_THREADS>::new();
353+
// Create an empty thread pool. We will set the size later once we
354+
// concretely know the # of harnesses we need to analyze.
355+
let mut export_thread_pool = ThreadPool::empty();
353356

354357
match reachability {
355358
ReachabilityType::AllFns | ReachabilityType::Harnesses => {
356359
let mut units = CodegenUnits::new(&queries, tcx);
357360
let mut modifies_instances = vec![];
358361
let mut loop_contracts_instances = vec![];
362+
363+
// If we are verifying all harnesses, cap the number of threads at `[# of harnesses] - 1`.
364+
// This is just a common sense rule to keep us from spinning up threads that couldn't possibly
365+
// provide additional parallelism over existing threads. One thread can potentially work on each
366+
// harness' goto file but the main compiler thread can just handle the last file itself.
367+
let num_harnesses: usize = units.iter().map(|unit| unit.harnesses.len()).sum();
368+
export_thread_pool
369+
.add_workers(min(NUM_FILE_EXPORT_THREADS, num_harnesses.saturating_sub(1)));
370+
359371
// Cross-crate collecting of all items that are reachable from the crate harnesses.
360372
for unit in units.iter() {
361373
// We reset the body cache for now because each codegen unit has different
@@ -391,6 +403,11 @@ impl CodegenBackend for GotocCodegenBackend {
391403
ReachabilityType::None => unreachable!(),
392404
ReachabilityType::PubFns => {
393405
let unit = CodegenUnit::default();
406+
407+
// Here, it's more difficult to determine which functions will be analyzed,
408+
// so just use the max NUM_FILE_EXPORT_THREADS.
409+
export_thread_pool.add_workers(NUM_FILE_EXPORT_THREADS);
410+
394411
let transformer = BodyTransformation::new(&queries, tcx, &unit);
395412
let main_instance =
396413
stable_mir::entry_fn().map(|main_fn| Instance::try_from(main_fn).unwrap());

kani-compiler/src/codegen_cprover_gotoc/utils/file_writing_pool.rs

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// SPDX-License-Identifier: Apache-2.0 OR MIT
33

44
use std::collections::BTreeMap;
5-
use std::convert::identity;
65
use std::path::PathBuf;
76
use std::sync::mpmc::{Receiver, Sender, channel};
87
use std::sync::mpsc::TryRecvError;
@@ -50,34 +49,37 @@ impl FileDataToWrite {
5049
///
5150
/// File data can be sent to the `work_queue`. This will wake a worker thread which will then serialize and write
5251
/// it to disk in parallel, allowing the main compiler thread to continue codegen.
53-
pub struct ThreadPool<const N: usize> {
54-
pub(crate) work_queue: Sender<WithInterner<FileDataToWrite>>,
55-
join_handles: [JoinHandle<WorkerReturn>; N],
52+
pub struct ThreadPool {
53+
pub(crate) work_queue: Sender<WorkToSend>,
54+
work_queue_recv: Receiver<WorkToSend>,
55+
join_handles: Vec<JoinHandle<WorkerReturn>>,
5656
}
5757

5858
type WorkerReturn = ();
5959

6060
type WorkToSend = WithInterner<FileDataToWrite>;
61-
impl<const N: usize> ThreadPool<N> {
62-
pub fn new() -> Self {
61+
impl ThreadPool {
62+
pub fn empty() -> Self {
6363
let (work_queue_send, work_queue_recv) = channel();
64+
ThreadPool { work_queue: work_queue_send, work_queue_recv, join_handles: Vec::new() }
65+
}
6466

65-
// Spawn a thread for each worker, and pass it the recv end of the work queue.
66-
let join_handles: [JoinHandle<()>; N] = core::array::from_fn(identity).map(|_| {
67-
let new_work_queue_recv = work_queue_recv.clone();
68-
std::thread::spawn(move || {
69-
worker_loop(new_work_queue_recv);
70-
})
71-
});
67+
fn new_worker(work_queue: &Receiver<WorkToSend>) -> JoinHandle<()> {
68+
let new_work_queue = work_queue.clone();
69+
std::thread::spawn(move || {
70+
worker_loop(new_work_queue);
71+
})
72+
}
7273

73-
ThreadPool { work_queue: work_queue_send, join_handles }
74+
pub fn add_workers(&mut self, count: usize) {
75+
self.join_handles.extend((0..count).map(|_| Self::new_worker(&self.work_queue_recv)));
7476
}
7577

7678
/// Try to send work to the work queue, or do it yourself if there's no worker threads.
7779
/// Will only fail if all recievers have disconnected.
7880
pub fn send_work(&self, work: WorkToSend) -> Result<(), &str> {
7981
// If we don't have any workers, just synchronously handle the work ourselves.
80-
if N == 0 {
82+
if self.join_handles.is_empty() {
8183
handle_file(work.into_inner());
8284
return Ok(());
8385
}

0 commit comments

Comments
 (0)