Skip to content

Commit 46a4ec3

Browse files
committed
std: xous: add thread_local_key
Add an implementation of thread local storage. This uses a container that is pointed to by the otherwise-unsed `$tp` register. This container is allocated on-demand, so threads that use no TLS will not allocate this extra memory. Signed-off-by: Sean Cross <[email protected]>
1 parent d36e516 commit 46a4ec3

File tree

2 files changed

+190
-1
lines changed

2 files changed

+190
-1
lines changed

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

-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ pub mod pipe;
2828
pub mod process;
2929
pub mod stdio;
3030
pub mod thread;
31-
#[path = "../unsupported/thread_local_key.rs"]
3231
pub mod thread_local_key;
3332
#[path = "../unsupported/thread_parking.rs"]
3433
pub mod thread_parking;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
use crate::mem::ManuallyDrop;
2+
use crate::ptr;
3+
use crate::sync::atomic::AtomicPtr;
4+
use crate::sync::atomic::AtomicUsize;
5+
use crate::sync::atomic::Ordering::SeqCst;
6+
use core::arch::asm;
7+
8+
use crate::os::xous::ffi::{map_memory, unmap_memory, MemoryFlags};
9+
10+
/// Thread Local Storage
11+
///
12+
/// Currently, we are limited to 1023 TLS entries. The entries
13+
/// live in a page of memory that's unique per-process, and is
14+
/// stored in the `$tp` register. If this register is 0, then
15+
/// TLS has not been initialized and thread cleanup can be skipped.
16+
///
17+
/// The index into this register is the `key`. This key is identical
18+
/// between all threads, but indexes a different offset within this
19+
/// pointer.
20+
pub type Key = usize;
21+
22+
pub type Dtor = unsafe extern "C" fn(*mut u8);
23+
24+
const TLS_MEMORY_SIZE: usize = 4096;
25+
26+
/// TLS keys start at `1` to mimic POSIX.
27+
static TLS_KEY_INDEX: AtomicUsize = AtomicUsize::new(1);
28+
29+
fn tls_ptr_addr() -> *mut usize {
30+
let mut tp: usize;
31+
unsafe {
32+
asm!(
33+
"mv {}, tp",
34+
out(reg) tp,
35+
);
36+
}
37+
core::ptr::from_exposed_addr_mut::<usize>(tp)
38+
}
39+
40+
/// Create an area of memory that's unique per thread. This area will
41+
/// contain all thread local pointers.
42+
fn tls_ptr() -> *mut usize {
43+
let mut tp = tls_ptr_addr();
44+
45+
// If the TP register is `0`, then this thread hasn't initialized
46+
// its TLS yet. Allocate a new page to store this memory.
47+
if tp.is_null() {
48+
tp = unsafe {
49+
map_memory(
50+
None,
51+
None,
52+
TLS_MEMORY_SIZE / core::mem::size_of::<usize>(),
53+
MemoryFlags::R | MemoryFlags::W,
54+
)
55+
}
56+
.expect("Unable to allocate memory for thread local storage")
57+
.as_mut_ptr();
58+
59+
unsafe {
60+
// Key #0 is currently unused.
61+
(tp).write_volatile(0);
62+
63+
// Set the thread's `$tp` register
64+
asm!(
65+
"mv tp, {}",
66+
in(reg) tp as usize,
67+
);
68+
}
69+
}
70+
tp
71+
}
72+
73+
/// Allocate a new TLS key. These keys are shared among all threads.
74+
fn tls_alloc() -> usize {
75+
TLS_KEY_INDEX.fetch_add(1, SeqCst)
76+
}
77+
78+
#[inline]
79+
pub unsafe fn create(dtor: Option<Dtor>) -> Key {
80+
let key = tls_alloc();
81+
if let Some(f) = dtor {
82+
unsafe { register_dtor(key, f) };
83+
}
84+
key
85+
}
86+
87+
#[inline]
88+
pub unsafe fn set(key: Key, value: *mut u8) {
89+
assert!((key < 1022) && (key >= 1));
90+
unsafe { tls_ptr().add(key).write_volatile(value as usize) };
91+
}
92+
93+
#[inline]
94+
pub unsafe fn get(key: Key) -> *mut u8 {
95+
assert!((key < 1022) && (key >= 1));
96+
core::ptr::from_exposed_addr_mut::<u8>(unsafe { tls_ptr().add(key).read_volatile() })
97+
}
98+
99+
#[inline]
100+
pub unsafe fn destroy(_key: Key) {
101+
panic!("can't destroy keys on Xous");
102+
}
103+
104+
// -------------------------------------------------------------------------
105+
// Dtor registration (stolen from Windows)
106+
//
107+
// Xous has no native support for running destructors so we manage our own
108+
// list of destructors to keep track of how to destroy keys. We then install a
109+
// callback later to get invoked whenever a thread exits, running all
110+
// appropriate destructors.
111+
//
112+
// Currently unregistration from this list is not supported. A destructor can be
113+
// registered but cannot be unregistered. There's various simplifying reasons
114+
// for doing this, the big ones being:
115+
//
116+
// 1. Currently we don't even support deallocating TLS keys, so normal operation
117+
// doesn't need to deallocate a destructor.
118+
// 2. There is no point in time where we know we can unregister a destructor
119+
// because it could always be getting run by some remote thread.
120+
//
121+
// Typically processes have a statically known set of TLS keys which is pretty
122+
// small, and we'd want to keep this memory alive for the whole process anyway
123+
// really.
124+
//
125+
// Perhaps one day we can fold the `Box` here into a static allocation,
126+
// expanding the `StaticKey` structure to contain not only a slot for the TLS
127+
// key but also a slot for the destructor queue on windows. An optimization for
128+
// another day!
129+
130+
static DTORS: AtomicPtr<Node> = AtomicPtr::new(ptr::null_mut());
131+
132+
struct Node {
133+
dtor: Dtor,
134+
key: Key,
135+
next: *mut Node,
136+
}
137+
138+
unsafe fn register_dtor(key: Key, dtor: Dtor) {
139+
let mut node = ManuallyDrop::new(Box::new(Node { key, dtor, next: ptr::null_mut() }));
140+
141+
let mut head = DTORS.load(SeqCst);
142+
loop {
143+
node.next = head;
144+
match DTORS.compare_exchange(head, &mut **node, SeqCst, SeqCst) {
145+
Ok(_) => return, // nothing to drop, we successfully added the node to the list
146+
Err(cur) => head = cur,
147+
}
148+
}
149+
}
150+
151+
pub unsafe fn destroy_tls() {
152+
let tp = tls_ptr_addr();
153+
154+
// If the pointer address is 0, then this thread has no TLS.
155+
if tp.is_null() {
156+
return;
157+
}
158+
unsafe { run_dtors() };
159+
160+
// Finally, free the TLS array
161+
unsafe {
162+
unmap_memory(core::slice::from_raw_parts_mut(
163+
tp,
164+
TLS_MEMORY_SIZE / core::mem::size_of::<usize>(),
165+
))
166+
.unwrap()
167+
};
168+
}
169+
170+
unsafe fn run_dtors() {
171+
let mut any_run = true;
172+
for _ in 0..5 {
173+
if !any_run {
174+
break;
175+
}
176+
any_run = false;
177+
let mut cur = DTORS.load(SeqCst);
178+
while !cur.is_null() {
179+
let ptr = unsafe { get((*cur).key) };
180+
181+
if !ptr.is_null() {
182+
unsafe { set((*cur).key, ptr::null_mut()) };
183+
unsafe { ((*cur).dtor)(ptr as *mut _) };
184+
any_run = true;
185+
}
186+
187+
unsafe { cur = (*cur).next };
188+
}
189+
}
190+
}

0 commit comments

Comments
 (0)