Stream: cranelift

Topic: Doing a Scheme call using Cranelift


view this post on Zulip Adel Prokurov (Feb 26 2024 at 09:18):

So I was investigating into usage of Cranelift as a backend for my Scheme runtime. For this it needs to support indirect tail-calls and it seems to be in Cranelift already. Next thing is that argument count should be passed to the callee somehow.
What I thought of is passing argument count as first argument and then callee could check it, is it a good idea?

view this post on Zulip Adel Prokurov (Feb 26 2024 at 09:20):

Calls will always be indirect so I could just pass my own SigRef everywhere, question is: would it break calling Cranelift convention if I do such calls? Like theoretically more arguments could be passed to a function that it expects, it should check this argument count register inside on its own

view this post on Zulip fitzgen (he/him) (Feb 26 2024 at 15:17):

FYI, Cranelift currently only supports tail calls when both the caller and callee function use the tail calling convention.

Next thing is that argument count should be passed to the callee somehow. What I thought of is passing argument count as first argument and then callee could check it, is it a good idea? [...] would it break calling Cranelift convention if I do such calls?

The Cranelift-level function signatures must match. But that doesn't mean that the Scheme-level function signatures must match, if you lower all Scheme functions such that they use the same Cranelift-level function signature (which is what I think you are getting at?)

Wasmtime does something similar for dynamic-y calls. We call it the "array" calling convention, and the same fixed native/cranelift-level signature is used for all Wasm function signatures when using this calling convention (Wasmtime has three calling conventions).

The native/Cranelift-level function signature is:

https://github.com/bytecodealliance/wasmtime/blob/300fe46d29b00c8ec49a38be4ced541e8b6d1c61/crates/runtime/src/vmcontext.rs#L34

/// A function pointer that exposes the array calling convention.
///
/// Regardless of the underlying Wasm function type, all functions using the
/// array calling convention have the same Rust signature.
///
/// Arguments:
///
/// * Callee `vmctx` for the function itself.
///
/// * Caller's `vmctx` (so that host functions can access the linear memory of
///   their Wasm callers).
///
/// * A pointer to a buffer of `ValRaw`s where both arguments are passed into
///   this function, and where results are returned from this function.
///
/// * The capacity of the `ValRaw` buffer. Must always be at least
///   `max(len(wasm_params), len(wasm_results))`.
pub type VMArrayCallFunction =
    unsafe extern "C" fn(*mut VMOpaqueContext, *mut VMOpaqueContext, *mut ValRaw, usize);

(and here is the corresponding code that construct the cranelift_codegen::ir::Signature: https://github.com/bytecodealliance/wasmtime/blob/300fe46d29b00c8ec49a38be4ced541e8b6d1c61/crates/cranelift/src/lib.rs#L118)

If you use this native/Cranelift-level function signature, functions can dynamically check that the number of actual arguments passed matches their expected number of formal parameters.

view this post on Zulip Adel Prokurov (Mar 01 2024 at 01:15):

Thanks! I've settled for using my own codegen backend at the moment as I need quite custom callconv with no callee-saves and allow function signatures to not match

view this post on Zulip Adel Prokurov (Mar 13 2024 at 07:25):

fitzgen (he/him) said:

FYI, Cranelift currently only supports tail calls when both the caller and callee function use the tail calling convention.

Next thing is that argument count should be passed to the callee somehow. What I thought of is passing argument count as first argument and then callee could check it, is it a good idea? [...] would it break calling Cranelift convention if I do such calls?

The Cranelift-level function signatures must match. But that doesn't mean that the Scheme-level function signatures must match, if you lower all Scheme functions such that they use the same Cranelift-level function signature (which is what I think you are getting at?)

Wasmtime does something similar for dynamic-y calls. We call it the "array" calling convention, and the same fixed native/cranelift-level signature is used for all Wasm function signatures when using this calling convention (Wasmtime has three calling conventions).

The native/Cranelift-level function signature is:

https://github.com/bytecodealliance/wasmtime/blob/300fe46d29b00c8ec49a38be4ced541e8b6d1c61/crates/runtime/src/vmcontext.rs#L34

/// A function pointer that exposes the array calling convention.
///
/// Regardless of the underlying Wasm function type, all functions using the
/// array calling convention have the same Rust signature.
///
/// Arguments:
///
/// * Callee `vmctx` for the function itself.
///
/// * Caller's `vmctx` (so that host functions can access the linear memory of
///   their Wasm callers).
///
/// * A pointer to a buffer of `ValRaw`s where both arguments are passed into
///   this function, and where results are returned from this function.
///
/// * The capacity of the `ValRaw` buffer. Must always be at least
///   `max(len(wasm_params), len(wasm_results))`.
pub type VMArrayCallFunction =
    unsafe extern "C" fn(*mut VMOpaqueContext, *mut VMOpaqueContext, *mut ValRaw, usize);

(and here is the corresponding code that construct the cranelift_codegen::ir::Signature: https://github.com/bytecodealliance/wasmtime/blob/300fe46d29b00c8ec49a38be4ced541e8b6d1c61/crates/cranelift/src/lib.rs#L118)

If you use this native/Cranelift-level function signature, functions can dynamically check that the number of actual arguments passed matches their expected number of formal parameters.

Ok actually I've got a question now: if I were to intorduce "array" calling convention how would that work? If I allocate array for arguments in caller and then do return_call it would destroy the array

view this post on Zulip fitzgen (he/him) (Mar 13 2024 at 11:52):

for the combination of tail calls and functions that can dynamically pass any number of arguments/returns, the most straightforward solution that pops to mind is to heap allocate, rather than stack allocate, that array and then resize it as necessary (potentially with calling out to the host/vm to do that resizing when necessary; should reach a steady state and amortize that cost pretty quickly)

another alternative is to impose some reasonable implementation limit on the max number of arguments/returns (say 1000) and then static allocate the array

view this post on Zulip fitzgen (he/him) (Mar 13 2024 at 11:53):

(for Wasmtime, we don't need to support tail calls through the array calling convention, so we don't have this particular issue)


Last updated: Nov 22 2024 at 16:03 UTC