On 01/31/2013 02:37 PM, Patrick Walton wrote:
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();
}
This amortizes the cost of the stack switch over many native function
calls.
I think this API requires #4479 and #4480 to be safe. Currently, the
execution environment after the stack switch is very different, so
running arbitrary Rust code there is dangerous. We may want to think of
'stack switching' instead as 'make sure I'm running on a stack segment
that is big'. Then whatever code executes after that doesn't matter - if
it's C code it will run till it runs off the stack, if it's Rust code it
will request a new segment when it hits the end.
https://github.com/mozilla/rust/issues/4479
https://github.com/mozilla/rust/issues/4480
An API that was more like `stackswitch!(function, args)` wouldn't have
that problem.
2. It should be possible to have sections of Rust code that run on a
big C stack and do not use segmented stacks; for example, the new Rust
scheduler (which is to be written in Rust), or the Servo DOM as
mentioned above.
3. If (2) is possible, the Rust compiler never knows whether there's
enough stack space available to safely call a C function. Therefore,
performing the stack switch ought to be under the programmer's control.
I don't strictly agree with this but also don't think it invalidates
your argument. My pull request #4691 makes the decision to do the stack
switch dynamic in a way that it should work correctly whether it's in
task context or not. There is overhead though.
4. We should have a lint pass that ensures that stack switches are
performed properly, because we do not want programmers to accidentally
shoot themselves in the foot.
This does force more work on programmers in the common case, and it
doesn't just punish the person declaring the functions but every caller.
That's a difficult trade off.
5. Because C functions are always unsafe in the Rust sense, Rust code
will almost always wrap functionality provided by foreign libraries
into safe Rust abstractions. The stack switch can be moved into these
abstractions.
Sure. Alleviates the problem I mentioned above.
6. C functions are always unsafe, so this does not, formally, add any
new unsafety.
Whatever decision we come to, we should make this decision soon
(before 0.6), because this will break code. Thoughts?
I do want 'extern fn's (the ones that call into Rust from C) to be plain
C ABI functions, but I think this is solvable just with #4479 and #4480.
Under that scenario calling into Rust via C would not ever have a stack
switch. Instead, it would simply be relying on the segmented stack
prologue to determine if there was enough stack.
I also though would like to move the stack switching machinery out of
trans, and I want access to foreign function pointers, and I think a
syntax extension is doable, but I'm worried about how inconvenient it
might be. I also am reluctant surfacing the stack switching in the
language. Maybe we can phrase it a way that isn't tied to the
implementation, like 'prepare_foreign_call' (just spitballing here).
_______________________________________________
Rust-dev mailing list
Rust-dev@mozilla.org
https://mail.mozilla.org/listinfo/rust-dev