A function signature tells you what a source-level call is allowed to look like. It does not tell you the whole binary protocol the caller and callee must speak.
Consider this ordinary-looking declaration:
struct Big make_big(long long seed);At the source level, there is one visible argument and one return value. At the ABI boundary, that can become a different packet. If struct Big is not returned in registers, the caller may allocate result storage and pass a pointer to that storage into the callee. That pointer is not written in the source signature, but it is part of the call.
The concrete artifact is the hidden result-storage path. For a deliberately large return object, common 64-bit ABIs do not all encode the same source call the same way. On Windows x64, the result pointer is passed in RCX, and the visible seed shifts to RDX. On System V AMD64, the result pointer is passed in RDI, and seed shifts to RSI. On AAPCS64, the indirect result location uses x8, while the visible argument can remain in x0.
That distinction matters for JIT stubs, FFI bridges, hooks, hand-written trampolines, profilers, and any code that constructs or interprets a call below the source language. A bridge that sends only the visible arguments may build a packet the callee cannot decode. On Windows x64 or System V AMD64, the first visible value can land where the callee expects a writable result address. On AAPCS64, the visible arguments may look correct while x8 is stale or unset.
The boundary is narrow. This does not mean every source-level call literally performs a platform ABI ceremony at runtime. The compiler can inline, fold, specialize, or erase calls when both sides are under its control. The ABI matters when independently compiled code must meet.
The safer claim is this: the function signature is a programmer-facing projection. The ABI call is the machine-facing protocol.
Read the full essay on lospino.so: The Function Signature Is a Lie
Original essay published on lospino.so on 2026-05-29.
