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 <mneum...@ntecs.de
<mailto:mneum...@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@mozilla.org <mailto:Rust-dev@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
extensions.
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 {
#[reserve_stack]
fn rust_task_fail();
#[reserve_stack(Tiny)]
fn rust_get_task() -> *rust_task;
}
Or even
#[reserve_stack]
extern {
...
}
_______________________________________________
Rust-dev mailing list
Rust-dev@mozilla.org
https://mail.mozilla.org/listinfo/rust-dev