Skip to main content

rustc_data_structures/sync/
parallel.rs

1//! This module defines parallel operations that are implemented in
2//! one way for the serial compiler, and another way the parallel compiler.
3
4use std::any::Any;
5use std::ops::{Index, Range};
6use std::panic::{AssertUnwindSafe, catch_unwind, resume_unwind};
7
8use parking_lot::Mutex;
9
10use crate::FatalErrorMarker;
11use crate::sync::{DynSend, DynSync, FromDyn, IntoDynSyncSend, mode};
12
13pub trait Dss = DynSend + DynSync;
14
15pub trait HasLength {
16    fn len(&self) -> usize;
17}
18
19impl<T> HasLength for [T] {
20    fn len(&self) -> usize {
21        self.len()
22    }
23}
24
25impl<T> HasLength for indexmap::set::Slice<T> {
26    fn len(&self) -> usize {
27        self.len()
28    }
29}
30
31pub trait ParSlice<T> = HasLength + Index<usize, Output = T> + DynSync;
32
33/// A guard used to hold panics that occur during a parallel section to later by unwound.
34/// This is used for the parallel compiler to prevent fatal errors from non-deterministically
35/// hiding errors by ensuring that everything in the section has completed executing before
36/// continuing with unwinding. It's also used for the non-parallel code to ensure error message
37/// output match the parallel compiler for testing purposes.
38pub struct ParallelGuard {
39    panic: Mutex<Option<IntoDynSyncSend<Box<dyn Any + Send + 'static>>>>,
40}
41
42impl ParallelGuard {
43    pub fn run<R>(&self, f: impl FnOnce() -> R) -> Option<R> {
44        catch_unwind(AssertUnwindSafe(f))
45            .map_err(|err| {
46                let mut panic = self.panic.lock();
47                if panic.is_none() || !(*err).is::<FatalErrorMarker>() {
48                    *panic = Some(IntoDynSyncSend(err));
49                }
50            })
51            .ok()
52    }
53}
54
55/// This gives access to a fresh parallel guard in the closure and will unwind any panics
56/// caught in it after the closure returns.
57#[inline]
58pub fn parallel_guard<R>(f: impl FnOnce(&ParallelGuard) -> R) -> R {
59    let guard = ParallelGuard { panic: Mutex::new(None) };
60    let ret = f(&guard);
61    if let Some(IntoDynSyncSend(panic)) = guard.panic.into_inner() {
62        resume_unwind(panic);
63    }
64    ret
65}
66
67fn serial_join<A, B, RA, RB>(oper_a: A, oper_b: B) -> (RA, RB)
68where
69    A: FnOnce() -> RA,
70    B: FnOnce() -> RB,
71{
72    let (a, b) = parallel_guard(|guard| {
73        let a = guard.run(oper_a);
74        let b = guard.run(oper_b);
75        (a, b)
76    });
77    (a.unwrap(), b.unwrap())
78}
79
80pub fn spawn(func: impl FnOnce() + DynSend + 'static) {
81    if let Some(proof) = mode::check_dyn_thread_safe() {
82        let func = proof.derive(func);
83        rustc_thread_pool::spawn(|| {
84            (func.into_inner())();
85        });
86    } else {
87        func()
88    }
89}
90
91/// Runs the functions in parallel.
92///
93/// The first function is executed immediately on the current thread.
94/// Use that for the longest running function for better scheduling.
95pub fn par_fns(funcs: &mut [&mut (dyn FnMut() + DynSend)]) {
96    parallel_guard(|guard: &ParallelGuard| {
97        if let Some(proof) = mode::check_dyn_thread_safe() {
98            let funcs = proof.derive(funcs);
99            rustc_thread_pool::scope(|s| {
100                let Some((first, rest)) = funcs.into_inner().split_at_mut_checked(1) else {
101                    return;
102                };
103
104                // Reverse the order of the later functions since Rayon executes them in reverse
105                // order when using a single thread. This ensures the execution order matches
106                // that of a single threaded rustc.
107                for f in rest.iter_mut().rev() {
108                    let f = proof.derive(f);
109                    s.spawn(|_| {
110                        guard.run(|| (f.into_inner())());
111                    });
112                }
113
114                // Run the first function without spawning to
115                // ensure it executes immediately on this thread.
116                guard.run(|| first[0]());
117            });
118        } else {
119            for f in funcs {
120                guard.run(|| f());
121            }
122        }
123    });
124}
125
126#[inline]
127pub fn par_join<A, B, RA: DynSend, RB: DynSend>(oper_a: A, oper_b: B) -> (RA, RB)
128where
129    A: FnOnce() -> RA + DynSend,
130    B: FnOnce() -> RB + DynSend,
131{
132    if let Some(proof) = mode::check_dyn_thread_safe() {
133        let oper_a = proof.derive(oper_a);
134        let oper_b = proof.derive(oper_b);
135        let (a, b) = parallel_guard(|guard| {
136            rustc_thread_pool::join(
137                move || guard.run(move || proof.derive(oper_a.into_inner()())),
138                move || guard.run(move || proof.derive(oper_b.into_inner()())),
139            )
140        });
141        (a.unwrap().into_inner(), b.unwrap().into_inner())
142    } else {
143        serial_join(oper_a, oper_b)
144    }
145}
146
147const MAX_GROUP_COUNT: usize = 128;
148
149fn par_range_internal(
150    range: Range<usize>,
151    guard: &ParallelGuard,
152    proof: FromDyn<()>,
153    op: impl Fn(usize) + Dss,
154) {
155    let len = range.end - range.start;
156    match len {
157        0 => return,
158        1 => {
159            guard.run(|| op(range.start));
160            return;
161        }
162        _ => {}
163    }
164
165    let op = proof.derive(op);
166
167    rustc_thread_pool::scope(|s| {
168        let group_size = len.div_ceil(MAX_GROUP_COUNT);
169        if true {
    if !(group_size > 0) {
        ::core::panicking::panic("assertion failed: group_size > 0")
    };
};debug_assert!(group_size > 0);
170
171        // Take the first group and execute it after the loop on this thread.
172        let first_group = range.start..range.end.min(range.start + group_size);
173
174        let range = first_group.end..range.end;
175        let mut curr_end = range.end;
176
177        // We iterate from end to start to reverse the chunks as it is done in `par_slice_mut`.
178        loop {
179            let curr_start = curr_end.checked_sub(group_size).unwrap_or(range.start);
180
181            // Don't let the `curr_start` go before the initial range start.
182            let curr_start = curr_start.max(range.start);
183
184            // We can encounter empty range here, don't spawn anything in this case.
185            if curr_start == curr_end {
186                break;
187            }
188
189            let curr_range = curr_start..curr_end;
190            s.spawn(|_| {
191                for idx in curr_range {
192                    guard.run(|| op(idx));
193                }
194            });
195
196            if curr_start == range.start {
197                break;
198            }
199
200            curr_end -= group_size;
201        }
202
203        for idx in first_group {
204            guard.run(|| op(idx));
205        }
206    });
207}
208
209/// Executes the given action `op` with each index from range `range` in parallel
210/// if the parallel frontend is enabled, otherwise executes them sequentially. Action
211/// is executed with each index under parallel guard in both scenarios so if one of them
212/// panics all others will be executed anyway.
213pub fn par_range(range: Range<usize>, op: impl Fn(usize) + Dss) {
214    parallel_guard(|guard| {
215        if let Some(proof) = mode::check_dyn_thread_safe() {
216            par_range_internal(range, guard, proof, op);
217        } else {
218            for i in range {
219                guard.run(|| op(i));
220            }
221        }
222    })
223}
224
225fn par_slice_mut<I: DynSend>(
226    items: &mut [I],
227    guard: &ParallelGuard,
228    for_each: impl Fn(&mut I) + Dss,
229    proof: FromDyn<()>,
230) {
231    match items {
232        [] => return,
233        [item] => {
234            guard.run(|| for_each(item));
235            return;
236        }
237        _ => (),
238    }
239
240    let for_each = proof.derive(for_each);
241    let mut items = for_each.derive(items);
242    rustc_thread_pool::scope(|s| {
243        let proof = items.derive(());
244
245        let group_size = items.len().div_ceil(MAX_GROUP_COUNT);
246        let mut groups = items.chunks_mut(group_size);
247
248        let Some(first_group) = groups.next() else { return };
249
250        // Reverse the order of the later functions since Rayon executes them in reverse
251        // order when using a single thread. This ensures the execution order matches
252        // that of a single threaded rustc.
253        for group in groups.rev() {
254            let group = proof.derive(group);
255            s.spawn(|_| {
256                let mut group = group;
257                for i in group.iter_mut() {
258                    guard.run(|| for_each(i));
259                }
260            });
261        }
262
263        // Run the first function without spawning to avoid overwhelming stealing.
264        for i in first_group.iter_mut() {
265            guard.run(|| for_each(i));
266        }
267    });
268}
269
270pub fn par_for_each_in<T: Dss>(items: &(impl ParSlice<T> + ?Sized), for_each: impl Fn(&T) + Dss) {
271    let range = 0..items.len();
272    parallel_guard(|guard| {
273        if let Some(proof) = mode::check_dyn_thread_safe() {
274            par_range_internal(range, guard, proof, |idx| for_each(&items[idx]));
275        } else {
276            for idx in range {
277                guard.run(|| for_each(&items[idx]));
278            }
279        }
280    })
281}
282
283// FIXME: actually make parallel and `T: DynSend`
284pub fn par_for_each_slice<T>(items: &mut [T], for_each: impl Fn(&mut T)) {
285    parallel_guard(|guard| {
286        items.iter_mut().for_each(|i| {
287            guard.run(|| for_each(i));
288        });
289    });
290}
291
292/// This runs `for_each` in parallel for each iterator item. If one or more of the
293/// `for_each` calls returns `Err`, the function will also return `Err`. The error returned
294/// will be non-deterministic, but this is expected to be used with `ErrorGuaranteed` which
295/// are all equivalent.
296pub fn try_par_for_each_in<T: Dss, E: DynSend>(
297    items: &(impl ParSlice<T> + ?Sized),
298    for_each: impl Fn(&T) -> Result<(), E> + Dss,
299) -> Result<(), E> {
300    let range = 0..items.len();
301    parallel_guard(|guard| {
302        if let Some(proof) = mode::check_dyn_thread_safe() {
303            let error = Mutex::new(None);
304
305            par_range_internal(range, guard, proof, |idx| {
306                if let Err(err) = for_each(&items[idx]) {
307                    *error.lock() = Some(err);
308                }
309            });
310
311            if let Some(err) = error.into_inner() { Err(err) } else { Ok(()) }
312        } else {
313            range
314                .into_iter()
315                .filter_map(|idx| guard.run(|| for_each(&items[idx])))
316                .fold(Ok(()), Result::and)
317        }
318    })
319}
320
321pub fn par_map<I: DynSend, T: IntoIterator<Item = I>, R: DynSend, C: FromIterator<R>>(
322    t: T,
323    map: impl Fn(I) -> R + Dss,
324) -> C {
325    parallel_guard(|guard| {
326        if let Some(proof) = mode::check_dyn_thread_safe() {
327            let map = proof.derive(map);
328
329            let mut items: Vec<(Option<I>, Option<R>)> =
330                t.into_iter().map(|i| (Some(i), None)).collect();
331
332            par_slice_mut(
333                &mut items,
334                guard,
335                |i| {
336                    i.1 = Some(map(i.0.take().unwrap()));
337                },
338                proof,
339            );
340
341            items.into_iter().filter_map(|i| i.1).collect()
342        } else {
343            t.into_iter().filter_map(|i| guard.run(|| map(i))).collect()
344        }
345    })
346}
347
348pub fn broadcast<R: DynSend>(op: impl Fn(usize) -> R + DynSync) -> Vec<R> {
349    if let Some(proof) = mode::check_dyn_thread_safe() {
350        let op = proof.derive(op);
351        let results = rustc_thread_pool::broadcast(|context| op.derive(op(context.index())));
352        results.into_iter().map(|r| r.into_inner()).collect()
353    } else {
354        ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [op(0)]))vec![op(0)]
355    }
356}