#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" ) ")"
ArgumentConvention
.Cthe C convention of the target being built
.SysVSystem V AMD64: integer arguments in rdi, rsi, rdx, rcx, r8, r9; result in rax
.Win64Microsoft 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:

TargetDefault (.C)
Windows on x86-64Win64
Linux, macOS and FreeBSD on x86-64System V AMD64
every system on AArch64AAPCS64 (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