Stream: cranelift

Topic: multi-part argument passing (#9509)


view this post on Zulip joshua you (Oct 14 2025 at 05:09):

hi, i'm writing a C compiler at the moment and want to implement passing structs by value. i saw from discussion on reddit and on github (#9509) that there is an issue with the way this is handled by cranelift currently

to summarize: sometimes, the ABI (SysV) allows passing small structs in registers. however, other times, if the struct is larger than some size, it must be passed via the stack.

currently, cranelift delegates this choice to frontend author - for big structs, they should use StructArgument purpose on a ptrsize-value param, and cranelift will generate a memcpy from the pointer given to Call IR instruction to the stack.

however, there's an edge case when a small struct which would ordinarily be passed through registers instead has to be passed through stack due to lack of registers for some of its fields (or some other reason). in this case, SysV mandates that the entire struct go onto the stack, but if the frontend lowers a struct into multiple values, there's no way for Cranelift to reason about those values making up the struct as a group.

it seems like you can get it to work by essentially doing all of the ABI calculations yourself, but this seems like a bit of a hack, and not really ideal.

i'd like for my compiler to implement this properly, so i was hoping to get some input from maintainers about possible patch for cranelift

from github: add some kind of AbiParamGroup abstraction to ir::Signature to say that multiple Values should be treated as a unit for the purposes of allocating argument-passing registers when passing small structs, keep StructArgument for larger structs. need to emit code to spill registers onto stack when doing Call, and to load from stack on the other side.

i guess my thought is that this feels a bit confusing to have both StructArgument and this "AbiParamGroup" mechanism for frontend authors, but since they are expected to handle some ABI details anyways, maybe its ok?

Feature When lowering a C or Rust type in a clif ir function signature, sometimes it is necessary to split it into multiple clif ir level parameters. For example #[repr(C)] struct F32Array { field0...

view this post on Zulip Chris Fallin (Oct 14 2025 at 16:50):

I don't fully remember all the discussion here, but I also don't see anything in the issue you linked about

keep StructArgument for larger structs

and I suspect we actually wouldn't want to do that; rather we'd expect the caller to use a stackslot to put the struct somewhere on stack, then pass the pointer in. In other words, we want to give building blocks to allow you to build an implementation of the ABI without platform-specific knowledge (that's the param grouping and ISA-specific allocation algorithm over it) but not to bundle in too much extra.

view this post on Zulip joshua you (Oct 14 2025 at 19:07):

wouldn't it still be necessary to have the StructArgument purpose though? even if you make the memcpy into callee's argument passing area explicit, you'd still have to tell cranelift what order that struct goes into the callee's argument passing area relative to implicit spills in function call from register pressure

view this post on Zulip Chris Fallin (Oct 14 2025 at 19:19):

Ah, I may be misremembering the details of the ABI -- it's been a few years, sorry :-) If it has to be in a particular place, then yep, you're right. I seem to remember a pointer to the arg being passed in a reg and if that's the case and there are no restrictions where it actually lives, then we may not need it

view this post on Zulip bjorn3 (Oct 15 2025 at 09:25):

I seem to remember a pointer to the arg being passed in a reg and if that's the case and there are no restrictions where it actually lives, then we may not need it

That is on s390x and maybe on aarch64, where cg_clif would indeed not use StructArgument. On x86_64 you definitively need StructArgument however.


Last updated: Dec 06 2025 at 06:05 UTC