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

Reply via email to