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?
According to the Book
and from the FFI chapter
as well as here:
However, when I actually compile
pub externnon-mangled rust functions on x86-64 systems, i.e.: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:
and without extern:
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:
(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:
externkeyword on rust shared library function definitions is ideally supposed to cause.I imagine there is some case in which the
externkeyword 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 futureexternguarantees "C ABI" calling conventions (x86-64 System V ABI on Linux/OSX), whereas functions withoutexternpresent may or may not be free to use an entirely different calling convention?