Skip to content

does extern keyword actually affect a function's calling convention? #28740

@m4b

Description

@m4b

According to the Book

The pub means that this function should be callable from outside of this module, and the extern says that it should be able to be called from C. That’s it! Not a whole lot of change.

and from the FFI chapter

The extern block is a list of function signatures in a foreign library, in this case with the platform's C ABI.

as well as here:

The extern makes this function adhere to the C calling convention, as discussed above in "Foreign Calling Conventions".

However, when I actually compile pub extern non-mangled rust functions on x86-64 systems, i.e.:

#![crate_type = "dylib"]

#[no_mangle]
pub extern fn beef_float (x: f64, y: f64) -> f64 {
    x * y
}

#[no_mangle]
pub extern fn beef_maximum (x: u64, y: u64) -> u64 {
    if x >= y {
        x
    } else {
        y
    }
}

#[no_mangle]
pub extern fn beef_stack (d: u64, e: u64, a: u64, d2: u64, b: u64, e2: u64, a2: u64, f: u64) -> u64 {
    return 0xefbeadde + d + e + a + d2 + b + e2 + a2 + f;
}

#[no_mangle]
pub extern fn beef_pointer (p: &u64, x: u64) -> u64 {
    return *p + x + 0xdeadbeef;
}

with something like rustc -O -C prefer-dynamic, I don't actually see any difference in generated libraries' ABI calling convention, i.e., it's still the standard System V x86-64 calling conventions.

E.g., with extern keyword:

(gdb) disass beef_stack
Dump of assembler code for function beef_stack:
   0x0000000000000730 <+0>: lea    (%rdi,%rsi,1),%rsi
   0x0000000000000734 <+4>: add    %rdx,%rsi
   0x0000000000000737 <+7>: add    %rcx,%rsi
   0x000000000000073a <+10>:    add    %r8,%rsi
   0x000000000000073d <+13>:    add    %r9,%rsi
   0x0000000000000740 <+16>:    add    0x8(%rsp),%rsi
   0x0000000000000745 <+21>:    add    0x10(%rsp),%rsi
   0x000000000000074a <+26>:    mov    $0xefbeadde,%eax
   0x000000000000074f <+31>:    add    %rsi,%rax
   0x0000000000000752 <+34>:    retq   
End of assembler dump.
(gdb) disass beef_float
Dump of assembler code for function beef_float:
   0x0000000000000710 <+0>: mulsd  %xmm1,%xmm0
   0x0000000000000714 <+4>: retq   
End of assembler dump.
(gdb) disass beef_maximum
Dump of assembler code for function beef_maximum:
   0x0000000000000720 <+0>: cmp    %rsi,%rdi
   0x0000000000000723 <+3>: cmovb  %rsi,%rdi
   0x0000000000000727 <+7>: mov    %rdi,%rax
   0x000000000000072a <+10>:    retq   
End of assembler dump.
(gdb) disass beef_pointer
Dump of assembler code for function beef_pointer:
   0x00000000000007b0 <+0>: add    (%rdi),%rsi
   0x00000000000007b3 <+3>: mov    $0xdeadbeef,%eax
   0x00000000000007b8 <+8>: lea    (%rax,%rsi,1),%rax
   0x00000000000007bc <+12>:    retq   
End of assembler dump.

and without extern:

(gdb) disass beef_stack
Dump of assembler code for function beef_stack:
   0x0000000000000770 <+0>: lea    (%rdi,%rsi,1),%rsi
   0x0000000000000774 <+4>: add    %rdx,%rsi
   0x0000000000000777 <+7>: add    %rcx,%rsi
   0x000000000000077a <+10>:    add    %r8,%rsi
   0x000000000000077d <+13>:    add    %r9,%rsi
   0x0000000000000780 <+16>:    add    0x8(%rsp),%rsi
   0x0000000000000785 <+21>:    add    0x10(%rsp),%rsi
   0x000000000000078a <+26>:    mov    $0xefbeadde,%eax
   0x000000000000078f <+31>:    add    %rsi,%rax
   0x0000000000000792 <+34>:    retq   
End of assembler dump.
(gdb) disass beef_float
Dump of assembler code for function beef_float:
   0x0000000000000750 <+0>: mulsd  %xmm1,%xmm0
   0x0000000000000754 <+4>: retq   
End of assembler dump.
(gdb) disass beef_maximum
Dump of assembler code for function beef_maximum:
   0x0000000000000760 <+0>: cmp    %rsi,%rdi
   0x0000000000000763 <+3>: cmovb  %rsi,%rdi
   0x0000000000000767 <+7>: mov    %rdi,%rax
   0x000000000000076a <+10>:    retq
End of assembler dump.
(gdb) disass beef_pointer
Dump of assembler code for function beef_pointer:
   0x00000000000007b0 <+0>: add    (%rdi),%rsi
   0x00000000000007b3 <+3>: mov    $0xdeadbeef,%eax
   0x00000000000007b8 <+8>: lea    (%rax,%rsi,1),%rax
   0x00000000000007bc <+12>:    retq   
End of assembler dump.

I.e., the functions produced are the same, and the parameters are passed in the expected registers or on the stack, all according to the X86-64 System V calling conventions.

Similarly on OSX:

(lldb) disass -n beef_float
good.dylib`beef_float:
good.dylib[0xe40] <+0>:  cmpq   %gs:0x330, %rsp
good.dylib[0xe49] <+9>:  ja     0xe65                     ; <+37>
good.dylib[0xe4b] <+11>: movabsq $0x8, %r10
good.dylib[0xe55] <+21>: movabsq $0x0, %r11
good.dylib[0xe5f] <+31>: callq  0xf37                     ; __morestack
good.dylib[0xe64] <+36>: retq   
good.dylib[0xe65] <+37>: pushq  %rbp
good.dylib[0xe66] <+38>: movq   %rsp, %rbp
good.dylib[0xe69] <+41>: mulsd  %xmm1, %xmm0
good.dylib[0xe6d] <+45>: popq   %rbp
good.dylib[0xe6e] <+46>: retq   
good.dylib[0xe6f] <+47>: nop    

(lldb) disass -n beef_float
bad.dylib`beef_float:
bad.dylib[0xd90] <+0>:  cmpq   %gs:0x330, %rsp
bad.dylib[0xd99] <+9>:  ja     0xdb5                     ; <+37>
bad.dylib[0xd9b] <+11>: movabsq $0x8, %r10
bad.dylib[0xda5] <+21>: movabsq $0x0, %r11
bad.dylib[0xdaf] <+31>: callq  0xe87                     ; __morestack
bad.dylib[0xdb4] <+36>: retq   
bad.dylib[0xdb5] <+37>: pushq  %rbp
bad.dylib[0xdb6] <+38>: movq   %rsp, %rbp
bad.dylib[0xdb9] <+41>: mulsd  %xmm1, %xmm0
bad.dylib[0xdbd] <+45>: popq   %rbp
bad.dylib[0xdbe] <+46>: retq   
bad.dylib[0xdbf] <+47>: nop   

(although it's unclear to me why the optimization flag on OSX doesn't remove the unnecessary frame pointer calculations and book-keeping)

So I'm curious about:

  • what (if any) difference including/excluding the extern keyword on rust shared library function definitions is ideally supposed to cause.
  • what kind of functions might be subject to this change, if there is one
  • if it does nothing, why is it present/suggested?

I imagine there is some case in which the extern keyword does something (in which case it would be nice if the documentation red flags these cases), or perhaps currently it does do nothing, but in the future extern guarantees "C ABI" calling conventions (x86-64 System V ABI on Linux/OSX), whereas functions without extern present may or may not be free to use an entirely different calling convention?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions