Stream: cranelift

Topic: Question about passing structs by value


view this post on Zulip Setzer22 (Apr 23 2023 at 17:41):

Hi! I'm adding structs to my toy compiler (custom frontend, cranelift as backend) and I was wondering about how to pass structs by value when calling functions.

As far as I understand, the specific requirements of how to call functions are up to the ABI. I know it's often either splitting up the struct into multiple arguments or creating a copy of the struct in the caller's stack, and passing a pointer to that value (depending on size). In LLVM, you don't need to care about all this because struct types are part of the IR, but with cranelift, it seems things are a bit more low level since there are no structs in the IR.

So, my question is. Do I need to have platform-specific code that adheres to the calling conventions of each platform when passing structs by value? Is there some helper code in cranelift already to achieve this?

view this post on Zulip Notification Bot (Apr 23 2023 at 17:58):

Setzer22 has marked this topic as resolved.

view this post on Zulip Notification Bot (Apr 23 2023 at 17:58):

Setzer22 has marked this topic as unresolved.

view this post on Zulip bjorn3 (Apr 23 2023 at 19:27):

Cranelift doesn't have any helper code to handle calling conventions. It only exposes the primitives necessary like the StructArgument atgument purpose for values that have been determined to be passed as a struct on the stack. You need your own code to determine if the struct should be passed on the stack with StructArgument or put in registers.

view this post on Zulip bjorn3 (Apr 23 2023 at 19:28):

You could look at compiler/rustc_target/src/call in https://github.com/rust-lang/rust to see what it would roughly be. The code for sysv x86_64 is somewhat complex, but most other archs have simpler code.

Empowering everyone to build reliable and efficient software. - GitHub - rust-lang/rust: Empowering everyone to build reliable and efficient software.

view this post on Zulip Setzer22 (Apr 24 2023 at 08:45):

Thanks! I'll have a look :)

One question, though. When you say "Cranelift doesn't have any helper code to handle calling conventions", I'm assuming cranelift is still doing some work for me, right? For instance, picking the right registers in the right order, and fetching the return value from the right place too? Basically, how much does cranelift do for me here, and what do I need to care about on my end?

view this post on Zulip bjorn3 (Apr 24 2023 at 09:00):

Cranelift handles register assignment and in case of too much registers the placement of arguments on the stack. You are responsible for specifying the correct argument extension, argument purpose and lowering structs into primitive parts or using the struct argument argument purpose depending on the type. In addition for returns depending on the type you either add returns or add an extra pointer argument with the struct return argument purpose and write to this pointer on the callee side and pass a pointer to the location to put the return value as argument.

view this post on Zulip Chris Fallin (Apr 24 2023 at 16:04):

Just to make it extra-clear, I would say it as: Cranelift does handle calling conventions, but only for the concepts that exist in its IR. Primitive types (i32/64, f32/64, 128-bit vectors) exist, so Cranelift can pass them as args and return them. Structs do not exist at the Cranelift level, so there's no ABI handling; you're responsible for that lowering Hopefully that's a useful way to think about it

view this post on Zulip Setzer22 (May 01 2023 at 10:43):

Oops, forgot about replying. Thanks a lot for the answers! :) Things are a lot clearer now

view this post on Zulip Setzer22 (May 05 2023 at 18:28):

I'm finally delving into the intricacies of calling conventions :sweat_smile: I had a few questions about ArgumentPurpose.

First, there's StructArgument, which IIUC is necessary whenever the calling convention requires me to pass a struct via pointer to the stack (e.g. systemv requires this for structs with the MEMORY type). I don't understand why this takes a size argument. I thought I was expected to allocate a stack slot in the caller and pass its address as a pointer, but grepping through the code it seems cranelift-codegen itself is handling the allocation of that stack slot for me whenver I specify an argument with purpose StructArgument(size)? So my question is: Who is responsible from allocating that stack slot? And in case it's cranelift and not me, how do I figure out the address where I have to copy the struct data before the function call?

I'm also quite confused about StructReturn for similar reasons. StructReturn is used for large return types that don't fit in registers. In those cases, the caller allocates space on the stack, and passes a pointer as a hidden first argument. The callee then writes the result in that stack slot. Here, again, who allocates this stack slot? There's no size argument here, which makes me think it's the caller who allocates it.

Also about StructReturn. There's a note in the docs about some ABIs requiring it as a return type. But in the case of SystemV, the StructReturn argument has to be the first argument to the function, right?

Thanks again! And hope you don't mind I'm asking so much stuff

view this post on Zulip Setzer22 (May 05 2023 at 18:40):

how do I figure out the address where I have to copy the struct data before the function call

Okay, after looking into it a bit more, it seems cranelift-codegen is also handling the memcpy for me. So in that case, it looks like I should simply pass any pointer to the struct. Could someone confirm this is correct? :smile:

view this post on Zulip Chris Fallin (May 05 2023 at 19:18):

cc @bjorn3 who has implemented this for cg_clif

view this post on Zulip bjorn3 (May 05 2023 at 19:20):

For StructArgument the argument is a pointer to the value you want to pass. Cranelift will copy it to the location on the stack where the callee expects it and on the callee side, the parameter will be a pointer to this copy.

view this post on Zulip Setzer22 (May 05 2023 at 19:22):

Thanks! So it seems I was on the right track in the end

view this post on Zulip Setzer22 (May 05 2023 at 19:22):

And in the case of StructReturn, I guess I'm responsible for allocating the satck space and providing a pointer argument, correct?

view this post on Zulip bjorn3 (May 05 2023 at 19:23):

Indeed. For StructReturn the caller passes a pointer to a chunk of memory large enough to hold the return value and then the callee can write to this memory. The only reason that it is a separate argument purpose is that some calling conventions pass the struct return pointer in a register which isn't used for regular arguments.

view this post on Zulip bjorn3 (May 05 2023 at 19:24):

AArch64 for example passes it in x8, but uses x0-x7 for regular args. x86 however simply puts it as first argument. Note that Cranelift doesn't currently handle this on x86, so you need to ensure that the StructReturn argument is the first argument.

view this post on Zulip Setzer22 (May 06 2023 at 09:46):

Another thing I wanted to ask about is packing struct fields in registers. Let's say I have a {i32, i32, i64} struct. Do I have two pass that as three AbiParams (two with types::I32, and one with types::I64), or do I have to follow the calling convention and pack the first two into an i64 AbiParam (and thus, have a signature with just two arguments)? Basically, who is responsible for doing that, me or cranelift? (my example assumes SystemV)

view this post on Zulip bjorn3 (May 06 2023 at 14:03):

You have to pack it if the calling convention specifies this. Cranelift doesn't have native struct types. It only exposes the primitives necessary to handle them around the calling convention, but it is the responsibility of the clir ir producer to use those primitives according to the requirements of the calling convention.

view this post on Zulip Setzer22 (May 06 2023 at 14:08):

So, back to my example, this means if the ABI tells me I have to pack the first two ints into a single register, the signature I build in CLIF should have two arguments, not three, and I have to do some bit-fiddling on the frontend to actually produce the packed value. Did I get that right?

view this post on Zulip bjorn3 (May 06 2023 at 18:59):

Yes

view this post on Zulip Setzer22 (May 08 2023 at 18:08):

Ok, seems I finally got it working! :grinning_face_with_smiling_eyes:
image.png

Thanks a lot for bearing with me! :smile: Can't promise I won't be back with more questions, but in exchange, if you think there's a good place in the cranelift docs where I could write a piece on passing / returning structs by value, I think that could be valuable to future readers (and I'd be up for the task)

view this post on Zulip Chris Fallin (May 08 2023 at 18:15):

@Setzer22 that's great, congrats!

Off the top of my head, we don't really have many good documents on "ways to use Cranelift to implement common language features" -- a section on that (i.e. a new Markdown file in cranelift/docs/) might be really nice. It could eventually be a part of a user's/embedder's guide, or example/tutorial language implementation, etc. In any case, please feel free to write up something and we can hammer it into the shape we want incrementally!

view this post on Zulip Setzer22 (May 09 2023 at 18:22):

Alright! I'll write up something and let you know

view this post on Zulip Taylor Holliday (May 29 2023 at 16:24):

bjorn3 said:

You could look at compiler/rustc_target/src/call in https://github.com/rust-lang/rust to see what it would roughly be. The code for sysv x86_64 is somewhat complex, but most other archs have simpler code.

Seems there is no longer a call dir in compiler/rustc_target/src ... where should I look these days for an example of lowering structs?

view this post on Zulip Taylor Holliday (May 29 2023 at 16:25):

Setzer22 said:

Alright! I'll write up something and let you know

Did you write up something? or do you have some code online? :)

view this post on Zulip Taylor Holliday (May 29 2023 at 16:42):

I'm guessing this is codegen for struct field accesses? https://github.com/rust-lang/rust/blob/498553fc04f6a3fdc53412320f4e913bc53bc267/compiler/rustc_codegen_cranelift/src/value_and_place.rs#L8

Empowering everyone to build reliable and efficient software. - rust/value_and_place.rs at 498553fc04f6a3fdc53412320f4e913bc53bc267 · rust-lang/rust

view this post on Zulip bjorn3 (May 29 2023 at 17:42):

Taylor Holliday said:

bjorn3 said:

You could look at compiler/rustc_target/src/call in https://github.com/rust-lang/rust to see what it would roughly be. The code for sysv x86_64 is somewhat complex, but most other archs have simpler code.

Seems there is no longer a call dir in compiler/rustc_target/src ... where should I look these days for an example of lowering structs?

Was missing abi/ in between. The right link is https://github.com/rust-lang/rust/tree/master/compiler/rustc_target/src/abi/call

Empowering everyone to build reliable and efficient software. - rust/compiler/rustc_target/src/abi/call at master · rust-lang/rust

view this post on Zulip bjorn3 (May 29 2023 at 17:43):

Taylor Holliday said:

I'm guessing this is codegen for struct field accesses? https://github.com/rust-lang/rust/blob/498553fc04f6a3fdc53412320f4e913bc53bc267/compiler/rustc_codegen_cranelift/src/value_and_place.rs#L8

Yeah. That code is not really relevant to the calling convention though.

view this post on Zulip bjorn3 (May 29 2023 at 17:45):

The code around https://github.com/rust-lang/rust/blob/498553fc04f6a3fdc53412320f4e913bc53bc267/compiler/rustc_codegen_cranelift/src/abi/pass_mode.rs#L78 is relevant. cg_clif uses rustc_target for calculating the abi to use and this code actually applies the calculated abi.

Empowering everyone to build reliable and efficient software. - rust/pass_mode.rs at 498553fc04f6a3fdc53412320f4e913bc53bc267 · rust-lang/rust

view this post on Zulip Chris Clark (Jul 03 2023 at 18:41):

Setzer22 said:

Alright! I'll write up something and let you know

ever manage to get around to doing this :) ?


Last updated: Nov 22 2024 at 17:03 UTC