diff --git a/library/std/src/os/horizon/mod.rs b/library/std/src/os/horizon/mod.rs index d7da2adac307e..b94839f60affb 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 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..dce22a9604611 --- /dev/null +++ b/library/std/src/os/horizon/thread.rs @@ -0,0 +1,88 @@ +//! 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")] + +/// Extensions on [`std::thread::Builder`] for the Nintendo 3DS. +/// +/// [`std::thread::Builder`]: crate::thread::Builder +pub trait BuilderExt: Sized { + /// 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(mut 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(mut 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..7b95b2d3472d7 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)] +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 5b718a460dfa9..bf32c458f69ea 100644 --- a/library/std/src/sys/itron/thread.rs +++ b/library/std/src/sys/itron/thread.rs @@ -84,7 +84,15 @@ 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 { + // Inherit the current task's priority + let current_task = task::try_current_task_id().map_err(|e| e.as_io_error())?; + let priority = task::try_task_priority(current_task).map_err(|e| e.as_io_error())?; + let inner = Box::new(ThreadInner { start: UnsafeCell::new(ManuallyDrop::new(p)), lifecycle: AtomicUsize::new(LIFECYCLE_INIT), @@ -289,6 +297,9 @@ impl Drop for Thread { } } +#[derive(Debug)] +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..7cc017d784daa 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)] +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 cf8cf5ad49f73..871edef5673ad 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 @@ -194,7 +215,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. @@ -265,6 +287,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..aaf56340c2992 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)] +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..22dfe61362862 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)] +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 16418a06226e4..1f5ebf16703d4 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)] +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 e4bba9255d23e..167185398121c 100644 --- a/library/std/src/sys/windows/thread.rs +++ b/library/std/src/sys/windows/thread.rs @@ -20,7 +20,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 @@ -100,6 +104,9 @@ impl Thread { } } +#[derive(Debug)] +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 f8d790c37852c..f6568c7515d51 100644 --- a/library/std/src/thread/mod.rs +++ b/library/std/src/thread/mod.rs @@ -264,6 +264,9 @@ 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, } impl Builder { @@ -287,7 +290,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 @@ -467,11 +470,9 @@ impl Builder { T: Send + 'a, 'scope: 'a, { - let Builder { name, stack_size } = self; + let stack_size = self.stack_size.unwrap_or_else(thread::min_stack); - let stack_size = 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(); @@ -528,6 +529,7 @@ impl Builder { mem::transmute::, Box>( Box::new(main), ), + self.native_options, )? }, thread: my_thread,