xref: /drstd/src/std/sys/sgx/thread.rs (revision 0fe3ff0054d3aec7fbf9bddecfecb10bc7d23a51)
1 #![cfg_attr(test, allow(dead_code))] // why is this necessary?
2 use super::unsupported;
3 use crate::std::ffi::CStr;
4 use crate::std::io;
5 use crate::std::num::NonZeroUsize;
6 use crate::std::time::Duration;
7 
8 use super::abi::usercalls;
9 
10 pub struct Thread(task_queue::JoinHandle);
11 
12 pub const DEFAULT_MIN_STACK_SIZE: usize = 4096;
13 
14 pub use self::task_queue::JoinNotifier;
15 
16 mod task_queue {
17     use super::wait_notify;
18     use crate::std::sync::{Mutex, MutexGuard, Once};
19 
20     pub type JoinHandle = wait_notify::Waiter;
21 
22     pub struct JoinNotifier(Option<wait_notify::Notifier>);
23 
24     impl Drop for JoinNotifier {
25         fn drop(&mut self) {
26             self.0.take().unwrap().notify();
27         }
28     }
29 
30     pub(super) struct Task {
31         p: Box<dyn FnOnce()>,
32         done: JoinNotifier,
33     }
34 
35     impl Task {
36         pub(super) fn new(p: Box<dyn FnOnce()>) -> (Task, JoinHandle) {
37             let (done, recv) = wait_notify::new();
38             let done = JoinNotifier(Some(done));
39             (Task { p, done }, recv)
40         }
41 
42         pub(super) fn run(self) -> JoinNotifier {
43             (self.p)();
44             self.done
45         }
46     }
47 
48     #[cfg_attr(test, linkage = "available_externally")]
49     #[export_name = "_ZN16__rust_internals3std3sys3sgx6thread15TASK_QUEUE_INITE"]
50     static TASK_QUEUE_INIT: Once = Once::new();
51     #[cfg_attr(test, linkage = "available_externally")]
52     #[export_name = "_ZN16__rust_internals3std3sys3sgx6thread10TASK_QUEUEE"]
53     static mut TASK_QUEUE: Option<Mutex<Vec<Task>>> = None;
54 
55     pub(super) fn lock() -> MutexGuard<'static, Vec<Task>> {
56         unsafe {
57             TASK_QUEUE_INIT.call_once(|| TASK_QUEUE = Some(Default::default()));
58             TASK_QUEUE.as_ref().unwrap().lock().unwrap()
59         }
60     }
61 }
62 
63 /// This module provides a synchronization primitive that does not use thread
64 /// local variables. This is needed for signaling that a thread has finished
65 /// execution. The signal is sent once all TLS destructors have finished at
66 /// which point no new thread locals should be created.
67 pub mod wait_notify {
68     use crate::std::pin::Pin;
69     use crate::std::sync::Arc;
70     use crate::std::sys_common::thread_parking::Parker;
71 
72     pub struct Notifier(Arc<Parker>);
73 
74     impl Notifier {
75         /// Notify the waiter. The waiter is either notified right away (if
76         /// currently blocked in `Waiter::wait()`) or later when it calls the
77         /// `Waiter::wait()` method.
78         pub fn notify(self) {
79             Pin::new(&*self.0).unpark()
80         }
81     }
82 
83     pub struct Waiter(Arc<Parker>);
84 
85     impl Waiter {
86         /// Wait for a notification. If `Notifier::notify()` has already been
87         /// called, this will return immediately, otherwise the current thread
88         /// is blocked until notified.
89         pub fn wait(self) {
90             // SAFETY:
91             // This is only ever called on one thread.
92             unsafe { Pin::new(&*self.0).park() }
93         }
94     }
95 
96     pub fn new() -> (Notifier, Waiter) {
97         let inner = Arc::new(Parker::new());
98         (Notifier(inner.clone()), Waiter(inner))
99     }
100 }
101 
102 impl Thread {
103     // unsafe: see thread::Builder::spawn_unchecked for safety requirements
104     pub unsafe fn new(_stack: usize, p: Box<dyn FnOnce()>) -> io::Result<Thread> {
105         let mut queue_lock = task_queue::lock();
106         unsafe { usercalls::launch_thread()? };
107         let (task, handle) = task_queue::Task::new(p);
108         queue_lock.push(task);
109         Ok(Thread(handle))
110     }
111 
112     pub(super) fn entry() -> JoinNotifier {
113         let mut pending_tasks = task_queue::lock();
114         let task = rtunwrap!(Some, pending_tasks.pop());
115         drop(pending_tasks); // make sure to not hold the task queue lock longer than necessary
116         task.run()
117     }
118 
119     pub fn yield_now() {
120         let wait_error = rtunwrap!(Err, usercalls::wait(0, usercalls::raw::WAIT_NO));
121         rtassert!(wait_error.kind() == io::ErrorKind::WouldBlock);
122     }
123 
124     /// SGX should protect in-enclave data from the outside (attacker),
125     /// so there should be no data leakage to the OS,
126     /// and therefore also no 1-1 mapping between SGX thread names and OS thread names.
127     ///
128     /// This is why the method is intentionally No-Op.
129     pub fn set_name(_name: &CStr) {
130         // Note that the internally visible SGX thread name is already provided
131         // by the platform-agnostic (target-agnostic) Rust thread code.
132         // This can be observed in the [`std::thread::tests::test_named_thread`] test,
133         // which succeeds as-is with the SGX target.
134     }
135 
136     pub fn sleep(dur: Duration) {
137         usercalls::wait_timeout(0, dur, || true);
138     }
139 
140     pub fn join(self) {
141         self.0.wait();
142     }
143 }
144 
145 pub fn available_parallelism() -> io::Result<NonZeroUsize> {
146     unsupported()
147 }
148 
149 pub mod guard {
150     pub type Guard = !;
151     pub unsafe fn current() -> Option<Guard> {
152         None
153     }
154     pub unsafe fn init() -> Option<Guard> {
155         None
156     }
157 }
158