-
Notifications
You must be signed in to change notification settings - Fork 13.8k
Description
The -Zregparm
option on 32-bit x86 should pass arguments in registers where possible, but for 64-bit arguments, it will push them onto the stack, whereas C compilers (both gcc and clang) will put them in a pair of registers.
For example, the following rust code:
unsafe extern "C" {
pub fn test(s: i64) -> i64;
}
#[unsafe(no_mangle)]
pub fn f1() {
unsafe {
let r = test(42);
}
}
compiles to (with -Zregparm=3
):
f1:
sub esp, 20
push 0
push 42
call test
add esp, 28
ret
Note the 64-bit value is put on the stack.
However, the equivalent C code:
extern long long test(long long s);
void f1(void)
{
long long r = test(42);
}
compiles to (gcc 15.2 with -mregparm=3
, but clang produces something similar):
f1():
push ebp
mov ebp, esp
sub esp, 24
mov eax, 42
mov edx, 0
call test(long long)
mov DWORD PTR [ebp-16], eax
mov DWORD PTR [ebp-12], edx
nop
leave
ret
Note that the 64-bit value is put in edx:eax.
This can cause crashes/incorrect results due to an ABI mismatch, particularly in Rust-for-Linux on 32-bit x86.
Note that 64-bit return values seem to be functioning correctly, and are placed in edx:eax.
Compiler explorer link with the above examples: https://godbolt.org/z/feb7addG7
Rust-for-Linux tracking issue for 32-bit x86: Rust-for-Linux/linux#78
Tracking issue for -Zregparm
: #131749