Stream: cranelift

Topic: working with async


view this post on Zulip JT (May 26 2023 at 01:20):

Has anyone had a chance to explore calling async functions from cranelift? Also, are there plans for creating async functions from cranelift?

I've been poking around a bit, and was wondering just how much of Rust we might be able to call into. Async is definitely a big one that I wasn't sure if cranelift supported.

view this post on Zulip Chris Fallin (May 26 2023 at 01:27):

The concept of "async functions" doesn't really exist at the Cranelift level. An async function in Rust, for example, is transformed by the Rust compiler into a function that looks kind of like a state machine and keeps its state in an anonymous type that implements the Future trait. Other languages may implement async differently. Cranelift has no knowledge of the Rust async semantics or stdlib, nor any other language's async, and so it wouldn't make sense to talk about "async functions" at the Cranelift level.

As a parallel example, while Rust supports async functions, the IR presented to LLVM when you compile the Rust program has no "async" functions either. Think of Cranelift as similar to LLVM: you need to translate whatever higher-level notions you have into standard functions that take primitive args (floats/ints/pointers) and return primitive results. Does that make sense?

view this post on Zulip Chris Fallin (May 26 2023 at 01:29):

On the topic of calling into Rust specifically: there are a bunch of Rust-level notions, like aggregate types (structs) for example, that Cranelift doesn't support. You can look at cg_clif (the rustc backend that uses Cranelift) to see how it implements all of this one abstraction layer up from Cranelift. (cc @bjorn3 for more on this!)

view this post on Zulip Jamey Sharp (May 26 2023 at 01:29):

I haven't tried it, but it boils down to calling poll on whatever implementation of the Future trait your async function returns, where the actual type is auto-generated by the Rust compiler. So if you know how to call trait methods, and can figure out the size of the generated type so you can allocate enough space to store it, then you're set. Except that is so many levels of unstable Rust ABIs that I hate to imagine trying to generate correct Cranelift calls for it without hooking deep into the Rust compiler.

The usual trick for hiding away all those ABIs works here too though: construct a Box<dyn Future> in Rust code, then pass it to Cranelift-generated code through an extern "C" interface, and pass the box back into another Rust function when you want to call poll.

view this post on Zulip Jamey Sharp (May 26 2023 at 01:31):

(Actually you probably want Box<Box<dyn Future>> so that you have a thin pointer to pass around instead of a fat pointer.)

view this post on Zulip JT (May 26 2023 at 05:00):

Chris Fallin said:

The concept of "async functions" doesn't really exist at the Cranelift level. An async function in Rust, for example, is transformed by the Rust compiler into a function that looks kind of like a state machine and keeps its state in an anonymous type that implements the Future trait. Other languages may implement async differently. Cranelift has no knowledge of the Rust async semantics or stdlib, nor any other language's async, and so it wouldn't make sense to talk about "async functions" at the Cranelift level.

As a parallel example, while Rust supports async functions, the IR presented to LLVM when you compile the Rust program has no "async" functions either. Think of Cranelift as similar to LLVM: you need to translate whatever higher-level notions you have into standard functions that take primitive args (floats/ints/pointers) and return primitive results. Does that make sense?

yeah, this was also my assumption but I wanted to confirm with y'all to be sure. Thanks, that's helpful.

view this post on Zulip JT (May 26 2023 at 05:01):

Jamey Sharp said:

I haven't tried it, but it boils down to calling poll on whatever implementation of the Future trait your async function returns, where the actual type is auto-generated by the Rust compiler. So if you know how to call trait methods, and can figure out the size of the generated type so you can allocate enough space to store it, then you're set. Except that is so many levels of unstable Rust ABIs that I hate to imagine trying to generate correct Cranelift calls for it without hooking deep into the Rust compiler.

The usual trick for hiding away all those ABIs works here too though: construct a Box<dyn Future> in Rust code, then pass it to Cranelift-generated code through an extern "C" interface, and pass the box back into another Rust function when you want to call poll.

this makes me a'feard :sweat_smile: I think I'll stick to simpler paths.


Last updated: Dec 23 2024 at 13:07 UTC