#Abi
#Abi fixes the calling convention of a function: which registers and stack slots carry its arguments and result. Between two Rux functions the compiler handles the convention itself, so most code never names one. #Abi matters at a border with code the compiler did not generate — a C library calling back into Rux, or an asm func whose body reads its arguments from particular registers.
abi-attribute = "#Abi" "(" "." ( "C" | "SysV" | "Win64" ) ")"
| Argument | Convention |
|---|---|
.C | the C convention of the target being built |
.SysV | System V AMD64: integer arguments in rdi, rsi, rdx, rcx, r8, r9; result in rax |
.Win64 | Microsoft x64: integer arguments in rcx, rdx, r8, r9; result in rax |
#Abi applies to a function, an asm func, an extern func and an extern { } block, whose members all take it. Anything else is refused:
error: '#Abi' can only be applied to a function or extern block
error: unknown ABI '.Fast'; valid ABIs are: .C, .SysV, .Win64
Defaults
Without #Abi, a function, an asm func and an extern all use the target's C convention — exactly what #Abi(.C) names:
| Target | Default (.C) |
|---|---|
| Windows on x86-64 | Win64 |
| Linux, macOS and FreeBSD on x86-64 | System V AMD64 |
| every system on AArch64 | AAPCS64 (arguments in x0–x7, result in x0) |
.SysV and .Win64 name x86-64 conventions. Pinning one makes a function use it on every x86-64 target: the compiler adapts each call, so a Rux caller on Windows passes the arguments of a #Abi(.SysV) function in rdi and rsi.
AArch64 has one convention, which .C names, so #Abi(.SysV) or #Abi(.Win64) in an AArch64 build is an error:
error: '#Abi(.Win64)' names an x86-64 calling convention, which a build for AArch64 cannot use
note: AArch64 has one calling convention, which '#Abi(.C)' names
help: write '#Abi(.C)', or keep the x86-64 declaration in a 'when #target.arch' arm
A function C calls
A function handed to C as a callback must use the convention C uses. Rux functions already do by default, but #Abi(.C) writes the promise down — so it stays true, and so the next reader knows the function is called from outside Rux:
import Core::{ #target, #Error };
import Io::PrintLine;
when #target.os {
.FreeBSD => const CRuntime = "libc.so.7";
.Linux => const CRuntime = "libc.so.6";
.macOS => const CRuntime = "libSystem.B.dylib";
.Windows => const CRuntime = "ucrtbase.dll";
else => #Error("unknown C runtime")
}
#Link(CRuntime)
extern func qsort(base: *var opaque, count: uint, size: uint, compare: func(*opaque, *opaque) -> int32);
// Called by C, so it promises C's convention.
#Abi(.C)
func Descending(left: *opaque, right: *opaque) -> int32 {
let a = *(left as *int32);
let b = *(right as *int32);
return a > b ? -1i32 : a < b ? 1i32 : 0i32;
}
func Main() -> int {
var values: int32[5] = [3, 9, 1, 7, 5];
qsort(@values[0] as *var opaque, values.length, sizeof(int32), Descending);
PrintLine("{} {} {} {} {}", values[0], values[1], values[2], values[3], values[4]); // 9 7 5 3 1
return 0;
}
A C convention can only carry the floating-point types C has, float32 and float64. A #Abi function whose parameter or result is float8, float16, or float80 or wider is refused with parameter 'value' of '#Abi' function 'Callback' has type 'float16', which has no C ABI mapping; see Foreign Function Interface.
A wrong convention is not an error
The compiler cannot see what the other side expects. Pin Descending to #Abi(.SysV) and build for Windows: qsort passes the two addresses in rcx and rdx, the function reads rdi and rsi, and it compares whatever was there. Nothing reports it. The same holds for an asm func body written for one convention and pinned to another. Use .C for anything C calls, and pin an asm func to the convention its body was written for.
See also
- Assembly — bodies written against a convention
- Foreign function interface — extern declarations and function pointers
- Compile-time context —
#target.abi, the target's own convention - Learn: ABI, Assembly