Difference between "byval" and actually passing by value?

There appears to be a difference between passing by value:

call void @foo\(%some\_struct %v\)

And using the `byval` attribute on a pointer:

call void @foo\(%some\_struct\* byval %v\)

They are not compatible with each other yet logically do the same
thing. The second form is the one that appears to work with the ABI on
linux, and the first one not.

What is the reason for the difference?

Hi,

They are not compatible with each other yet logically do the same
thing. The second form is the one that appears to work with the ABI on
linux, and the first one not.

What is the reason for the difference?

If you pass a struct directly, the LLVM target will just see an
exploded list of the basic types (e.g. i8, i8, i32, ...) and have to
classsify them individually. This is usually too simplistic to model a
real C ABI, so it's not used much. But LLVM does have to give it some
meaning because it's still a legitimate IR function. The result is
that you tend to get the arguments out again if everything is
consistent, but where they actually get passed is a bit random.

A byval parameter is usually used to model a struct that gets passed
on the stack; the target sees the size & alignment constraints, but
not each individual sub-type. This is often actually used in C ABIs
for a couple of reasons. First, the extra alignment parameter lets you
pass structs with above-natural alignment correctly. Second, because
the type actually is in memory, you tend to get a cleaner mapping from
IR to machine instructions if the IR is represented that way. Of
course, peepholes could probably fix a lot of that if we really cared.

For structs that get passed in registers, the front-ends tend to
coerce them to some type with the same constraints (register type,
size, alignment, ...) in a pretty ad-hoc manner when emitting IR.
That's actually responsible for a lot of the difficulty in calling C
code from LLVM.

Cheers.

Tim.

Super, that clarifies a lot what happens. And yes, it's been a
challenge calling C APIs with by-value structures, but I think I've got
it working now, at least on x86_64 Linux.

If I understood correctly, when llvm sees a struct like `foo = { i8,
i64, float }` and then a function like `bar( %foo )` it is the same as
the function `bar( i8, i64, float )`? Is the call guaranteed to be byte
compatible, or should I not rely on it (I don't of course).

What is the reason that `byval` requires a pointer type, as opposed to
being able to take a value type? It's forcing me to copy values into an
alloca memory, which I presume will then just be copied again on to the
stack -- looking at the optimized output the redundancy is not disappearing.

If I understood correctly, when llvm sees a struct like `foo = { i8,
i64, float }` and then a function like `bar( %foo )` it is the same as
the function `bar( i8, i64, float )`? Is the call guaranteed to be byte
compatible, or should I not rely on it (I don't of course).

I don't think it's really a guarantee, just an implementation detail.
I would always use functions with self-consistent prototypes.

What is the reason that `byval` requires a pointer type, as opposed to
being able to take a value type?

Non-pointers are intrinsically passed by value. I can see there might
be a use for an "align" tag there too, but no-one has proposed or
implemented it.

Cheers.

Tim.