# SysV: the kernel's calling convention Several places in danos say "the kernel is SysV" — most visibly `system/boot-handoff.zig`: ```zig pub const kernel_abi: std.builtin.CallingConvention = .{ .x86_64_sysv = .{} }; ``` **SysV** is short for the **System V AMD64 ABI**, the calling convention that Unix-like systems (Linux, the BSDs, macOS) use on x86-64. This page explains what that means and why danos has to pin it explicitly. ## What a calling convention is At the machine level there's no language keeping two functions honest when one calls the other — just registers and a stack. So there has to be a shared agreement on the mechanics: - which registers carry the **arguments**, and in what order, - where the **return value** goes, - which registers the callee must **preserve** versus may freely clobber, - the **stack alignment** required at a call, - how larger things (structs, floats, varargs) are passed. That agreement is the calling convention. Both sides of a call must be compiled to the *same* one, or they read arguments out of the wrong registers and get garbage. ("ABI" — Application Binary Interface — is the broader term, also covering type sizes and object-file format; here we mean the calling-convention part.) ## What SysV specifies (the parts that matter here) Integer and pointer arguments go in this register sequence: | arg | 1 | 2 | 3 | 4 | 5 | 6 | |------|---------|-----|-----|-----|----|----| | reg | **RDI** | RSI | RDX | RCX | R8 | R9 | The return value comes back in **RAX**. RBX, RBP and R12–R15 are **callee-saved** (a function must restore them before returning); the rest are caller-saved. The stack must be 16-byte aligned at a `call`. And there's a **red zone** — 128 bytes below RSP that a function may use as scratch without adjusting RSP. The name is historical: it descends from AT&T's *System V* Unix, whose ABI documents this lineage comes from. The modern spec is the "System V Application Binary Interface, AMD64 Architecture Processor Supplement." ## Why danos pins it: RDI vs RCX The reason this is called out explicitly is a clash with the *other* common x86-64 convention, **Microsoft x64** — used by Windows **and UEFI** — where the first argument arrives in **RCX**, not RDI. danos's two binaries default to different conventions: - `boot/efi.zig` is built for the UEFI target, so its default C convention is Microsoft x64 (first argument → RCX). - The kernel is freestanding, so its convention is SysV (first argument → RDI). When the loader jumps to the kernel passing the `BootInfo` pointer, both sides have to agree *which register that pointer lands in*. Left to their defaults, the loader would place it in RCX while the kernel looked in RDI — and the kernel would read garbage. So both sides reference the same `system.kernel_abi` (SysV): the loader's function-pointer type and the kernel's `_start` both carry `callconv(system.kernel_abi)`, and the pointer reliably arrives in RDI. That is the whole reason `kernel_abi` lives in the shared contract — see [efi.md](efi.md) for the handoff it governs. ## Where else it surfaces - **The red zone → `red_zone = false`.** `build.zig` disables the red zone for the kernel. Interrupts push their frame onto the current stack; if the interrupted code was using its 128-byte red zone, that push would stomp it. Turning the red zone off is the standard fix for kernel code — a direct consequence of SysV *having* a red zone. (See [interrupts.md](interrupts.md).) - **`callconv(.c)` == SysV here.** The exception/interrupt dispatcher (`interruptDispatch`) is declared `callconv(.c)`, which resolves to SysV on this target. That's why the assembly stub in `isr.s` moves the `CpuState` pointer into **RDI** before `call`ing it — the same first-argument rule. So "the kernel is SysV" means: its functions pass arguments in RDI/RSI/RDX/…, return in RAX, preserve the SysV callee-saved registers, and assume a red zone — and every boundary that calls into the kernel (the loader, the interrupt stubs) has to speak that same convention at the point of the call. ## A note on other architectures This is x86-64-specific. An AArch64 port ([arch.md](arch.md)) has its own calling convention (arguments in X0–X7, and so on) — a different ABI entirely. `kernel_abi` would be set per-architecture, but the *principle* is the same: the loader/entry boundary and the kernel must agree on how arguments are passed.