A shared binary contract

Caller and callee must agree on argument and result locations. They must also know which registers a call may overwrite and which the callee must preserve, so later instructions do not use clobbered values.

These rules are distinct from the function’s intended source-level behavior. Apparently matching declarations do not ensure a correct call when the compiled sides disagree about binary passing rules.

References: [1] [2]

Different platform arrangements

Arm’s AArch64 procedure-call standard uses general-purpose argument registers and distinguishes scratch from preserved registers, with additional rules for floating-point, vector and aggregate values.

Microsoft’s default x64 convention assigns its first four argument positions to specified integer or floating-point registers and requires caller-reserved stack space. Later parameters use the stack; this is not a universal rule for all 64-bit machines.

Function form adds rules even on one platform. Microsoft x64 requires caller-provided shadow space for four register parameters even when fewer are passed; for vararg or unprototyped calls, floating-point values in the first four argument positions must also be duplicated in the corresponding general-purpose registers. Reusing a fixed-signature floating-point call sequence for every variadic call can therefore violate the binary contract.

References: [1] [2] [4]

Passing depends on data layout

Aggregate alignment and field sizes affect how a value is passed, including when an indirect address is required. Microsoft’s x64 software conventions therefore specify scalar sizes, structure layout and alignment alongside calling rules.

A function-call convention must also be distinguished from a system-call interface for entering the kernel. Cross-language interfaces and assembly code require agreement on platform, function kind and data representation, not names alone.

References: [1] [3]