]>
Commit | Line | Data |
---|---|---|
1a4d82fc JJ |
1 | // Copyright 2014 The Rust Project Developers. See the COPYRIGHT |
2 | // file at the top-level directory of this distribution and at | |
3 | // http://rust-lang.org/COPYRIGHT. | |
4 | // | |
5 | // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | |
6 | // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license | |
7 | // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your | |
8 | // option. This file may not be copied, modified, or distributed | |
9 | // except according to those terms. | |
10 | ||
11 | use prelude::v1::*; | |
12 | ||
85aaf69f | 13 | use ptr; |
92a42be0 | 14 | use sys::c; |
62682a34 | 15 | use sys_common::mutex::Mutex; |
92a42be0 | 16 | use sys_common; |
1a4d82fc | 17 | |
92a42be0 | 18 | pub type Key = c::DWORD; |
1a4d82fc JJ |
19 | pub type Dtor = unsafe extern fn(*mut u8); |
20 | ||
21 | // Turns out, like pretty much everything, Windows is pretty close the | |
22 | // functionality that Unix provides, but slightly different! In the case of | |
23 | // TLS, Windows does not provide an API to provide a destructor for a TLS | |
24 | // variable. This ends up being pretty crucial to this implementation, so we | |
25 | // need a way around this. | |
26 | // | |
27 | // The solution here ended up being a little obscure, but fear not, the | |
28 | // internet has informed me [1][2] that this solution is not unique (no way | |
29 | // I could have thought of it as well!). The key idea is to insert some hook | |
30 | // somewhere to run arbitrary code on thread termination. With this in place | |
31 | // we'll be able to run anything we like, including all TLS destructors! | |
32 | // | |
bd371182 | 33 | // To accomplish this feat, we perform a number of threads, all contained |
1a4d82fc JJ |
34 | // within this module: |
35 | // | |
36 | // * All TLS destructors are tracked by *us*, not the windows runtime. This | |
37 | // means that we have a global list of destructors for each TLS key that | |
38 | // we know about. | |
39 | // * When a TLS key is destroyed, we're sure to remove it from the dtor list | |
40 | // if it's in there. | |
41 | // * When a thread exits, we run over the entire list and run dtors for all | |
42 | // non-null keys. This attempts to match Unix semantics in this regard. | |
43 | // | |
44 | // This ends up having the overhead of using a global list, having some | |
45 | // locks here and there, and in general just adding some more code bloat. We | |
46 | // attempt to optimize runtime by forgetting keys that don't have | |
47 | // destructors, but this only gets us so far. | |
48 | // | |
49 | // For more details and nitty-gritty, see the code sections below! | |
50 | // | |
51 | // [1]: http://www.codeproject.com/Articles/8113/Thread-Local-Storage-The-C-Way | |
52 | // [2]: https://github.com/ChromiumWebApps/chromium/blob/master/base | |
53 | // /threading/thread_local_storage_win.cc#L42 | |
54 | ||
55 | // NB these are specifically not types from `std::sync` as they currently rely | |
56 | // on poisoning and this module needs to operate at a lower level than requiring | |
57 | // the thread infrastructure to be in place (useful on the borders of | |
58 | // initialization/destruction). | |
62682a34 | 59 | static DTOR_LOCK: Mutex = Mutex::new(); |
e9174d1e | 60 | static mut DTORS: *mut Vec<(Key, Dtor)> = ptr::null_mut(); |
1a4d82fc JJ |
61 | |
62 | // ------------------------------------------------------------------------- | |
63 | // Native bindings | |
64 | // | |
65 | // This section is just raw bindings to the native functions that Windows | |
66 | // provides, There's a few extra calls to deal with destructors. | |
67 | ||
68 | #[inline] | |
69 | pub unsafe fn create(dtor: Option<Dtor>) -> Key { | |
92a42be0 SL |
70 | let key = c::TlsAlloc(); |
71 | assert!(key != c::TLS_OUT_OF_INDEXES); | |
1a4d82fc JJ |
72 | match dtor { |
73 | Some(f) => register_dtor(key, f), | |
74 | None => {} | |
75 | } | |
76 | return key; | |
77 | } | |
78 | ||
79 | #[inline] | |
80 | pub unsafe fn set(key: Key, value: *mut u8) { | |
92a42be0 | 81 | let r = c::TlsSetValue(key, value as c::LPVOID); |
1a4d82fc JJ |
82 | debug_assert!(r != 0); |
83 | } | |
84 | ||
85 | #[inline] | |
86 | pub unsafe fn get(key: Key) -> *mut u8 { | |
92a42be0 | 87 | c::TlsGetValue(key) as *mut u8 |
1a4d82fc JJ |
88 | } |
89 | ||
90 | #[inline] | |
91 | pub unsafe fn destroy(key: Key) { | |
92 | if unregister_dtor(key) { | |
93 | // FIXME: Currently if a key has a destructor associated with it we | |
94 | // can't actually ever unregister it. If we were to | |
95 | // unregister it, then any key destruction would have to be | |
96 | // serialized with respect to actually running destructors. | |
97 | // | |
98 | // We want to avoid a race where right before run_dtors runs | |
99 | // some destructors TlsFree is called. Allowing the call to | |
100 | // TlsFree would imply that the caller understands that *all | |
101 | // known threads* are not exiting, which is quite a difficult | |
102 | // thing to know! | |
103 | // | |
104 | // For now we just leak all keys with dtors to "fix" this. | |
105 | // Note that source [2] above shows precedent for this sort | |
106 | // of strategy. | |
107 | } else { | |
92a42be0 | 108 | let r = c::TlsFree(key); |
1a4d82fc JJ |
109 | debug_assert!(r != 0); |
110 | } | |
111 | } | |
112 | ||
1a4d82fc JJ |
113 | // ------------------------------------------------------------------------- |
114 | // Dtor registration | |
115 | // | |
116 | // These functions are associated with registering and unregistering | |
117 | // destructors. They're pretty simple, they just push onto a vector and scan | |
118 | // a vector currently. | |
119 | // | |
120 | // FIXME: This could probably be at least a little faster with a BTree. | |
121 | ||
122 | unsafe fn init_dtors() { | |
123 | if !DTORS.is_null() { return } | |
124 | ||
125 | let dtors = box Vec::<(Key, Dtor)>::new(); | |
1a4d82fc | 126 | |
e9174d1e | 127 | let res = sys_common::at_exit(move|| { |
1a4d82fc JJ |
128 | DTOR_LOCK.lock(); |
129 | let dtors = DTORS; | |
c34b1796 AL |
130 | DTORS = 1 as *mut _; |
131 | Box::from_raw(dtors); | |
132 | assert!(DTORS as usize == 1); // can't re-init after destructing | |
1a4d82fc JJ |
133 | DTOR_LOCK.unlock(); |
134 | }); | |
c34b1796 | 135 | if res.is_ok() { |
62682a34 | 136 | DTORS = Box::into_raw(dtors); |
c34b1796 AL |
137 | } else { |
138 | DTORS = 1 as *mut _; | |
139 | } | |
1a4d82fc JJ |
140 | } |
141 | ||
142 | unsafe fn register_dtor(key: Key, dtor: Dtor) { | |
143 | DTOR_LOCK.lock(); | |
144 | init_dtors(); | |
c34b1796 AL |
145 | assert!(DTORS as usize != 0); |
146 | assert!(DTORS as usize != 1, | |
147 | "cannot create new TLS keys after the main thread has exited"); | |
1a4d82fc JJ |
148 | (*DTORS).push((key, dtor)); |
149 | DTOR_LOCK.unlock(); | |
150 | } | |
151 | ||
152 | unsafe fn unregister_dtor(key: Key) -> bool { | |
153 | DTOR_LOCK.lock(); | |
154 | init_dtors(); | |
c34b1796 AL |
155 | assert!(DTORS as usize != 0); |
156 | assert!(DTORS as usize != 1, | |
157 | "cannot unregister destructors after the main thread has exited"); | |
1a4d82fc JJ |
158 | let ret = { |
159 | let dtors = &mut *DTORS; | |
160 | let before = dtors.len(); | |
161 | dtors.retain(|&(k, _)| k != key); | |
162 | dtors.len() != before | |
163 | }; | |
164 | DTOR_LOCK.unlock(); | |
165 | ret | |
166 | } | |
167 | ||
168 | // ------------------------------------------------------------------------- | |
169 | // Where the Magic (TM) Happens | |
170 | // | |
171 | // If you're looking at this code, and wondering "what is this doing?", | |
172 | // you're not alone! I'll try to break this down step by step: | |
173 | // | |
174 | // # What's up with CRT$XLB? | |
175 | // | |
176 | // For anything about TLS destructors to work on Windows, we have to be able | |
177 | // to run *something* when a thread exits. To do so, we place a very special | |
178 | // static in a very special location. If this is encoded in just the right | |
179 | // way, the kernel's loader is apparently nice enough to run some function | |
180 | // of ours whenever a thread exits! How nice of the kernel! | |
181 | // | |
182 | // Lots of detailed information can be found in source [1] above, but the | |
183 | // gist of it is that this is leveraging a feature of Microsoft's PE format | |
184 | // (executable format) which is not actually used by any compilers today. | |
185 | // This apparently translates to any callbacks in the ".CRT$XLB" section | |
186 | // being run on certain events. | |
187 | // | |
188 | // So after all that, we use the compiler's #[link_section] feature to place | |
189 | // a callback pointer into the magic section so it ends up being called. | |
190 | // | |
191 | // # What's up with this callback? | |
192 | // | |
193 | // The callback specified receives a number of parameters from... someone! | |
85aaf69f | 194 | // (the kernel? the runtime? I'm not quite sure!) There are a few events that |
1a4d82fc JJ |
195 | // this gets invoked for, but we're currently only interested on when a |
196 | // thread or a process "detaches" (exits). The process part happens for the | |
197 | // last thread and the thread part happens for any normal thread. | |
198 | // | |
199 | // # Ok, what's up with running all these destructors? | |
200 | // | |
201 | // This will likely need to be improved over time, but this function | |
202 | // attempts a "poor man's" destructor callback system. To do this we clone a | |
203 | // local copy of the dtor list to start out with. This is our fudgy attempt | |
204 | // to not hold the lock while destructors run and not worry about the list | |
205 | // changing while we're looking at it. | |
206 | // | |
207 | // Once we've got a list of what to run, we iterate over all keys, check | |
208 | // their values, and then run destructors if the values turn out to be non | |
209 | // null (setting them to null just beforehand). We do this a few times in a | |
210 | // loop to basically match Unix semantics. If we don't reach a fixed point | |
211 | // after a short while then we just inevitably leak something most likely. | |
212 | // | |
213 | // # The article mentions crazy stuff about "/INCLUDE"? | |
214 | // | |
e9174d1e SL |
215 | // It sure does! Specifically we're talking about this quote: |
216 | // | |
217 | // The Microsoft run-time library facilitates this process by defining a | |
218 | // memory image of the TLS Directory and giving it the special name | |
219 | // “__tls_used” (Intel x86 platforms) or “_tls_used” (other platforms). The | |
220 | // linker looks for this memory image and uses the data there to create the | |
221 | // TLS Directory. Other compilers that support TLS and work with the | |
222 | // Microsoft linker must use this same technique. | |
223 | // | |
224 | // Basically what this means is that if we want support for our TLS | |
225 | // destructors/our hook being called then we need to make sure the linker does | |
226 | // not omit this symbol. Otherwise it will omit it and our callback won't be | |
227 | // wired up. | |
228 | // | |
229 | // We don't actually use the `/INCLUDE` linker flag here like the article | |
230 | // mentions because the Rust compiler doesn't propagate linker flags, but | |
231 | // instead we use a shim function which performs a volatile 1-byte load from | |
232 | // the address of the symbol to ensure it sticks around. | |
1a4d82fc JJ |
233 | |
234 | #[link_section = ".CRT$XLB"] | |
235 | #[linkage = "external"] | |
236 | #[allow(warnings)] | |
92a42be0 SL |
237 | pub static p_thread_callback: unsafe extern "system" fn(c::LPVOID, c::DWORD, |
238 | c::LPVOID) = | |
1a4d82fc JJ |
239 | on_tls_callback; |
240 | ||
241 | #[allow(warnings)] | |
92a42be0 SL |
242 | unsafe extern "system" fn on_tls_callback(h: c::LPVOID, |
243 | dwReason: c::DWORD, | |
244 | pv: c::LPVOID) { | |
245 | if dwReason == c::DLL_THREAD_DETACH || dwReason == c::DLL_PROCESS_DETACH { | |
1a4d82fc JJ |
246 | run_dtors(); |
247 | } | |
e9174d1e SL |
248 | |
249 | // See comments above for what this is doing. Note that we don't need this | |
250 | // trickery on GNU windows, just on MSVC. | |
251 | reference_tls_used(); | |
252 | #[cfg(target_env = "msvc")] | |
253 | unsafe fn reference_tls_used() { | |
254 | extern { static _tls_used: u8; } | |
255 | ::intrinsics::volatile_load(&_tls_used); | |
256 | } | |
257 | #[cfg(not(target_env = "msvc"))] | |
258 | unsafe fn reference_tls_used() {} | |
1a4d82fc JJ |
259 | } |
260 | ||
85aaf69f | 261 | #[allow(dead_code)] // actually called above |
1a4d82fc JJ |
262 | unsafe fn run_dtors() { |
263 | let mut any_run = true; | |
85aaf69f | 264 | for _ in 0..5 { |
1a4d82fc JJ |
265 | if !any_run { break } |
266 | any_run = false; | |
267 | let dtors = { | |
268 | DTOR_LOCK.lock(); | |
c34b1796 | 269 | let ret = if DTORS as usize <= 1 { |
1a4d82fc JJ |
270 | Vec::new() |
271 | } else { | |
272 | (*DTORS).iter().map(|s| *s).collect() | |
273 | }; | |
274 | DTOR_LOCK.unlock(); | |
275 | ret | |
276 | }; | |
85aaf69f | 277 | for &(key, dtor) in &dtors { |
92a42be0 | 278 | let ptr = c::TlsGetValue(key); |
1a4d82fc | 279 | if !ptr.is_null() { |
92a42be0 | 280 | c::TlsSetValue(key, ptr::null_mut()); |
1a4d82fc JJ |
281 | dtor(ptr as *mut _); |
282 | any_run = true; | |
283 | } | |
284 | } | |
285 | } | |
286 | } |