-
Notifications
You must be signed in to change notification settings - Fork 12.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
The sparc64, riscv64, loongarch64 extern "C" fn
ABIs are all wrong when aligned/packed structs are involved
#115609
Comments
Looks like clang will split struct P {
int8_t a;
float b;
} __packed__; into two arguments on rv64gc with |
So that's |
In this case it seemed like two separate args were emitted. AFAIK LLVM will internally recursively split structs into separate arguments anyway though if |
The |
The issue is with how we turn the value as represented in memory into an argument that can be passed in registers. Currently we assume that the value as represented in memory and the llvm type we use for argument passing have the same layout, which is not the case for aligned/packed structs as the llvm type has a different amount of padding from the real type. |
The issue is: when I declare a packed type in C, and a packed type in Rust, then they should be ABI-compatible. But currently they are not. If you take the type |
Thanks for your explanation. ❤️ I tried the test cases you mentioned, and indeed there is an issue when accessing packed structure fields through memory addresses. t.rs: #[repr(packed, C)]
struct P {
i: i8,
f: f32,
}
#[no_mangle]
extern "C" fn test(p: P) {
let ip = std::ptr::addr_of!(p.i);
let fp = std::ptr::addr_of!(p.f);
let i = unsafe { std::ptr::read_unaligned(ip) };
let f = unsafe { std::ptr::read_unaligned(fp) };
println!("{:p} {:p} {} {}", ip, fp, i, f);
} t.c: struct P
{
char i;
float f;
} __attribute__((packed));
extern void test(struct P p);
int
main (int argc, char *argv[])
{
struct P p;
p.i = 16;
p.f = 128.0;
test(p);
return 0;
} run: rustc --crate-type=cdylib -o libt.so t.rs; gcc -o t t.c -L . -l t; LD_LIBRARY_PATH=`pwd` ./t x86_64 with
x86_64 without
loongarch64 with
loongarch64 without
|
Interesting. x86_64 uses indirect pass mode for packed structs, so... that can't really be an ABI difference. Not sure what is happening. Do the field offsets match on both sides? |
This comment was marked as outdated.
This comment was marked as outdated.
I can't read assembly so I can't quite figure out what that gdb snippet at the end tells me. |
For this type: #[repr(packed, C)]
struct P {
i: i8,
f: f32,
}
#[no_mangle]
extern "C" fn test(p: P) {
let ip = std::ptr::addr_of!(p.i);
let fp = std::ptr::addr_of!(p.f);
println!("{:p} {:p}", ip, fp);
} The callee creates a temporary variable on the stack with the same type and alignment as the parameter, called The issue is that rust/compiler/rustc_codegen_llvm/src/abi.rs Lines 201 to 259 in cc7a9d6
|
The first question is, what even is the C ABI for packed types on this target, how do they get passed? Is it indirect, or are the fields spread across registers, and if yes how? Only after answering that question then can we try to figure out how to encode that ABI in rustc + LLVM. |
@RalfJung That's a good point. For the current 'packed' instance, which consists of two fields (one integer and the other floating-point), there is a unique case to consider. In the context of the calling convention of LoongArch, it's important to note that integers are passed via GAR (General Argument Register) while floating-point values are passed via FAR (Floating-point Argument Register). I'm not sure how other ISAs handle this. From a professional Rust perspective, what approach do you think would be more suitable to start with? |
So, this passing in registers applies even for packed structs? Sadly that document you linked doesn't mention packed structs at all. I think this means that we want to generate an LLVM function with type If this hypothesis is correct, then we need to extend rustc However, even with that we get into trouble for more complicated types, such as #[repr(packed)]
struct P(i8, f32);
#[repr(C)]
struct Arg(i8, i32, P); The document seems to say that this is passed the same as This is a concern even without packed; |
Is the problematic case https://godbolt.org/z/jWEoncaP6? Specifically the fact that the load is used on a non-packed struct? (Packed / non-packed shouldn't matter for the call itself.) I think we should generally avoid passing struct arguments on the LLVM IR level (only use structs for pair returns) -- I believe these will always get scalarized anyway, and just directly passing these in scalarized form is more amenable to optimization and makes the ABI more obvious. |
So, you're saying if the ABI says "pass this struct in a float register and an int register", then we should generate two LLVM IR arguments, of type That makes a lot of sense. However, that would be a significant departure from our current argument handling, and requires a fundamental refactor of |
Using |
It might be, but that's not helpful for this issue since we cannot transmute from the packed type to that struct type. I was very surprised by
Those might be equivalent on the ABI side? I don't know. Either way we need to emit the appropriate code to deconstruct and reconstruct the Rust value on both ends. For returns, I think we'll have to use struct types in LLVM function signatures anyway. So we might as well do it everywhere? |
Sorry for late. The rules for structures also apply to "packed". |
@nikic is there any documentation of how the LLVM ABI works when LLVM functions take structs as arguments (and return type)? Looks like attributes such as |
@rustbot label I-unsound |
WG-prioritization assigning priority (Zulip discussion). @rustbot label -I-prioritize +P-medium |
cc @kito-cheng @michaelmaitland @robin-randhawa-sifive Please triage this issue. It would be nice to confirm whether this is fixed or not for RISCV. |
All of these ABIs have in common that they are looking at
layout.fields
to compute the ABI for the function, and they are all getting it wrong, in various different ways. (I previously reported bugs against all of these becauserepr(transparent)
is not properly respected; those bugs have the same cause -- relying onlayout.fields
-- but they are orthogonal.)For instance, this type
on riscv64 gets a PassMode::Cast of
which gets translated to the LLVM type
{ i8, f32 }
, and that's a non-packed LLVM struct. On a call,P
gets transmuted to that LLVM type, which obviously produces garbage since the fields are at different offsets.On sparc64, the type translates into
{ i32, f32 }
which is wrong as well.As another example, this type
on loongarch64 gets a PassMode::Cast of
which is the LLVM type
{ i32, f32 }
, and that's obviously not the right type forS2
.(Caveat: I can't actually run code on these targets, so there's a small chance that I am completely misinterpreting what these
PassMode
mean. I hope that's not the case...)I don't know what the C ABI on those platforms has to say about packed and aligned structs. Currently, we don't even provide ABIs a way to generate a packed LLVM struct for the arguments --
PassMode::Cast
always uses an un-packed LLVM struct.The text was updated successfully, but these errors were encountered: