Stream: cranelift

Topic: Calling closures


view this post on Zulip JT (May 23 2023 at 18:10):

Is there currently a way to call a closure from cranelift? The ones I'm thinking of trying wouldn't have closed over any variables, but otherwise would be a first class function.

view this post on Zulip bjorn3 (May 23 2023 at 18:15):

Rust closures have an unstable ABI. What you can do however is do Box<Box<dyn FnOnce()>>, turns this into a raw pointer to pass to the cranelift compiled function and then provide an extern "C" function which turns the raw pointer back into Box<Box<dyn FnOnce()>> (or &Box<dyn Fn()> if you have a dyn Fn() closure) and does the call. Note that you need double allocation as the layout of Box<dyn FnOnce()> is unstable.

view this post on Zulip Jamey Sharp (May 23 2023 at 18:20):

Correct me if I'm wrong @bjorn3 but I think in this case a simpler answer to @JT's question is that if the closure doesn't capture any variables, then you can use it at a Rust type of fn(...) instead of Fn*(...) and then its representation is just a thin function pointer. You still have to deal with the Rust ABI for function calls being unstable but you shouldn't need to box anything or use trait objects, I think.

view this post on Zulip bjorn3 (May 23 2023 at 18:22):

Missed the "wouldn't have closed over any variables" part.

view this post on Zulip JT (May 23 2023 at 18:29):

Jamey Sharp said:

Correct me if I'm wrong bjorn3 but I think in this case a simpler answer to JT's question is that if the closure doesn't capture any variables, then you can use it at a Rust type of fn(...) instead of Fn*(...) and then its representation is just a thin function pointer. You still have to deal with the Rust ABI for function calls being unstable but you shouldn't need to box anything or use trait objects, I think.

Can you fill out what you mean a bit? I can't quite picture how to tie that back to cranelift. Are you saying pass it in as a fn(...) argument and then pass that value to cranelift?

view this post on Zulip bjorn3 (May 23 2023 at 18:29):

You still need an extern "C" wrapper function for calling the fn(...) I think.

view this post on Zulip bjorn3 (May 23 2023 at 18:31):

You did cast the function pointer to a regular pointer or usize and pass it to the compiled function. The compiled function can then call a rust extern "C" function and pass this value. The rust function can transmute it back to a function pointer and call it.

view this post on Zulip Chris Fallin (May 23 2023 at 18:34):

you can do this without unsafe or transmutes I think: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=b9871f0855b226fafe1cdc22cf9bd9b8

view this post on Zulip Chris Fallin (May 23 2023 at 18:34):

the Rust syntax for a closure (|...| { ... }) will coerce to a fn(...) -> ... type if it has no captures

view this post on Zulip Jamey Sharp (May 23 2023 at 18:38):

That's right, Chris, except that calling the fn(...) pointer from Cranelift is more complicated than calling it from Rust, because you can't put an extern "C" on the closure.

view this post on Zulip Jamey Sharp (May 23 2023 at 18:39):

In Wasmtime we make assumptions about the unstable Rust function-call ABI so that we can call such functions directly, but we also have a large testing infrastructure and some other advantages to give us confidence that new Rust versions haven't broken our usage. (edit: I might be wrong about this? maybe we do have extern "C" annotations everywhere we need them)

view this post on Zulip Chris Fallin (May 23 2023 at 18:41):

ah, darn, I hadn't realized this can't take an ABI annotation

view this post on Zulip JT (May 23 2023 at 18:41):

Thanks for the help :) this gives me a few ways to try


Last updated: Nov 26 2024 at 01:30 UTC