[rust-dev] RFC: Explicit stack switching

Brian Anderson banderson at mozilla.com
Fri Feb 1 23:02:21 PST 2013

On 02/01/2013 03:14 PM, Brian Anderson wrote:
> On 02/01/2013 09:23 AM, Matthieu Monrocq wrote:
>> On Fri, Feb 1, 2013 at 12:09 PM, Michael Neumann <mneumann at ntecs.de 
>> <mailto:mneumann at ntecs.de>> wrote:
>>     Am 31.01.2013 23:37, schrieb Patrick Walton:
>>         Hi everyone,
>>         With the revamp of the scheduler underway, I'd like to
>>         propose a change to the way C functions work.
>>         Currently, we generate a shim and a stack switch for every
>>         function call from Rust to C and likewise from C to Rust,
>>         except for functions annotated with `#[rust_stack]`. These
>>         wrappers result in a significant performance overhead. For
>>         some workloads this performance overhead is acceptable in
>>         order to maintain small stacks. For some workloads the
>>         performance overhead is undesirable.
>>         For instance, the DOM in Servo requires lots of very small
>>         calls from JavaScript to Rust. The overhead of stack
>>         switching swamps most of the time here. Popular Web
>>         benchmarks will do things like `someElement.clientX;` over
>>         and over, which require calls from JavaScript to Rust to
>>         retrieve a cached value. So we must carefully consider every
>>         CPU cycle spent in the C-to-Rust transition.
>>         To address these issues I would like to propose a somewhat
>>         radical change: don't have the compiler generate stack
>>         switching stubs at all. Instead, the scheduler can expose a
>>         primitive that generates the stack switch, and it's the
>>         programmer's responsibility to perform the stack switch to
>>         call out to C functions. To avoid the obvious footgun here, I
>>         propose a lint pass, on by default, that ensures that
>>         functions not annotated with `#[rust_stack]` are called
>>         inside a stack switching helper.
>>         The rationale here is as follows:
>>         1. It should be possible to group many C calls under a single
>>         stack switching operation. For example:
>>             do stackswitch {
>>                 c_function_1();
>>                 c_function_2();
>>                 c_function_3();
>>             }
>>     wouldn't it be possible for this case to just do:
>>     extern mod lib_c {
>>       #[rust_stack]
>>       fn c_function_1();
>>       #[rust_stack]
>>        fn c_function_2();
>>       #[rust_stack]
>>        fn c_function_3();
>>     }
>>     and then calling it like above with *one* "do stackswitch"?
>>     The default would still be to do a stack switch. If you need to
>>     call c_function_1 sometimes with a stack switch, and sometimes
>>     in a group of other functions (with just one stack switch for
>>     that group), we could have something like this:
>>     extern mod lib_c {
>>       // This is the default
>>       fn c_function_1();
>>       #[rust_stack]
>>       fn c_function_1() as rs_c_function1();
>>     }
>>     Then use lib_c::c_function_1() when you want a stack switch, or
>>     rs_c_function1() without. One could go further
>>     and auto generate for mod lib_c a sub module called "rs" (for
>>     rust stack), where each function has a #[rust_stack]
>>     directive in front of it, so you don't have to declare it twice.
>>     This woudl give use: lib_c::c_function_1() and
>>     lib_c::rs::c_function_1().
>>     Regards,
>>       Michael
>>     _______________________________________________
>>     Rust-dev mailing list
>>     Rust-dev at mozilla.org <mailto:Rust-dev at mozilla.org>
>>     https://mail.mozilla.org/listinfo/rust-dev
>> I would have a stupid proposal: what if the C function declaration 
>> was annotated not with #[rust_stack] but with #[stack 5k] instead. 
>> That is, instead of having a single blunt tool, let the programmer 
>> declare how much stack is necessary for the C function and let the 
>> compiler reason about it to guarantee that enough stack is available.
>> extern mod lib_c {
>>   #[stack 4k]
>>   fn c_function_1();
>>   #[stack unlimited]
>>    fn c_function_2();
>>   #[stack 16k]
>>    fn c_function_3();
>> }
> I do imagine we will eventually want precise control to declare how 
> much stack we need. I originally wanted this for optimizations in 
> core, but as more of core is written in Rust this probably isn't going 
> to matter much. There is an issue open on this subject: 
> https://github.com/mozilla/rust/issues/4481

After thinking about this more, I like this direction and think it will 
matter for core because core should entirely be able to avoid big stack 
switches once the runtime is rewritten.

I want to stop thinking about this as 'stack switching' and instead as 
'reserving stack'. At some point I intend to remove the distinction 
between the C stack and Rust stack segments, and they will be cached in 
a pool on the scheduler. It will be considerably simpler than the 
current arrangement that employs two very different strategies for 
creating and caching stacks.

I am thinking a two-pronged approach of library functions plus syntax 

extern-ABI functions don't switch stacks by default.

In the library we add this sort of function that simply guarantee that 
the closure has some amount of stack available.

do reserve_stack(Standard) { rust_task_fail(); }
do reserve_stack(Tiny) {... }
do reserve_stack(Large) { }
do reserve_stack(Size(4096)) { }

Then a decorating syntax extension that automatically wraps a declaration.

extern {
   fn rust_task_fail();

   fn rust_get_task() -> *rust_task;

Or even

extern {

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/rust-dev/attachments/20130201/ee8e029a/attachment.html>

More information about the Rust-dev mailing list