Skip to content

Commit 93d7a5f

Browse files
committed
Support thread-local-storage
1 parent a85a320 commit 93d7a5f

File tree

8 files changed

+201
-10
lines changed

8 files changed

+201
-10
lines changed

library/std/src/os/custom/mod.rs

+7
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
//! - Initially, locking primitives are functional and behave as spinlocks.
2727
//! - Before `os::set_impl` has been called, any call to `abort_internal`
2828
//! will result in an infinite loop.
29+
//! - Before `thread::set_impl` has been called, `None` is used where
30+
//! `current_thread_id` would be called.
2931
//!
3032
//! These should be set/changed as soon as possible, as they are used internally.
3133
//!
@@ -272,6 +274,8 @@ pub mod thread {
272274

273275
static_rwlock_box_impl!(ThreadManager);
274276

277+
pub type ThreadId = usize;
278+
275279
/// Platform-specific management of threads
276280
pub trait ThreadManager: Send + Sync {
277281
/// unsafe: see thread::Builder::spawn_unchecked for safety requirements
@@ -282,6 +286,9 @@ pub mod thread {
282286
fn join(&self, thread: &Thread);
283287
fn available_parallelism(&self) -> io::Result<NonZeroUsize>;
284288

289+
/// Must return None for the initial thread
290+
fn current_thread_id(&self) -> Option<ThreadId>;
291+
285292
// todo: thread parking
286293
}
287294
}

library/std/src/sync/mutex.rs

+15
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,21 @@ impl<T: ?Sized> Mutex<T> {
275275
}
276276
}
277277

278+
/// On the "custom" platform, allocator and thread local storage
279+
/// implementations use Mutex & RwLock, sometimes while a panic is
280+
/// being handled.
281+
///
282+
/// The poison checks that these primitives do are deadlock sources
283+
/// in these cases; to prevent these deadlocks, it is preferable to
284+
/// bypass the poison checks.
285+
#[cfg(target_os = "custom")]
286+
#[stable(feature = "rust1", since = "1.0.0")]
287+
pub(crate) fn lock_no_poison_check(&self) -> MutexGuard<'_, T> {
288+
self.inner.lock();
289+
let poison = poison::Guard::no_check();
290+
MutexGuard { lock: self, poison }
291+
}
292+
278293
/// Attempts to acquire this lock.
279294
///
280295
/// If the lock could not be acquired at this time, then [`Err`] is returned.

library/std/src/sync/poison.rs

+17
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,23 @@ pub struct Guard {
5959
panicking: bool,
6060
}
6161

62+
#[cfg(target_os = "custom")]
63+
impl Guard {
64+
/// On the "custom" platform, allocator and thread local storage
65+
/// implementations use Mutex & RwLock, sometimes while a panic is
66+
/// being handled.
67+
///
68+
/// The poison checks that these primitives do are deadlock sources
69+
/// in these cases; to prevent these deadlocks, it is preferable to
70+
/// bypass the poison checks.
71+
pub fn no_check() -> Self {
72+
// by setting `panicking` to true, `poison::Flag::done` doesn't call `thread::panicking`
73+
Self {
74+
panicking: true,
75+
}
76+
}
77+
}
78+
6279
/// A type of error which can be returned whenever a lock is acquired.
6380
///
6481
/// Both [`Mutex`]es and [`RwLock`]s are poisoned whenever a thread fails while the lock

library/std/src/sync/rwlock.rs

+36
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,26 @@ impl<T: ?Sized> RwLock<T> {
212212
}
213213
}
214214

215+
/// On the "custom" platform, allocator and thread local storage
216+
/// implementations use Mutex & RwLock, sometimes while a panic is
217+
/// being handled.
218+
///
219+
/// The poison checks that these primitives do are deadlock sources
220+
/// in these cases; to prevent these deadlocks, it is preferable to
221+
/// bypass the poison checks.
222+
#[inline]
223+
#[cfg(target_os = "custom")]
224+
#[stable(feature = "rust1", since = "1.0.0")]
225+
pub(crate) fn read_no_poison_check(&self) -> RwLockReadGuard<'_, T> {
226+
unsafe {
227+
self.inner.read();
228+
RwLockReadGuard {
229+
data: NonNull::new_unchecked(self.data.get()),
230+
inner_lock: &self.inner,
231+
}
232+
}
233+
}
234+
215235
/// Attempts to acquire this `RwLock` with shared read access.
216236
///
217237
/// If the access could not be granted at this time, then `Err` is returned.
@@ -300,6 +320,22 @@ impl<T: ?Sized> RwLock<T> {
300320
}
301321
}
302322

323+
/// On the "custom" platform, allocator and thread local storage
324+
/// implementations use Mutex & RwLock, sometimes while a panic is
325+
/// being handled.
326+
///
327+
/// The poison checks that these primitives do are deadlock sources
328+
/// in these cases; to prevent these deadlocks, it is preferable to
329+
/// bypass the poison checks.
330+
#[inline]
331+
#[cfg(target_os = "custom")]
332+
#[stable(feature = "rust1", since = "1.0.0")]
333+
pub(crate) fn write_no_poison_check(&self) -> RwLockWriteGuard<'_, T> {
334+
self.inner.write();
335+
let poison = poison::Guard::no_check();
336+
RwLockWriteGuard { lock: self, poison }
337+
}
338+
303339
/// Attempts to lock this `RwLock` with exclusive write access.
304340
///
305341
/// If the lock could not be acquired at this time, then `Err` is returned.

library/std/src/sys/common/thread_local/os_local.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use crate::{fmt, marker, panic, ptr};
1111
pub macro thread_local_inner {
1212
// used to generate the `LocalKey` value for const-initialized thread locals
1313
(@key $t:ty, const $init:expr) => {{
14-
#[inline]
14+
#[cfg_attr(not(target_os = "custom"), inline)]
1515
#[deny(unsafe_op_in_unsafe_fn)]
1616
unsafe fn __getit(
1717
_init: $crate::option::Option<&mut $crate::option::Option<$t>>,
@@ -20,7 +20,7 @@ pub macro thread_local_inner {
2020

2121
// On platforms without `#[thread_local]` we fall back to the
2222
// same implementation as below for os thread locals.
23-
#[inline]
23+
#[cfg_attr(not(target_os = "custom"), inline)]
2424
const fn __init() -> $t { INIT_EXPR }
2525
static __KEY: $crate::thread::local_impl::Key<$t> =
2626
$crate::thread::local_impl::Key::new();
@@ -46,12 +46,12 @@ pub macro thread_local_inner {
4646
// used to generate the `LocalKey` value for `thread_local!`
4747
(@key $t:ty, $init:expr) => {
4848
{
49-
#[inline]
49+
#[cfg_attr(not(target_os = "custom"), inline)]
5050
fn __init() -> $t { $init }
5151

5252
// `#[inline] does not work on windows-gnu due to linking errors around dllimports.
5353
// See https://github.com/rust-lang/rust/issues/109797.
54-
#[cfg_attr(not(windows), inline)]
54+
#[cfg_attr(all(not(windows), not(target_os = "custom")), inline)]
5555
unsafe fn __getit(
5656
init: $crate::option::Option<&mut $crate::option::Option<$t>>,
5757
) -> $crate::option::Option<&'static $t> {

library/std/src/sys/custom/alloc.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ fn encode_slot(i: usize, len: usize, next: usize) {
125125
fn free(i: usize, len: usize) {
126126
assert!(len >= 2);
127127

128-
let mut first_slot = FIRST_SLOT.lock().unwrap();
128+
let mut first_slot = FIRST_SLOT.lock_no_poison_check();
129129
encode_slot(i, len, *first_slot);
130130
*first_slot = i;
131131
}
@@ -164,7 +164,7 @@ unsafe impl GlobalAlloc for DefaultAlloc {
164164
let (req_align, req_size) = prepare_layout(layout);
165165

166166
let (mut filler, leftover);
167-
let mut first_slot = FIRST_SLOT.lock().unwrap();
167+
let mut first_slot = FIRST_SLOT.lock_no_poison_check();
168168
let mut i = *first_slot;
169169
let mut prev = None;
170170

library/std/src/sys/custom/mod.rs

+3-4
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,6 @@ pub mod args;
2424
#[path = "../unsupported/once.rs"]
2525
pub mod once;
2626

27-
// "custom" doesn't support thread local storage
28-
#[path = "../unsupported/thread_local_key.rs"]
29-
pub mod thread_local_key;
30-
3127
#[path = "../unsupported/common.rs"]
3228
#[deny(unsafe_op_in_unsafe_fn)]
3329
#[allow(unused)]
@@ -47,6 +43,9 @@ pub mod thread;
4743
pub mod thread_parking;
4844
pub mod time;
4945

46+
// really bad implementation
47+
pub mod thread_local_key;
48+
5049
pub fn decode_error_kind(errno: i32) -> std_io::ErrorKind {
5150
custom_os_impl!(os, decode_error_kind, errno)
5251
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
use crate::sync::{RwLock, Mutex};
2+
use crate::os::custom::thread::{ThreadId, IMPL};
3+
4+
pub type Key = usize;
5+
6+
const NULL: *mut u8 = core::ptr::null_mut();
7+
8+
type Destructor = Option<unsafe extern "C" fn(*mut u8)>;
9+
10+
fn thread_id() -> Option<ThreadId> {
11+
let reader = IMPL.read_no_poison_check();
12+
reader.as_ref()?.current_thread_id()
13+
}
14+
15+
// safety: each ThreadStorage is virtually owned by one thread
16+
unsafe impl Sync for ThreadStorage {}
17+
unsafe impl Send for ThreadStorage {}
18+
19+
struct ThreadStorage(Mutex<Vec<*mut u8>>);
20+
21+
impl ThreadStorage {
22+
pub const fn new() -> Self {
23+
Self(Mutex::new(Vec::new()))
24+
}
25+
26+
pub fn get(&self, key: Key) -> *mut u8 {
27+
let locked = self.0.lock_no_poison_check();
28+
match locked.get(key - 1) {
29+
Some(pointer_ref) => *pointer_ref,
30+
None => NULL,
31+
}
32+
}
33+
34+
pub fn set(&self, key: Key, value: *mut u8) {
35+
let mut locked = self.0.lock_no_poison_check();
36+
locked.resize(key, NULL);
37+
locked[key - 1] = value;
38+
}
39+
}
40+
41+
struct Slots {
42+
destructors: RwLock<Vec<Destructor>>,
43+
initial_thread: ThreadStorage,
44+
other_threads: RwLock<Vec<(ThreadId, ThreadStorage)>>,
45+
}
46+
47+
impl Slots {
48+
pub const fn new() -> Self {
49+
Self {
50+
destructors: RwLock::new(Vec::new()),
51+
initial_thread: ThreadStorage::new(),
52+
other_threads: RwLock::new(Vec::new()),
53+
}
54+
}
55+
56+
pub fn push(&self, destructor: Destructor) -> Key {
57+
let mut destructors = self.destructors.write_no_poison_check();
58+
destructors.push(destructor);
59+
destructors.len()
60+
}
61+
62+
pub fn get_dtor(&self, key: Key) -> Destructor {
63+
let destructors = self.destructors.read_no_poison_check();
64+
destructors[key - 1]
65+
}
66+
67+
pub fn with_thread_storage<T, F: FnOnce(&ThreadStorage) -> T>(&self, callback: F) -> T {
68+
if let Some(id) = thread_id() {
69+
loop {
70+
let finder = |(tid, _): &(ThreadId, ThreadStorage)| tid.cmp(&id);
71+
72+
{
73+
let other_threads = self.other_threads.read_no_poison_check();
74+
let position = other_threads.binary_search_by(finder);
75+
76+
if let Ok(i) = position {
77+
return callback(&other_threads[i].1);
78+
}
79+
}
80+
81+
// cannot re-use `position` (race condition)
82+
let mut other_threads = self.other_threads.write_no_poison_check();
83+
let i = other_threads.binary_search_by(finder).unwrap_err();
84+
other_threads.insert(i, (id, ThreadStorage::new()));
85+
}
86+
} else {
87+
callback(&self.initial_thread)
88+
}
89+
}
90+
}
91+
92+
static SLOTS: Slots = Slots::new();
93+
94+
#[inline]
95+
pub unsafe fn create(dtor: Option<unsafe extern "C" fn(*mut u8)>) -> Key {
96+
SLOTS.push(dtor)
97+
}
98+
99+
#[inline]
100+
pub unsafe fn set(key: Key, value: *mut u8) {
101+
SLOTS.with_thread_storage(|storage| storage.set(key, value))
102+
}
103+
104+
#[inline]
105+
pub unsafe fn get(key: Key) -> *mut u8 {
106+
SLOTS.with_thread_storage(|storage| storage.get(key))
107+
}
108+
109+
#[inline]
110+
pub unsafe fn destroy(key: Key) {
111+
let value = get(key);
112+
if !value.is_null() {
113+
if let Some(destructor) = SLOTS.get_dtor(key) {
114+
destructor(value)
115+
}
116+
}
117+
}

0 commit comments

Comments
 (0)