@@ -96,8 +96,8 @@ fn get_stack_limit() -> Option<usize> {
96
96
STACK_LIMIT . with ( |s| s. get ( ) )
97
97
}
98
98
99
- fn set_stack_limit ( l : usize ) {
100
- STACK_LIMIT . with ( |s| s. set ( Some ( l ) ) )
99
+ fn set_stack_limit ( l : Option < usize > ) {
100
+ STACK_LIMIT . with ( |s| s. set ( l ) )
101
101
}
102
102
103
103
cfg_if ! {
@@ -120,9 +120,7 @@ cfg_if! {
120
120
unsafe {
121
121
libc:: munmap( self . map, self . stack_size) ;
122
122
}
123
- if let Some ( limit) = self . old_stack_limit {
124
- set_stack_limit( limit) ;
125
- }
123
+ set_stack_limit( self . old_stack_limit) ;
126
124
}
127
125
}
128
126
@@ -174,7 +172,7 @@ cfg_if! {
174
172
let stack_low = map as usize ;
175
173
176
174
// Prepare stack limits for the stack switch
177
- set_stack_limit( stack_low) ;
175
+ set_stack_limit( Some ( stack_low) ) ;
178
176
179
177
// Make sure the stack is 16-byte aligned which should be enough for all
180
178
// platforms right now. Allocations on 64-bit are already 16-byte aligned
@@ -204,6 +202,10 @@ cfg_if! {
204
202
205
203
cfg_if ! {
206
204
if #[ cfg( windows) ] {
205
+ extern {
206
+ fn __stacker_get_current_fiber( ) -> winapi:: PVOID ;
207
+ }
208
+
207
209
struct FiberInfo <' a> {
208
210
callback: & ' a mut FnMut ( ) ,
209
211
result: Option <std:: thread:: Result <( ) >>,
@@ -212,51 +214,72 @@ cfg_if! {
212
214
213
215
unsafe extern "system" fn fiber_proc( info: winapi:: LPVOID ) {
214
216
let info = & mut * ( info as * mut FiberInfo ) ;
217
+
218
+ // Remember the old stack limit
219
+ let old_stack_limit = get_stack_limit( ) ;
220
+ // Update the limit to that of the fiber stack
221
+ set_stack_limit( guess_os_stack_limit( ) ) ;
222
+
215
223
info. result = Some ( std:: panic:: catch_unwind( std:: panic:: AssertUnwindSafe ( || {
216
224
( info. callback) ( ) ;
217
225
} ) ) ) ;
226
+
227
+ // Restore the stack limit of the previous fiber
228
+ set_stack_limit( old_stack_limit) ;
229
+
218
230
kernel32:: SwitchToFiber ( info. parent_fiber) ;
219
231
return ;
220
232
}
221
233
222
234
fn _grow( stack_size: usize , callback: & mut FnMut ( ) ) {
223
235
unsafe {
236
+ // Fibers (or stackful coroutines) is the only way to create new stacks on the
237
+ // same thread on Windows. So in order to extend the stack we create fiber
238
+ // and switch to it so we can use it's stack. After running
239
+ // `callback` we switch back to the current stack and destroy
240
+ // the fiber and its associated stack.
241
+
224
242
let was_fiber = kernel32:: IsThreadAFiber ( ) == winapi:: TRUE ;
225
243
226
- // Fibers are essentially stackfull coroutines
227
244
let mut info = FiberInfo {
228
245
callback,
229
246
result: None ,
230
- parent_fiber: if was_fiber {
231
- extern {
232
- fn GetCurrentFiber ( ) -> * mut winapi:: c_void;
247
+
248
+ // We need a handle to the current stack / fiber so we can switch back to it
249
+ parent_fiber: {
250
+ // Is the current thread already a fiber? This is the case when we already
251
+ // used a fiber to extend the stack
252
+ if was_fiber {
253
+ // Get a handle to the current fiber. We need to use C for this
254
+ // as GetCurrentFiber is an header only function.
255
+ __stacker_get_current_fiber( )
256
+ } else {
257
+ // Convert the current thread to a fiber, so we are able to switch back
258
+ // to the current stack. Threads coverted to fibers still act like
259
+ // regular threads, but they have associated fiber data. We later
260
+ // convert it back to a regular thread and free the fiber data.
261
+ kernel32:: ConvertThreadToFiber ( 0i32 as _)
233
262
}
234
- GetCurrentFiber ( )
235
- } else {
236
- kernel32:: ConvertThreadToFiber ( 0i32 as _)
237
263
} ,
238
264
} ;
239
265
if info. parent_fiber == 0i32 as _ {
266
+ // We don't have a handle to the fiber, so we can't switch back
240
267
panic!( "unable to convert thread to fiber" ) ;
241
268
}
242
- // remember the old stack limit
243
- let old_stack_limit = get_stack_limit( ) ;
244
- // bump the know stack size in the thread local
245
- set_stack_limit( stack_size) ;
269
+
246
270
let fiber = kernel32:: CreateFiber ( stack_size as _, Some ( fiber_proc) , & mut info as * mut FiberInfo as * mut _) ;
247
271
if fiber == 0i32 as _ {
248
272
panic!( "unable to allocate fiber" ) ;
249
273
}
250
- // switch to fiber and immediately execute
274
+
275
+ // Switch to the fiber we created. This changes stacks and starts executing
276
+ // fiber_proc on it. fiber_proc will run `callback` and then switch back
251
277
kernel32:: SwitchToFiber ( fiber) ;
252
- // fiber execution finished, we can safely delete it now
253
- kernel32:: DeleteFiber ( fiber) ;
254
278
255
- // restore the old stack limit
256
- if let Some ( old) = old_stack_limit {
257
- set_stack_limit( old) ;
258
- }
279
+ // We are back on the old stack and now we have destroy the fiber and its stack
280
+ kernel32:: DeleteFiber ( fiber) ;
259
281
282
+ // If the
260
283
if !was_fiber {
261
284
kernel32:: ConvertFiberToThread ( ) ;
262
285
}
@@ -267,35 +290,38 @@ cfg_if! {
267
290
}
268
291
}
269
292
270
- cfg_if! {
271
- if #[ cfg( any( target_arch = "x86_64" , target_arch = "x86" ) ) ] {
272
- #[ inline( always) ]
273
- // We cannot know the initial stack size on x86
274
- unsafe fn guess_os_stack_limit( ) -> Option <usize > {
275
- None
276
- }
293
+ #[ inline( always) ]
294
+ fn get_thread_stack_guarantee( ) -> usize {
295
+ let min_guarantee = if cfg!( target_pointer_width = "32" ) {
296
+ 0x1000
277
297
} else {
278
- #[ inline( always) ]
279
- fn get_thread_stack_guarantee( ) -> usize {
280
- let min_guarantee = if cfg!( target_pointer_width = "32" ) {
281
- 0x1000
282
- } else {
283
- 0x2000
284
- } ;
285
- let mut stack_guarantee = 0 ;
286
- unsafe {
287
- kernel32:: SetThreadStackGuarantee ( & mut stack_guarantee)
288
- } ;
289
- std:: cmp:: max( stack_guarantee, min_guarantee) as usize + 0x1000
290
- }
298
+ 0x2000
299
+ } ;
300
+ let mut stack_guarantee = 0 ;
301
+ unsafe {
302
+ // Read the current thread stack guarantee
303
+ // This is the stack reserved for stack overflow
304
+ // exception handling.
305
+ // This doesn't return the true value so we need
306
+ // some further logic to calculate the real stack
307
+ // guarantee. This logic is what is used on x86-32 and
308
+ // x86-64 Windows 10. Other versions and platforms may differ
309
+ kernel32:: SetThreadStackGuarantee ( & mut stack_guarantee)
310
+ } ;
311
+ std:: cmp:: max( stack_guarantee, min_guarantee) as usize + 0x1000
312
+ }
291
313
292
- #[ inline( always) ]
293
- unsafe fn guess_os_stack_limit( ) -> Option <usize > {
294
- let mut mi;
295
- kernel32:: VirtualQuery ( __stacker_stack_pointer( ) , & mut mi, std:: mem:: size_of_val( & mi) ) ;
296
- Some ( mi. AllocationBase + get_thread_stack_guarantee( ) + 0x1000 )
297
- }
298
- }
314
+ #[ inline( always) ]
315
+ unsafe fn guess_os_stack_limit( ) -> Option <usize > {
316
+ let mut mi = std:: mem:: zeroed( ) ;
317
+ // Query the allocation which contains our stack pointer in order
318
+ // to discover the size of the stack
319
+ kernel32:: VirtualQuery (
320
+ __stacker_stack_pointer( ) as * const _,
321
+ & mut mi,
322
+ std:: mem:: size_of_val( & mi) as winapi:: SIZE_T ,
323
+ ) ;
324
+ Some ( mi. AllocationBase as usize + get_thread_stack_guarantee( ) + 0x1000 )
299
325
}
300
326
} else if #[ cfg( target_os = "linux" ) ] {
301
327
use std:: mem;
0 commit comments