Stream: cranelift

Topic: register promotion in conditional branching


view this post on Zulip Adrian (Aug 16 2024 at 14:09):

Hey everyone, i'm using cranelift as the backend for a DSL i am working on. However, there are some unknowns concerning the handling of variable assignments in conditional branching and loops, that i would like to know going forward and that i cannot really find any information. Consider the following case for a manual implementation of min:

fn min(x: f32, y: f32) -> f32 {
    if x < y {
        x
    } else {
        y
    }
}

To compile this function using cranelift, i would first create three blocks: a block for the then-case, one for the else-case and a merge block. I would then append a block parameter of type ir::types::F32 to the merge block and pass the value of either x or y when jumping from the then- or else-block to the merge block. This case is clear. However, what would be the standard procedure for handling the assignment of external variables in these blocks? Example:

fn min2(x: f32, y: f32) -> f32 {
    let mut res = 0.0;
    if x < y {
        res = x;
    } else {
        res = y;
    }
    res
}

In this case, the way i see it, there are two options:

  1. i can figure out which variables are assigned to in the then & else blocks, and append them to the block parameters of the merge block, passing the updated SSA values on the jump. Then, in the merge block, the new SSA value of each variable that gets updated in one or both of the blocks is assigned to its variable name, so that subsequent accesses to the variable use the correct value.
    In other words, i kind of implement the Phi-node for the variable assignment of res manually through analysis of the code and block parameters. While this would definitely work and closely mimics the implementation of expressive if-else statements in the cranelift_jit demo, this will quickly get quite complex, considering that using this method one has to take into account all variable assignments in conditional branches, chained if-else statements, loops, etc.

  2. alternatively, i can save res (and all other variables for that matter) not as raw SSA values, but as stack slots. Since the location of the stack slot does not change, the assignments are then only a stack_store instruction that overwrites the previous data stored in the stack slot.

In LLVM, i would simply use the second option, since it is much more simple to implement and i can safely rely on LLVMs register promotion to get good performance and avoid unnecessary load and store operations. However, i cannot find much information on the optimization passes that Cranelift performs (i use the JITModule extension for code-gen and execution) and if i can rely on register promotion to get good performance with option 2. Do you have any recommendations on what the standard way of handling these kinds of things would be using Cranelift?

Thanks ;)

PS: once i am more comfortable with Cranelift i would be happy to compile my experience in to a refresh of the jit-demo that covers a bit more than the bare basics that are covered in that repo right now. That way, dumb questions like this one may be avoided in the future :)

view this post on Zulip fitzgen (he/him) (Aug 16 2024 at 16:04):

cranelift_frontend::Variable will do option (1) for you

view this post on Zulip fitzgen (he/him) (Aug 16 2024 at 16:04):

On phone so no links, sorry

view this post on Zulip Afonso Bordado (Aug 16 2024 at 16:07):

Links: cranelift_frontend::Variable

See also: cranelift_frontend::FunctionBuilder::declare_var and def_var/use_var

view this post on Zulip Adrian (Aug 19 2024 at 08:25):

Hey, thanks guys. It's interesting to know that cranelift_frontend::Variable supports that out of the box. I have kind of moved away from this implementation of variables, since my lang supports aggregate types, with are mostly saved as stack slots internally to facilitate easy field access and reference taking, among other things. I will take a look at the implementation in cranelift_frontend and maybe get some inspiration how the variables are implemented there.

view this post on Zulip Ivan Chinenov (Aug 20 2024 at 19:16):

As a sidenote, does cranelift have a mem2reg pass to optimize out some of stack slots?

view this post on Zulip fitzgen (he/him) (Aug 20 2024 at 19:19):

it has an alias analysis that can do redundant load elimination and store-to-loard forwarding. it cannot do dead-store elimination, so you'd still get some writes to the stack, even if all uses were in registers


Last updated: Jan 10 2026 at 02:36 UTC