How libdav1d Ensures Cross-Platform ABI Stability
This article explores how libdav1d, the open-source AV1
video decoder, maintains consistent Application Binary Interface (ABI)
stability across different operating systems and hardware architectures.
We will examine its use of opaque data structures, controlled symbol
exports, standardized assembly calling conventions, and automated
testing pipelines that prevent breaking changes in downstream
applications.
Opaque Data Structures and API Design
The primary mechanism libdav1d uses to guarantee ABI
stability is the strict use of opaque data structures. In the public
headers, critical structures—such as decoder contexts and picture
buffers—are declared as incomplete types (pointers to structs).
Because the internal layout, field sizes, and alignments of these
structures are hidden from the public API, client applications cannot
access them directly. Instead, clients must interact with the library
using designated getter and setter functions. This separation allows
developers to modify, add, or reorder internal struct fields in newer
versions of libdav1d without altering the binary layout
expected by compiled applications.
Strict Symbol Export Control
To prevent name clashes and accidental dependency binding,
libdav1d strictly controls which symbols are visible to the
operating system’s linker.
The library utilizes compiler attributes (such as
__attribute__((visibility("default"))) on GCC/Clang and
__declspec(dllexport) on MSVC) mapped to a unified
DAV1D_API macro. Only functions explicitly decorated with
this macro are exported in the final shared library or DLL. All internal
helper functions, especially those used for platform-specific hardware
acceleration, are kept private, preventing external applications from
linking against volatile internal APIs.
Fixed-Width Integer Types and Standardized Layouts
Cross-platform compatibility requires consistent data sizes
regardless of the compiler, operating system, or CPU architecture.
libdav1d achieves this by strictly avoiding native C types
whose sizes vary across platforms (such as long, which is
32-bit on 64-bit Windows but 64-bit on 64-bit Linux).
Instead, the library relies entirely on standard fixed-width types
defined in <stdint.h> (e.g., int32_t,
uint64_t, intptr_t). This ensures that
function arguments and structure alignments remain identical whether
compiled on Windows, macOS, Linux, Android, or iOS.
Unified Assembly Calling Conventions
Because a significant portion of libdav1d is written in
assembly for x86 (using NASM) and ARM (using GAS) to maximize decoding
speed, managing CPU-level calling conventions is critical. Different
operating systems utilize different registers and stack layouts for
function calls (for example, the Windows x64 calling convention versus
the System V AMD64 ABI used by Linux and macOS).
libdav1d resolves this by utilizing a robust assembly
preprocessing layer. This layer abstracts the underlying calling
convention, automatically mapping function arguments to the correct CPU
registers and handling stack allocation based on the target platform at
compile time. This ensures that the hand-optimized assembly code
seamlessly adheres to the host platform’s ABI.
Automated ABI Verification in CI Pipelines
To prevent human error from introducing ABI regressions, the
libdav1d development workflow integrates automated static
analysis tools into its continuous integration (CI) pipeline.
Tools like abi-compliance-checker compare the public
header files and compiled shared libraries of new pull requests against
established stable releases. If a change inadvertently alters a function
signature, changes an exported symbol, or modifies an exposed data
structure, the CI pipeline flags the build as failed, ensuring that ABI
compatibility is verified before any code is merged into the main
branch.