Hello everyone. I will continue where I left off in post with the Rust main in this post. Since we now have an allocator, the Thread::new
statement at library/std/src/rt.rs
works. So we need to fix the line where we set the main thread with guard information. This will be a short post since it turned out easier than I initially thought.
Thread Local Macro
The macro thread_local!
wraps any number of static declarations and makes them thread-local. To get a better understanding of what this macro does, we can take a look at this example from the docs:
use std::cell::RefCell;
use std::thread;
thread_local!(static FOO: RefCell<u32> = RefCell::new(1));
FOO.with(|f| {
assert_eq!(*f.borrow(), 1);
*f.borrow_mut() = 2;
});
let t = thread::spawn(move|| {
FOO.with(|f| {
assert_eq!(*f.borrow(), 1);
*f.borrow_mut() = 3;
});
});
t.join().unwrap();
FOO.with(|f| {
assert_eq!(*f.borrow(), 2);
});
As you can see, it allows the creation of static variables local to each thread.
The Problem
The std::sys_common::thread_info
module defines a thread-local variable THREAD_INFO
using the thread_local!
macro. The error occurs when this variable is lazily initialized in the init()
function at rt.rs
.
The Solution
The __thread_local_internal
macro contains conditional compilation for wasm
target without atomics
. Since UEFI is single-threaded (I know there is a way to execute code in other cores, but it’s not exactly true multi-threading, from what I understand), and I don’t have any atomics
(at least not yet), I just decided to use the wasm implementation. This maps to a simple mutable static which should be fine to do for now. The new __thread_local_internal
looks like:
#[doc(hidden)]
#[unstable(feature = "thread_local_internals", reason = "should not be necessary", issue = "none")]
#[macro_export]
#[allow_internal_unstable(thread_local_internals, cfg_target_thread_local, thread_local)]
#[allow_internal_unsafe]
macro_rules! __thread_local_inner {
(@key $t:ty, const $init:expr) => {{
#[cfg_attr(not(windows), inline)] #[deny(unsafe_op_in_unsafe_fn)]
unsafe fn __getit(
_init: $crate::option::Option<&mut $crate::option::Option<$t>>,
) -> $crate::option::Option<&'static $t> {
const INIT_EXPR: $t = $init;
#[cfg(any(all(target_family = "wasm", not(target_feature = "atomics")), target_os = "uefi"))]
{
static mut VAL: $t = INIT_EXPR;
unsafe { $crate::option::Option::Some(&VAL) }
}
#[cfg(all(
target_thread_local,
not(all(target_family = "wasm", not(target_feature = "atomics"))),
not(target_os = "uefi")
))]
{
#[thread_local]
static mut VAL: $t = INIT_EXPR;
if !$crate::mem::needs_drop::<$t>() {
unsafe {
return $crate::option::Option::Some(&VAL)
}
}
#[thread_local]
static mut STATE: $crate::primitive::u8 = 0;
unsafe extern "C" fn destroy(ptr: *mut $crate::primitive::u8) {
let ptr = ptr as *mut $t;
unsafe {
$crate::debug_assert_eq!(STATE, 1);
STATE = 2;
$crate::ptr::drop_in_place(ptr);
}
}
unsafe {
match STATE {
0 => {
$crate::thread::__FastLocalKeyInner::<$t>::register_dtor(
$crate::ptr::addr_of_mut!(VAL) as *mut $crate::primitive::u8,
destroy,
);
STATE = 1;
$crate::option::Option::Some(&VAL)
}
1 => $crate::option::Option::Some(&VAL),
_ => $crate::option::Option::None,
}
}
}
#[cfg(all(
not(target_thread_local),
not(all(target_family = "wasm", not(target_feature = "atomics"))),
not(target_os = "uefi")
))]
{
#[inline]
const fn __init() -> $t { INIT_EXPR }
static __KEY: $crate::thread::__OsLocalKeyInner<$t> =
$crate::thread::__OsLocalKeyInner::new();
#[allow(unused_unsafe)]
unsafe {
__KEY.get(move || {
if let $crate::option::Option::Some(init) = _init {
if let $crate::option::Option::Some(value) = init.take() {
return value;
} else if $crate::cfg!(debug_assertions) {
$crate::unreachable!("missing initial value");
}
}
__init()
})
}
}
}
unsafe {
$crate::thread::LocalKey::new(__getit)
}
}};
(@key $t:ty, $init:expr) => {
{
#[inline]
fn __init() -> $t { $init }
#[cfg_attr(not(windows), inline)]
unsafe fn __getit(
init: $crate::option::Option<&mut $crate::option::Option<$t>>,
) -> $crate::option::Option<&'static $t> {
#[cfg(any(all(target_family = "wasm", not(target_feature = "atomics")), target_os = "uefi"))]
static __KEY: $crate::thread::__StaticLocalKeyInner<$t> =
$crate::thread::__StaticLocalKeyInner::new();
#[thread_local]
#[cfg(all(
target_thread_local,
not(all(target_family = "wasm", not(target_feature = "atomics"))),
not(target_os = "uefi")
))]
static __KEY: $crate::thread::__FastLocalKeyInner<$t> =
$crate::thread::__FastLocalKeyInner::new();
#[cfg(all(
not(target_thread_local),
not(all(target_family = "wasm", not(target_feature = "atomics"))),
not(target_os = "uefi")
))]
static __KEY: $crate::thread::__OsLocalKeyInner<$t> =
$crate::thread::__OsLocalKeyInner::new();
#[allow(unused_unsafe)]
unsafe {
__KEY.get(move || {
if let $crate::option::Option::Some(init) = init {
if let $crate::option::Option::Some(value) = init.take() {
return value;
} else if $crate::cfg!(debug_assertions) {
$crate::unreachable!("missing default value");
}
}
__init()
})
}
}
unsafe {
$crate::thread::LocalKey::new(__getit)
}
}
};
($(#[$attr:meta])* $vis:vis $name:ident, $t:ty, $($init:tt)*) => {
$(#[$attr])* $vis const $name: $crate::thread::LocalKey<$t> =
$crate::__thread_local_inner!(@key $t, $($init)*);
}
}
We will also need to add target_os = "uefi"
to conditional compilation of std::thread::__StaticLocalKeyInner
and std::thread::local::statik
.
After that, it works perfectly. I’m not sure if this is the correct implementation, but it also fixes stdio (which I will implement in the next post) for me, so I think it is acceptable for now. However, anyone who understands this better is free to contact me through mail.
Conclusion
As I stated earlier, this is a pretty short post. While I could post an empty main
function, it’s useless without having the ability to print to screen from main()
. So this is where I will conclude for now. I promise we will print “Hello World!” from main()
next time.
Consider supporting me if you like my work.