blob: 122cb240087e3766dc302f366e50daadab96a6ab [file]
//! This module defines parallel operations that are implemented in
//! one way for the serial compiler, and another way the parallel compiler.
use std::any::Any;
use std::ops::{Index, Range};
use std::panic::{AssertUnwindSafe, catch_unwind, resume_unwind};
use parking_lot::Mutex;
use crate::FatalErrorMarker;
use crate::sync::{DynSend, DynSync, FromDyn, IntoDynSyncSend, mode};
pub trait Dss = DynSend + DynSync;
pub trait HasLength {
fn len(&self) -> usize;
}
impl<T> HasLength for [T] {
fn len(&self) -> usize {
self.len()
}
}
impl<T> HasLength for indexmap::set::Slice<T> {
fn len(&self) -> usize {
self.len()
}
}
pub trait ParSlice<T> = HasLength + Index<usize, Output = T> + DynSync;
/// A guard used to hold panics that occur during a parallel section to later by unwound.
/// This is used for the parallel compiler to prevent fatal errors from non-deterministically
/// hiding errors by ensuring that everything in the section has completed executing before
/// continuing with unwinding. It's also used for the non-parallel code to ensure error message
/// output match the parallel compiler for testing purposes.
pub struct ParallelGuard {
panic: Mutex<Option<IntoDynSyncSend<Box<dyn Any + Send + 'static>>>>,
}
impl ParallelGuard {
pub fn run<R>(&self, f: impl FnOnce() -> R) -> Option<R> {
catch_unwind(AssertUnwindSafe(f))
.map_err(|err| {
let mut panic = self.panic.lock();
if panic.is_none() || !(*err).is::<FatalErrorMarker>() {
*panic = Some(IntoDynSyncSend(err));
}
})
.ok()
}
}
/// This gives access to a fresh parallel guard in the closure and will unwind any panics
/// caught in it after the closure returns.
#[inline]
pub fn parallel_guard<R>(f: impl FnOnce(&ParallelGuard) -> R) -> R {
let guard = ParallelGuard { panic: Mutex::new(None) };
let ret = f(&guard);
if let Some(IntoDynSyncSend(panic)) = guard.panic.into_inner() {
resume_unwind(panic);
}
ret
}
fn serial_join<A, B, RA, RB>(oper_a: A, oper_b: B) -> (RA, RB)
where
A: FnOnce() -> RA,
B: FnOnce() -> RB,
{
let (a, b) = parallel_guard(|guard| {
let a = guard.run(oper_a);
let b = guard.run(oper_b);
(a, b)
});
(a.unwrap(), b.unwrap())
}
pub fn spawn(func: impl FnOnce() + DynSend + 'static) {
if let Some(proof) = mode::check_dyn_thread_safe() {
let func = proof.derive(func);
rustc_thread_pool::spawn(|| {
(func.into_inner())();
});
} else {
func()
}
}
/// Runs the functions in parallel.
///
/// The first function is executed immediately on the current thread.
/// Use that for the longest running function for better scheduling.
pub fn par_fns(funcs: &mut [&mut (dyn FnMut() + DynSend)]) {
parallel_guard(|guard: &ParallelGuard| {
if let Some(proof) = mode::check_dyn_thread_safe() {
let funcs = proof.derive(funcs);
rustc_thread_pool::scope(|s| {
let Some((first, rest)) = funcs.into_inner().split_at_mut_checked(1) else {
return;
};
// Reverse the order of the later functions since Rayon executes them in reverse
// order when using a single thread. This ensures the execution order matches
// that of a single threaded rustc.
for f in rest.iter_mut().rev() {
let f = proof.derive(f);
s.spawn(|_| {
guard.run(|| (f.into_inner())());
});
}
// Run the first function without spawning to
// ensure it executes immediately on this thread.
guard.run(|| first[0]());
});
} else {
for f in funcs {
guard.run(|| f());
}
}
});
}
#[inline]
pub fn par_join<A, B, RA: DynSend, RB: DynSend>(oper_a: A, oper_b: B) -> (RA, RB)
where
A: FnOnce() -> RA + DynSend,
B: FnOnce() -> RB + DynSend,
{
if let Some(proof) = mode::check_dyn_thread_safe() {
let oper_a = proof.derive(oper_a);
let oper_b = proof.derive(oper_b);
let (a, b) = parallel_guard(|guard| {
rustc_thread_pool::join(
move || guard.run(move || proof.derive(oper_a.into_inner()())),
move || guard.run(move || proof.derive(oper_b.into_inner()())),
)
});
(a.unwrap().into_inner(), b.unwrap().into_inner())
} else {
serial_join(oper_a, oper_b)
}
}
const MAX_GROUP_COUNT: usize = 128;
fn par_range_internal(
range: Range<usize>,
guard: &ParallelGuard,
proof: FromDyn<()>,
op: impl Fn(usize) + Dss,
) {
let len = range.end - range.start;
match len {
0 => return,
1 => {
guard.run(|| op(range.start));
return;
}
_ => {}
}
let op = proof.derive(op);
rustc_thread_pool::scope(|s| {
let group_size = len.div_ceil(MAX_GROUP_COUNT);
debug_assert!(group_size > 0);
// Take the first group and execute it after the loop on this thread.
let first_group = range.start..range.end.min(range.start + group_size);
let range = first_group.end..range.end;
let mut curr_end = range.end;
// We iterate from end to start to reverse the chunks as it is done in `par_slice_mut`.
loop {
let curr_start = curr_end.checked_sub(group_size).unwrap_or(range.start);
// Don't let the `curr_start` go before the initial range start.
let curr_start = curr_start.max(range.start);
// We can encounter empty range here, don't spawn anything in this case.
if curr_start == curr_end {
break;
}
let curr_range = curr_start..curr_end;
s.spawn(|_| {
for idx in curr_range {
guard.run(|| op(idx));
}
});
if curr_start == range.start {
break;
}
curr_end -= group_size;
}
for idx in first_group {
guard.run(|| op(idx));
}
});
}
/// Executes the given action `op` with each index from range `range` in parallel
/// if the parallel frontend is enabled, otherwise executes them sequentially. Action
/// is executed with each index under parallel guard in both scenarios so if one of them
/// panics all others will be executed anyway.
pub fn par_range(range: Range<usize>, op: impl Fn(usize) + Dss) {
parallel_guard(|guard| {
if let Some(proof) = mode::check_dyn_thread_safe() {
par_range_internal(range, guard, proof, op);
} else {
for i in range {
guard.run(|| op(i));
}
}
})
}
fn par_slice_mut<I: DynSend>(
items: &mut [I],
guard: &ParallelGuard,
for_each: impl Fn(&mut I) + Dss,
proof: FromDyn<()>,
) {
match items {
[] => return,
[item] => {
guard.run(|| for_each(item));
return;
}
_ => (),
}
let for_each = proof.derive(for_each);
let mut items = for_each.derive(items);
rustc_thread_pool::scope(|s| {
let proof = items.derive(());
let group_size = items.len().div_ceil(MAX_GROUP_COUNT);
let mut groups = items.chunks_mut(group_size);
let Some(first_group) = groups.next() else { return };
// Reverse the order of the later functions since Rayon executes them in reverse
// order when using a single thread. This ensures the execution order matches
// that of a single threaded rustc.
for group in groups.rev() {
let group = proof.derive(group);
s.spawn(|_| {
let mut group = group;
for i in group.iter_mut() {
guard.run(|| for_each(i));
}
});
}
// Run the first function without spawning to avoid overwhelming stealing.
for i in first_group.iter_mut() {
guard.run(|| for_each(i));
}
});
}
pub fn par_for_each_in<T: Dss>(items: &(impl ParSlice<T> + ?Sized), for_each: impl Fn(&T) + Dss) {
let range = 0..items.len();
parallel_guard(|guard| {
if let Some(proof) = mode::check_dyn_thread_safe() {
par_range_internal(range, guard, proof, |idx| for_each(&items[idx]));
} else {
for idx in range {
guard.run(|| for_each(&items[idx]));
}
}
})
}
// FIXME: actually make parallel and `T: DynSend`
pub fn par_for_each_slice<T>(items: &mut [T], for_each: impl Fn(&mut T)) {
parallel_guard(|guard| {
items.iter_mut().for_each(|i| {
guard.run(|| for_each(i));
});
});
}
/// This runs `for_each` in parallel for each iterator item. If one or more of the
/// `for_each` calls returns `Err`, the function will also return `Err`. The error returned
/// will be non-deterministic, but this is expected to be used with `ErrorGuaranteed` which
/// are all equivalent.
pub fn try_par_for_each_in<T: Dss, E: DynSend>(
items: &(impl ParSlice<T> + ?Sized),
for_each: impl Fn(&T) -> Result<(), E> + Dss,
) -> Result<(), E> {
let range = 0..items.len();
parallel_guard(|guard| {
if let Some(proof) = mode::check_dyn_thread_safe() {
let error = Mutex::new(None);
par_range_internal(range, guard, proof, |idx| {
if let Err(err) = for_each(&items[idx]) {
*error.lock() = Some(err);
}
});
if let Some(err) = error.into_inner() { Err(err) } else { Ok(()) }
} else {
range
.into_iter()
.filter_map(|idx| guard.run(|| for_each(&items[idx])))
.fold(Ok(()), Result::and)
}
})
}
pub fn par_map<I: DynSend, T: IntoIterator<Item = I>, R: DynSend, C: FromIterator<R>>(
t: T,
map: impl Fn(I) -> R + Dss,
) -> C {
parallel_guard(|guard| {
if let Some(proof) = mode::check_dyn_thread_safe() {
let map = proof.derive(map);
let mut items: Vec<(Option<I>, Option<R>)> =
t.into_iter().map(|i| (Some(i), None)).collect();
par_slice_mut(
&mut items,
guard,
|i| {
i.1 = Some(map(i.0.take().unwrap()));
},
proof,
);
items.into_iter().filter_map(|i| i.1).collect()
} else {
t.into_iter().filter_map(|i| guard.run(|| map(i))).collect()
}
})
}
pub fn broadcast<R: DynSend>(op: impl Fn(usize) -> R + DynSync) -> Vec<R> {
if let Some(proof) = mode::check_dyn_thread_safe() {
let op = proof.derive(op);
let results = rustc_thread_pool::broadcast(|context| op.derive(op(context.index())));
results.into_iter().map(|r| r.into_inner()).collect()
} else {
vec![op(0)]
}
}