Add Special Thread Builder Functions for the Nintendo 3DS · Issue #71 · rust-lang/libs-team (original) (raw)
Proposal
Problem statement
(some background first)
The Nintendo 3DS is supported as a Tier 3 target (armv6k-nintendo-3ds
). Recently, basic std support was merged in rust-lang/rust#95897. There is now an open PR to enable std::thread
support: rust-lang/rust#98514.
The Nintendo 3DS has a few physical cores that can be used by user applications (2-4 depending on the model1). One of these cores is preemptive (primarily used by the OS, named system core or syscore), and the rest are cooperative (known as app cores). Threads live on the core they were created on, and don't move across cores. See https://www.3dbrew.org/wiki/Multi-threading.
Now coming to the real problem statement: The API used to spawn threads on the 3DS requires the core/processor where the thread will live, as well as the thread priority. The processor ID can't be changed after the thread has been created2, though the priority technically can be (but it should be set at thread creation time). The current Rust thread builder API doesn't provide a way to set the processor ID or priority during thread creation.
Motivation, use-cases
Setting the processor ID is important to 3DS applications. To use the system core, which is preemptive, the correct processor ID needs to be passed during thread creation. To spawn threads on a different core than the main thread, the program needs a way to set the processor ID to use.
There is a processor ID value that tells the 3DS to spawn the thread on the "default" core for the application, but if we use this value, we limit the application to just one core (and it can't use the preemptive system core).
Setting the priority is also important to 3DS applications. Since most of the cores are cooperative, the priority value tells the 3DS what order to executes threads in when a yield happens. Having the wrong priority value could mean lower performance (less opportunities to run the thread). There is an API to get the current thread's priority, which can be used to set new threads to the current thread's priority, so this isn't as big of an issue as the processor ID section. However, since the priority is passed in a thread creation we should have an API to let the user take advantage of this.
Solution sketches
To work around the missing APIs for setting processor ID and priority during thread creation, the std::thread
PR adds a 3DS target specific extension trait std::os::horizon::thread::BuilderExt
behind a feature flag which adds support for setting the processor ID and priority:
/// 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;
}
To implement this, the Builder
struct gets a new crate-private native_options
field whose type is platform specific:
// thread/mod.rs pub struct Builder { // [..]
// Other OS-specific fields. These can be set via extension traits, usually
// found in std::os.
pub(crate) native_options: imp::BuilderOptions,
}
// sys/unix/thread.rs
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,
}
// other platforms pub struct BuilderOptions;
// os/horizon/thread.rs 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
}
}
Since this is a crate/std private field, there is no user facing change to the thread builder outside of the 3DS target. The native_options
fields are used during thread creation, for example in sys/unix/thread.rs
Thread::new
:
impl Thread { pub unsafe fn new( stack: usize, p: Box<dyn FnOnce()>, #[allow(unused)] native_options: BuilderOptions, ) -> io::Result { // [..] } }
Alternatively, we could add this API to all platforms, but I don't think most platforms let you set things like the processor ID during thread creation, so this alternative solution probably wouldn't work.
Links and related work
std::thread
PR for the 3DS, which implements the proposed solution behind a feature flag: rust-lang/rust#98514
What happens now?
This issue is part of the libs-api team API change proposal process. Once this issue is filed the libs-api team will review open proposals in its weekly meeting. You should receive feedback within a week or two.
- https://www.3dbrew.org/wiki/Hardware ↩
- Technically there are APIs which seem to provide this functionality (setting/getting thread core affinity, fields in the kernel's thread representation), but they don't seem to work. Either way, threads don't seem to automatically load balance. ↩