Skip to content

-Zregparm doesn't put 64-bit arguments in registers #145694

Description

@sulix

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

Activity

  1. added
    needs-triageThis issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triaging
    on Aug 21, 2025
  2. added
    A-ABIArea: Concerning the application binary interface (ABI)
    on Aug 21, 2025
  3. added
    O-x86_32Target: x86 processors, 32 bit (like i686-*) (also known as IA-32, i386, i586, i686)
    A-rust-for-linuxRelevant for the Rust-for-Linux project
    on Aug 21, 2025
  4. ojeda commented on Aug 21, 2025

    @ojeda
    Contributor
  5. bjorn3 commented on Aug 21, 2025

    @bjorn3
    Member

    Maybe the size <= 32 at

    if arg.layout.size.bits() <= 32 && unit.kind == RegKind::Integer {
    attrs.set(ArgAttribute::InReg);
    }
    should be removed or replaced by size <= 64?

  6. sulix commented on Aug 21, 2025

    @sulix
    Author

    Looking at what clang does, it seems that regparm has a pretty different set of ABI rules to fastcall/vectorcall/regcall. In particular:

    • Unlike those ABIs, regparm isn't limited to primitive types (pointer/int).
    • The 32bit limit seems to be correct for fastcall/vectorcall/regcall, but not for regparm.

    At the very least, -mregparm ought to pass small structs in registers (but only, afaict, if the whole struct fits, it won't split a struct up between registers and the stack). See: https://godbolt.org/z/EYTszP457

    Of course, this seems to contradict the gcc docs, which state that regparm causes the:

    compiler to pass arguments number one to number if they are of integral type

    So, unless "integral type" includes structs (even with floating-point members), I suspect the documentation is being misleading here, and the fix should be more like

    if opts.flavor != Flavor::FastcallOrVectorcall || arg.layout.size.bits() <= 32 && unit.kind == RegKind::Integer { 
        attrs.set(ArgAttribute::InReg); 
    }

    But I haven't tried testing fastcall or vectorcall, so don't take my word on that blindly.

  7. workingjubilee commented on Aug 23, 2025

    @workingjubilee
    Member

    It seems we cannot have fastcall and regparm share as much logic as we thought.

  8. added
    T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.
    -ZregparmUnstable option: -Zregparm
    and removed
    needs-triageThis issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triaging
    on Aug 24, 2025
  9. added a commit that references this issue on Oct 12, 2025
    8118dc6
  10. sulix commented on Oct 13, 2025

    @sulix
    Author

    FYI: Sent out the fix in my previous comment as PR #147628 for ease of testing. A 32-bit x86 Linux build passed all of the rust KUnit & doctests with it, so it seems at least plausibly correct.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    -ZregparmUnstable option: -ZregparmA-ABIArea: Concerning the application binary interface (ABI)A-rust-for-linuxRelevant for the Rust-for-Linux projectC-bugCategory: This is a bug.O-x86_32Target: x86 processors, 32 bit (like i686-*) (also known as IA-32, i386, i586, i686)T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions