mozilla/rust — Issue #1732: Rust functions that can be called from C
Author: brson
Created: 2012-02-02 06:06:18+00:00
State: open
Labels: design, feature, front, middle, rfc, rt, trans

We have a lot of scenarios now where people creating bindings want to be able to provide a callback that a C function can call. The current solution is to write a native C function that uses some unspecified internal APIs to send a message back to Rust code. The ideal solution involves writing no C code.

Here is a minimal solution for creating functions in Rust that can be called from C code. The gist is: 1) we have yet another kind of function declaration, 2) this function cannot be called from Rust code, 3) it's value can be taken as an opaque unsafe pointer, 4) it bakes in the stack switching magic and adapts from the C ABI to the Rust ABI.

Declarations of C-to-Rust (crust) functions:

```
crust fn callback(a: *whatever) {
}
```

Getting an unsafe pointer to a C ABI function:

```
let callbackptr: *u8 = callback;
```

We could also define some type specifically for this purpose.

Compiler implementation:

It's mostly straightforward, but trans gets ugly. In trans we will need to do basically the opposite of what we do for native mod functions:

* Generate a C ABI function using the declared signature
* Generate a shim function that takes the C arguments in a struct
* The C function stuffs the arguments into a struct
* The C function calls upcall_call_shim_on_rust_stack with the struct of arguments and the address of the shim function
* Generate a Rust ABI function using the declared signature
* The shim function pulls the arguments out of the struct and calls the Rust function

Runtime implementation:

The runtime has to change in a few ways to make this happen:

* A new upcall for switching back to the Rust stack
* Tasks need to maintain a stack of Rust contexts and C contexts
* Needs a strategy to deal with failure after reentering the Rust stack
* Needs a strategy to deal with yielding after reentering the Rust stack

Failure:

We can't simply throw an exception after reentering the Rust stack because there's no guarantee the native code can be unwound with C++ exceptions. The Go language apparently will just skip over all the native frames in the scenario, leaking everything along the way. We, instead will abort - if the user want's to avoid catastrophic failure they should use their Rust callback to dispatch a message and immediately return.

Yielding:

Without changes to the way we handle C stacks we cannot allow Rust functions to return to the schedule after reentering the Rust stack from C code. I see two solutions:

1) Yielding is different after reentering the Rust stack and simply blocks. Tasks that want to do this should make sure they have their own scheduler (#1721).
2) Instead of running native code using the scheduler's stack, tasks will check out C stacks from a pool located in each scheduler. Each time a task reenters the C stack it will check if it already has one and reuse it, otherwise it will request a new one from the scheduler. This would allow Rust code to always yield normally without tying up the scheduler.

I prefer the second option.

See also #1508