diff --git a/library/std/src/os/horizon/mod.rs b/library/std/src/os/horizon/mod.rs index 326d0ae9cb96d..c2182facb0658 100644 --- a/library/std/src/os/horizon/mod.rs +++ b/library/std/src/os/horizon/mod.rs @@ -4,3 +4,4 @@ pub mod fs; pub(crate) mod raw; +pub mod thread; diff --git a/library/std/src/os/horizon/thread.rs b/library/std/src/os/horizon/thread.rs new file mode 100644 index 0000000000000..19a8aa1524868 --- /dev/null +++ b/library/std/src/os/horizon/thread.rs @@ -0,0 +1,93 @@ +//! Nintendo 3DS-specific extensions to primitives in the [`std::thread`] module. +//! +//! All 3DS models have at least two CPU cores available to spawn threads on: +//! The application core (appcore) and the system core (syscore). The New 3DS +//! has an additional two cores, the first of which can also run user-created +//! threads. +//! +//! Threads spawned on the appcore are cooperative rather than preemptive. This +//! means that threads must explicitly yield control to other threads (whether +//! via synchronization primitives or explicit calls to `yield_now`) when they +//! are not actively performing work. Failure to do so may result in control +//! flow being stuck in an inactive thread while the other threads are powerless +//! to continue their work. +//! +//! However, it is possible to spawn one fully preemptive thread on the syscore +//! by using a service call to reserve a slice of time for a thread to run. +//! Attempting to run more than one thread at a time on the syscore will result +//! in an error. +//! +//! [`std::thread`]: crate::thread + +#![unstable(feature = "horizon_thread_ext", issue = "none")] + +use crate::sealed::Sealed; + +/// Extensions on [`std::thread::Builder`] for the Nintendo 3DS. +/// +/// This trait is sealed: it cannot be implemented outside the standard library. +/// This is so that future additional methods are not breaking changes. +/// +/// [`std::thread::Builder`]: crate::thread::Builder +pub trait BuilderExt: Sized + Sealed { + /// Sets the priority level for the new thread. + /// + /// Low values gives the thread higher priority. For userland apps, this has + /// to be within the range of 0x18 to 0x3F inclusive. The main thread + /// usually has a priority of 0x30, but not always. + fn priority(self, priority: i32) -> Self; + + /// Sets the ID of the processor the thread should be run on. Threads on the + /// 3DS are only preemptive if they are on the system core. Otherwise they + /// are cooperative (must yield to let other threads run). + /// + /// Processor IDs are labeled starting from 0. On Old3DS it must be <2, and + /// on New3DS it must be <4. Pass -1 to execute the thread on all CPUs and + /// -2 to execute the thread on the default CPU (set in the application's + /// Exheader). + /// + /// * Processor #0 is the application core. It is always possible to create + /// a thread on this core. + /// * Processor #1 is the system core. If the CPU time limit is set, it is + /// possible to create a single thread on this core. + /// * Processor #2 is New3DS exclusive. Normal applications can create + /// threads on this core only if the built application has proper external setup. + /// * Processor #3 is New3DS exclusive. Normal applications cannot create + /// threads on this core. + fn processor_id(self, processor_id: i32) -> Self; +} + +impl BuilderExt for crate::thread::Builder { + fn priority(mut self, priority: i32) -> Self { + self.native_options.priority = Some(priority); + self + } + + fn processor_id(mut self, processor_id: i32) -> Self { + self.native_options.processor_id = Some(processor_id); + self + } +} + +/// Get the current thread's priority level. Lower values correspond to higher +/// priority levels. +pub fn current_priority() -> i32 { + let thread_id = unsafe { libc::pthread_self() }; + let mut policy = 0; + let mut sched_param = libc::sched_param { sched_priority: 0 }; + + let result = unsafe { libc::pthread_getschedparam(thread_id, &mut policy, &mut sched_param) }; + assert_eq!(result, 0); + + sched_param.sched_priority +} + +/// Get the current thread's processor ID. +/// +/// * Processor #0 is the application core. +/// * Processor #1 is the system core. +/// * Processor #2 is New3DS exclusive. +/// * Processor #3 is New3DS exclusive. +pub fn current_processor() -> i32 { + unsafe { libc::pthread_getprocessorid_np() } +} diff --git a/library/std/src/sys/hermit/thread.rs b/library/std/src/sys/hermit/thread.rs index e53a1fea6a0dc..30a2d8e82e564 100644 --- a/library/std/src/sys/hermit/thread.rs +++ b/library/std/src/sys/hermit/thread.rs @@ -55,7 +55,11 @@ impl Thread { } } - pub unsafe fn new(stack: usize, p: Box) -> io::Result { + pub unsafe fn new( + stack: usize, + p: Box, + _native_options: BuilderOptions, + ) -> io::Result { Thread::new_with_coreid(stack, p, -1 /* = no specific core */) } @@ -97,6 +101,9 @@ impl Thread { } } +#[derive(Debug, Default)] +pub struct BuilderOptions; + pub fn available_parallelism() -> io::Result { unsupported() } diff --git a/library/std/src/sys/itron/thread.rs b/library/std/src/sys/itron/thread.rs index d28f57f33be20..7b9798e417d67 100644 --- a/library/std/src/sys/itron/thread.rs +++ b/library/std/src/sys/itron/thread.rs @@ -83,7 +83,11 @@ impl Thread { /// # Safety /// /// See `thread::Builder::spawn_unchecked` for safety requirements. - pub unsafe fn new(stack: usize, p: Box) -> io::Result { + pub unsafe fn new( + stack: usize, + p: Box, + _native_options: BuilderOptions, + ) -> io::Result { let inner = Box::new(ThreadInner { start: UnsafeCell::new(ManuallyDrop::new(p)), lifecycle: AtomicUsize::new(LIFECYCLE_INIT), @@ -288,6 +292,9 @@ impl Drop for Thread { } } +#[derive(Debug, Default)] +pub struct BuilderOptions; + pub mod guard { pub type Guard = !; pub unsafe fn current() -> Option { diff --git a/library/std/src/sys/sgx/thread.rs b/library/std/src/sys/sgx/thread.rs index d745a61961404..df3dfaccd6137 100644 --- a/library/std/src/sys/sgx/thread.rs +++ b/library/std/src/sys/sgx/thread.rs @@ -104,7 +104,11 @@ pub mod wait_notify { impl Thread { // unsafe: see thread::Builder::spawn_unchecked for safety requirements - pub unsafe fn new(_stack: usize, p: Box) -> io::Result { + pub unsafe fn new( + _stack: usize, + p: Box, + _native_options: BuilderOptions, + ) -> io::Result { let mut queue_lock = task_queue::lock(); unsafe { usercalls::launch_thread()? }; let (task, handle) = task_queue::Task::new(p); @@ -137,6 +141,9 @@ impl Thread { } } +#[derive(Debug, Default)] +pub struct BuilderOptions; + pub fn available_parallelism() -> io::Result { unsupported() } diff --git a/library/std/src/sys/unix/thread.rs b/library/std/src/sys/unix/thread.rs index d191e1fe7a650..f37aa8c437826 100644 --- a/library/std/src/sys/unix/thread.rs +++ b/library/std/src/sys/unix/thread.rs @@ -48,7 +48,11 @@ unsafe impl Sync for Thread {} impl Thread { // unsafe: see thread::Builder::spawn_unchecked for safety requirements - pub unsafe fn new(stack: usize, p: Box) -> io::Result { + pub unsafe fn new( + stack: usize, + p: Box, + #[allow(unused)] native_options: BuilderOptions, + ) -> io::Result { let p = Box::into_raw(box p); let mut native: libc::pthread_t = mem::zeroed(); let mut attr: libc::pthread_attr_t = mem::zeroed(); @@ -84,6 +88,23 @@ impl Thread { }; } + #[cfg(target_os = "horizon")] + { + // If no priority value is specified, spawn with the same priority + // as the parent thread. + let priority = native_options + .priority + .unwrap_or_else(crate::os::horizon::thread::current_priority); + let sched_param = libc::sched_param { sched_priority: priority }; + + // If no processor is specified, spawn on the default core. + // (determined by the application's Exheader) + let processor_id = native_options.processor_id.unwrap_or(-2); + + assert_eq!(libc::pthread_attr_setschedparam(&mut attr, &sched_param), 0); + assert_eq!(libc::pthread_attr_setprocessorid_np(&mut attr, processor_id), 0); + } + let ret = libc::pthread_create(&mut native, &attr, thread_start, p as *mut _); // Note: if the thread creation fails and this assert fails, then p will // be leaked. However, an alternative design could cause double-free @@ -200,7 +221,8 @@ impl Thread { target_os = "l4re", target_os = "emscripten", target_os = "redox", - target_os = "vxworks" + target_os = "vxworks", + target_os = "horizon" ))] pub fn set_name(_name: &CStr) { // Newlib, Emscripten, and VxWorks have no way to set a thread name. @@ -271,6 +293,27 @@ impl Drop for Thread { } } +#[derive(Debug)] +pub struct BuilderOptions { + /// The spawned thread's priority value + #[cfg(target_os = "horizon")] + pub(crate) priority: Option, + /// The processor to spawn the thread on. See [`os::horizon::thread::BuilderExt`]. + #[cfg(target_os = "horizon")] + pub(crate) processor_id: Option, +} + +impl Default for BuilderOptions { + fn default() -> Self { + BuilderOptions { + #[cfg(target_os = "horizon")] + priority: None, + #[cfg(target_os = "horizon")] + processor_id: None, + } + } +} + pub fn available_parallelism() -> io::Result { cfg_if::cfg_if! { if #[cfg(any( diff --git a/library/std/src/sys/unsupported/thread.rs b/library/std/src/sys/unsupported/thread.rs index a8db251de2017..9946906f85aa1 100644 --- a/library/std/src/sys/unsupported/thread.rs +++ b/library/std/src/sys/unsupported/thread.rs @@ -10,7 +10,11 @@ pub const DEFAULT_MIN_STACK_SIZE: usize = 4096; impl Thread { // unsafe: see thread::Builder::spawn_unchecked for safety requirements - pub unsafe fn new(_stack: usize, _p: Box) -> io::Result { + pub unsafe fn new( + _stack: usize, + _p: Box, + _native_options: BuilderOptions, + ) -> io::Result { unsupported() } @@ -31,6 +35,9 @@ impl Thread { } } +#[derive(Debug, Default)] +pub struct BuilderOptions; + pub fn available_parallelism() -> io::Result { unsupported() } diff --git a/library/std/src/sys/wasi/thread.rs b/library/std/src/sys/wasi/thread.rs index e7a6ab4be826f..60b985203d665 100644 --- a/library/std/src/sys/wasi/thread.rs +++ b/library/std/src/sys/wasi/thread.rs @@ -13,7 +13,11 @@ pub const DEFAULT_MIN_STACK_SIZE: usize = 4096; impl Thread { // unsafe: see thread::Builder::spawn_unchecked for safety requirements - pub unsafe fn new(_stack: usize, _p: Box) -> io::Result { + pub unsafe fn new( + _stack: usize, + _p: Box, + _native_options: BuilderOptions, + ) -> io::Result { unsupported() } @@ -66,6 +70,9 @@ impl Thread { } } +#[derive(Debug, Default)] +pub struct BuilderOptions; + pub fn available_parallelism() -> io::Result { unsupported() } diff --git a/library/std/src/sys/wasm/atomics/thread.rs b/library/std/src/sys/wasm/atomics/thread.rs index 714b704922794..9c27dad88ff4c 100644 --- a/library/std/src/sys/wasm/atomics/thread.rs +++ b/library/std/src/sys/wasm/atomics/thread.rs @@ -10,7 +10,11 @@ pub const DEFAULT_MIN_STACK_SIZE: usize = 4096; impl Thread { // unsafe: see thread::Builder::spawn_unchecked for safety requirements - pub unsafe fn new(_stack: usize, _p: Box) -> io::Result { + pub unsafe fn new( + _stack: usize, + _p: Box, + _native_options: BuilderOptions, + ) -> io::Result { unsupported() } @@ -40,6 +44,9 @@ impl Thread { pub fn join(self) {} } +#[derive(Debug, Default)] +pub struct BuilderOptions; + pub fn available_parallelism() -> io::Result { unsupported() } diff --git a/library/std/src/sys/windows/thread.rs b/library/std/src/sys/windows/thread.rs index c5c9e97e646fb..c774f269c04dd 100644 --- a/library/std/src/sys/windows/thread.rs +++ b/library/std/src/sys/windows/thread.rs @@ -21,7 +21,11 @@ pub struct Thread { impl Thread { // unsafe: see thread::Builder::spawn_unchecked for safety requirements - pub unsafe fn new(stack: usize, p: Box) -> io::Result { + pub unsafe fn new( + stack: usize, + p: Box, + _native_options: BuilderOptions, + ) -> io::Result { let p = Box::into_raw(box p); // FIXME On UNIX, we guard against stack sizes that are too small but @@ -98,6 +102,9 @@ impl Thread { } } +#[derive(Debug, Default)] +pub struct BuilderOptions; + pub fn available_parallelism() -> io::Result { let res = unsafe { let mut sysinfo: c::SYSTEM_INFO = crate::mem::zeroed(); diff --git a/library/std/src/thread/mod.rs b/library/std/src/thread/mod.rs index 0a6a7cfe976cc..87fcd2bd3021d 100644 --- a/library/std/src/thread/mod.rs +++ b/library/std/src/thread/mod.rs @@ -267,8 +267,15 @@ pub struct Builder { name: Option, // The size of the stack for the spawned thread in bytes stack_size: Option, + // Other OS-specific fields. These can be set via extension traits, usually + // found in std::os. + pub(crate) native_options: imp::BuilderOptions, } +/// Allows extension traits within `std`. +#[unstable(feature = "sealed", issue = "none")] +impl crate::sealed::Sealed for Builder {} + impl Builder { /// Generates the base configuration for spawning a thread, from which /// configuration methods can be chained. @@ -290,7 +297,7 @@ impl Builder { /// ``` #[stable(feature = "rust1", since = "1.0.0")] pub fn new() -> Builder { - Builder { name: None, stack_size: None } + Builder { name: None, stack_size: None, native_options: imp::BuilderOptions::default() } } /// Names the thread-to-be. Currently the name is used for identification @@ -470,11 +477,9 @@ impl Builder { T: Send + 'a, 'scope: 'a, { - let Builder { name, stack_size } = self; - - let stack_size = stack_size.unwrap_or_else(thread::min_stack); + let stack_size = self.stack_size.unwrap_or_else(thread::min_stack); - let my_thread = Thread::new(name.map(|name| { + let my_thread = Thread::new(self.name.map(|name| { CString::new(name).expect("thread name may not contain interior null bytes") })); let their_thread = my_thread.clone(); @@ -531,6 +536,7 @@ impl Builder { mem::transmute::, Box>( Box::new(main), ), + self.native_options, )? }, thread: my_thread,