Skip to content

Call to Rust extern "fastcall" function does not follow GNU/Clang fastcall convention #18086

Closed
@rprichard

Description

@rprichard

For a call to an extern "fastcall" function on 32-bit Linux, rustc passes arguments via the stack instead of via registers, and it doesn't pop the arguments from the stack after the call. I was expecting rustc to follow the GNU/Clang "fastcall" convention (http://en.wikipedia.org/wiki/X86_calling_conventions#fastcall). Instead, it seems to be following something like the "stdcall" convention.

Test case:

cat > fastcall_callee.c << EOF
#include <stdio.h>
void callee(int x, int y) __attribute__((fastcall));
void callee(int x, int y) {
    printf("%d %d\n", x, y);
}
EOF

cat > fastcall_good_caller.c << EOF
void callee(int x, int y) __attribute__((fastcall));
void good_caller() {
    callee(7, 11);
    callee(17, 19);
}
EOF

cat > fastcall_caller.rs << EOF
extern crate libc;
#[link(name = "fastcall_callee")]
extern "fastcall" {
    fn callee(x: libc::c_int, y: libc::c_int);
}
pub fn main() {
    unsafe {
        callee(7, 11);
        callee(17, 19);
    }
}
EOF

clang -m32 fastcall_callee.c -c -O2
rm -f libfastcall_callee.a
ar rf libfastcall_callee.a fastcall_callee.o

export LD_LIBRARY_PATH=$PWD/rust-nightly-i686-unknown-linux-gnu/lib
rust-nightly-i686-unknown-linux-gnu/bin/rustc fastcall_caller.rs -L . -O

./fastcall_caller

Output:

-159272960 -7321680
0 -7323132
Segmentation fault (core dumped)

Here's the assembly output from main (with some stuff elided):

_ZN4main20h75001e02b331b561raaE:
    cmpl    %gs:48, %esp
    ja  .LBB0_2
    pushl   $0
    pushl   $12
    calll   __morestack
    retl
.LBB0_2:
    pushl   %ebx
    subl    $8, %esp
    calll   .L0$pb
.L0$pb:
    popl    %ebx
.Ltmp3:
    addl    $_GLOBAL_OFFSET_TABLE_+(.Ltmp3-.L0$pb), %ebx
    movl    $11, 4(%esp)
    movl    $7, (%esp)
    calll   callee@PLT
    subl    $8, %esp
    movl    $19, 4(%esp)
    movl    $17, (%esp)
    calll   callee@PLT
    popl    %ebx
    retl

I compiled both fastcall_good_caller.c and fastcall_caller.rs to LLVM IR, and the difference seems to be an inreg keyword. Clang outputs it, but Rust doesn't.

fastcall_good_caller.ll:

; Function Attrs: nounwind
define void @good_caller() #0 {
  tail call x86_fastcallcc void @callee(i32 inreg 7, i32 inreg 11) #2
  tail call x86_fastcallcc void @callee(i32 inreg 17, i32 inreg 19) #2
  ret void
}

attributes #0 = { nounwind "less-precise-fpmad"="false" "no-frame-pointer-elim"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="8" "unsafe-fp-math"="false" "use-soft-float"="false" }
attributes #2 = { nounwind }

fastcall_caller.ll:

; Function Attrs: uwtable
define internal void @_ZN4main20h75001e02b331b561raaE() unnamed_addr #1 {
entry-block:
  tail call x86_fastcallcc void @callee(i32 7, i32 11)
  tail call x86_fastcallcc void @callee(i32 17, i32 19)
  ret void
}

attributes #1 = { uwtable "split-stack" }

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-codegenArea: Code generation

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions