diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index cd4b9540bdd9..e2b1dcce6717 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -497,12 +497,7 @@ jobs: fail-fast: false matrix: crate: - - better_scoped_tls - - string_enum - # - swc - - swc_bundler - # - swc_ecma_codegen - # - swc_ecma_minifier + - swc_parallel steps: - uses: actions/checkout@v4 with: diff --git a/Cargo.lock b/Cargo.lock index bc48628ed6df..73393036abc7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5937,7 +5937,9 @@ name = "swc_parallel" version = "1.0.1" dependencies = [ "chili", + "hstr", "once_cell", + "scoped-tls", ] [[package]] diff --git a/crates/swc_ecma_utils/src/parallel.rs b/crates/swc_ecma_utils/src/parallel.rs index f4359fd0c109..68f9ba8a26de 100644 --- a/crates/swc_ecma_utils/src/parallel.rs +++ b/crates/swc_ecma_utils/src/parallel.rs @@ -3,18 +3,10 @@ use once_cell::sync::Lazy; use swc_common::GLOBALS; use swc_ecma_ast::*; -use swc_parallel::join; - -use self::private::Sealed; - -mod private { - pub trait Sealed {} - - impl Sealed for Vec {} - impl Sealed for &mut Vec {} - impl Sealed for &mut [T] {} - impl Sealed for &[T] {} -} +use swc_parallel::{ + items::{IntoItems, Items}, + join, +}; static CPU_COUNT: Lazy = Lazy::new(num_cpus::get); @@ -36,102 +28,6 @@ pub trait Parallel: swc_common::sync::Send + swc_common::sync::Sync { fn after_module_items(&mut self, _stmts: &mut Vec) {} } -pub trait IntoItems: Sealed { - type Elem; - type Items: Items; - - fn into_items(self) -> Self::Items; -} - -impl IntoItems for I -where - I: Items, -{ - type Elem = T; - type Items = I; - - fn into_items(self) -> Self::Items { - self - } -} - -impl<'a, T> IntoItems for &'a mut Vec -where - T: Send + Sync, -{ - type Elem = &'a mut T; - type Items = &'a mut [T]; - - fn into_items(self) -> Self::Items { - self - } -} - -/// This is considered as a private type and it's NOT A PUBLIC API. -#[allow(clippy::len_without_is_empty)] -pub trait Items: Sized + IntoIterator + Send + Sealed { - type Elem: Send + Sync; - - fn len(&self) -> usize; - - #[cfg(feature = "concurrent")] - fn split_at(self, idx: usize) -> (Self, Self); -} - -impl Items for Vec -where - T: Send + Sync, -{ - type Elem = T; - - fn len(&self) -> usize { - Vec::len(self) - } - - #[cfg(feature = "concurrent")] - fn split_at(mut self, at: usize) -> (Self, Self) { - let b = self.split_off(at); - - (self, b) - } -} - -impl<'a, T> Items for &'a mut [T] -where - T: Send + Sync, -{ - type Elem = &'a mut T; - - fn len(&self) -> usize { - <[T]>::len(self) - } - - #[cfg(feature = "concurrent")] - fn split_at(self, at: usize) -> (Self, Self) { - let (a, b) = self.split_at_mut(at); - - (a, b) - } -} - -impl<'a, T> Items for &'a [T] -where - T: Send + Sync, -{ - type Elem = &'a T; - - fn len(&self) -> usize { - <[T]>::len(self) - } - - #[cfg(feature = "concurrent")] - fn split_at(self, at: usize) -> (Self, Self) { - let (a, b) = self.split_at(at); - - (a, b) - } -} - pub trait ParallelExt: Parallel { /// Invoke `op` in parallel, if `swc_ecma_utils` is compiled with /// concurrent feature enabled and `nodes.len()` is bigger than threshold. diff --git a/crates/swc_parallel/Cargo.toml b/crates/swc_parallel/Cargo.toml index 47cd8ba76d90..3261a937dfdd 100644 --- a/crates/swc_parallel/Cargo.toml +++ b/crates/swc_parallel/Cargo.toml @@ -12,7 +12,13 @@ version = "1.0.1" default = ["parallel"] # Make it really parallel parallel = ["chili"] +# Alias for parallel, just for CI. Do not use it if you are not working on SWC. +concurrent = ["parallel"] [dependencies] chili = { workspace = true, optional = true } once_cell = { workspace = true } + +[dev-dependencies] +hstr = { workspace = true } +scoped-tls = { workspace = true } diff --git a/crates/swc_parallel/src/items.rs b/crates/swc_parallel/src/items.rs new file mode 100644 index 000000000000..75cb89e19ab9 --- /dev/null +++ b/crates/swc_parallel/src/items.rs @@ -0,0 +1,102 @@ +use self::private::Sealed; + +mod private { + pub trait Sealed {} + + impl Sealed for Vec {} + impl Sealed for &mut Vec {} + impl Sealed for &mut [T] {} + impl Sealed for &[T] {} +} +pub trait IntoItems: Sealed { + type Elem; + type Items: Items; + + fn into_items(self) -> Self::Items; +} + +impl IntoItems for I +where + I: Items, +{ + type Elem = T; + type Items = I; + + fn into_items(self) -> Self::Items { + self + } +} + +impl<'a, T> IntoItems for &'a mut Vec +where + T: Send + Sync, +{ + type Elem = &'a mut T; + type Items = &'a mut [T]; + + fn into_items(self) -> Self::Items { + self + } +} + +/// This is considered as a private type and it's NOT A PUBLIC API. +#[allow(clippy::len_without_is_empty)] +pub trait Items: Sized + IntoIterator + Send + Sealed { + type Elem: Send + Sync; + + fn len(&self) -> usize; + + fn split_at(self, idx: usize) -> (Self, Self); +} + +impl Items for Vec +where + T: Send + Sync, +{ + type Elem = T; + + fn len(&self) -> usize { + Vec::len(self) + } + + fn split_at(mut self, at: usize) -> (Self, Self) { + let b = self.split_off(at); + + (self, b) + } +} + +impl<'a, T> Items for &'a mut [T] +where + T: Send + Sync, +{ + type Elem = &'a mut T; + + fn len(&self) -> usize { + <[T]>::len(self) + } + + fn split_at(self, at: usize) -> (Self, Self) { + let (a, b) = self.split_at_mut(at); + + (a, b) + } +} + +impl<'a, T> Items for &'a [T] +where + T: Send + Sync, +{ + type Elem = &'a T; + + fn len(&self) -> usize { + <[T]>::len(self) + } + + fn split_at(self, at: usize) -> (Self, Self) { + let (a, b) = self.split_at(at); + + (a, b) + } +} +// diff --git a/crates/swc_parallel/src/lib.rs b/crates/swc_parallel/src/lib.rs index fc13f90fa321..85b05f2df4eb 100644 --- a/crates/swc_parallel/src/lib.rs +++ b/crates/swc_parallel/src/lib.rs @@ -2,6 +2,8 @@ use std::{cell::RefCell, mem::transmute}; +pub mod items; + #[derive(Default)] pub struct MaybeScope<'a>(ScopeLike<'a>); diff --git a/crates/swc_parallel/tests/simulate.rs b/crates/swc_parallel/tests/simulate.rs new file mode 100644 index 000000000000..b9eab5343b33 --- /dev/null +++ b/crates/swc_parallel/tests/simulate.rs @@ -0,0 +1,179 @@ +use std::{ + sync::{Arc, Mutex}, + thread::{self, sleep}, + time::Duration, +}; + +use swc_parallel::{items::Items, join}; +#[derive(Default)] +struct Context { + sum: usize, + atoms: Vec, +} + +impl Parallel for Context { + fn create(&self) -> Self { + Context::default() + } + + fn merge(&mut self, other: Self) { + self.sum += other.sum; + } +} + +#[test] +fn case_1_single_thread() { + STATE.set(&State::default(), || { + sum_in_parallel(10000); + }); +} + +#[test] +fn case_2_wait_for() { + STATE.set(&State::default(), || { + let threads = 100; + let mut handles = vec![]; + + for _ in 0..threads { + handles.push(thread::spawn(move || { + STATE.set(&Default::default(), || sum_in_parallel(10000)); + })); + } + + sleep(Duration::from_secs(1)); + + for handle in handles { + handle.join().unwrap(); + } + }); +} + +#[test] +fn case_3_early_exit_of_entry() { + STATE.set(&State::default(), || { + let threads = 100; + let mut handles = vec![]; + + for _ in 0..threads { + handles.push(thread::spawn(move || { + sleep(Duration::from_secs(1)); + + STATE.set(&Default::default(), || sum_in_parallel(10000)); + })); + } + + for handle in handles { + handle.join().unwrap(); + } + }); +} + +#[test] +fn case_4_explode() { + STATE.set(&State::default(), || { + let threads = 100; + let mut handles = vec![]; + + for _ in 0..threads { + handles.push(thread::spawn(move || { + sleep(Duration::from_secs(1)); + + for _ in 0..10 { + spawn_work(); + } + })); + } + + for handle in handles { + handle.join().unwrap(); + } + }); +} + +#[derive(Default)] +struct State { + arc: Arc>, +} + +scoped_tls::scoped_thread_local!(static STATE: State); + +fn spawn_work() { + let _handle = thread::spawn(move || { + sleep(Duration::from_secs(1)); + + STATE.set(&State::default(), || sum_in_parallel(10000)); + }); +} + +fn sum_in_parallel(to: usize) { + let items = (0..to).collect::>(); + + let mut ctx = Context::default(); + maybe_par_idx_raw(&mut ctx, items, &|ctx, _idx, n| { + if n % 100 == 0 { + STATE.with(|state| { + let mut guard = state.arc.lock().unwrap(); + *guard += 1; + }); + } + + if n % 50 == 0 { + ctx.atoms.push(hstr::Atom::from(n.to_string())); + } + + ctx.sum += n; + }); + + assert_eq!(ctx.sum, to * (to - 1) / 2); +} + +pub trait Parallel: Send + Sync { + /// Used to create visitor. + fn create(&self) -> Self; + + /// This can be called in anytime. + fn merge(&mut self, other: Self); +} + +fn maybe_par_idx_raw(ctx: &mut C, nodes: I, op: &F) +where + C: Parallel, + I: Items, + F: Send + Sync + Fn(&mut C, usize, I::Elem), +{ + let len = nodes.len(); + if len == 0 { + return; + } + + if len == 1 { + op(ctx, 0, nodes.into_iter().next().unwrap()); + return; + } + + let (na, nb) = nodes.split_at(len / 2); + + let (va, vb) = STATE.with(|state| { + join( + || { + STATE.set(state, || { + let mut new_ctx = ctx.create(); + maybe_par_idx_raw(&mut new_ctx, na, op); + + new_ctx + }) + }, + || { + STATE.set(state, || { + let mut new_ctx = ctx.create(); + maybe_par_idx_raw(&mut new_ctx, nb, op); + + new_ctx + }) + }, + ) + }); + + ctx.merge(va); + ctx.merge(vb); +}