ADR-0198: Rename volk's vk* symbols to vmaf_priv_vk* for static-archive builds¶
- Status: Accepted
- Date: 2026-04-27
- Deciders: Lusoris, Claude (Anthropic)
- Tags: vulkan, build, fork-local, abi
- Supersedes: nothing — extends ADR-0185 to the case
--exclude-libs,ALLcannot reach.
Context¶
ADR-0185 hid volk's vk* / volk* symbols from libvmaf.so's public ABI by passing -Wl,--exclude-libs,ALL to the shared-library link step. That flag works because GNU ld applies it during the gcc -shared link that produces libvmaf.so.3.0.0.
Static archives (.a) are not produced by a link step — meson runs ar rcs libvmaf.a *.o and concatenates the per-target object files. --exclude-libs has nothing to bind to. Result: libvmaf.a keeps every vk* PFN dispatcher volk defines as a STB_GLOBAL symbol (700+ entries from the bundled volk-1.4.341 loader).
The collision shows up in fully-static link environments that consume both libvmaf and the official Vulkan loader:
$ gcc -static main.c libvmaf.a libvulkan.a -ldl
ld: volk.c.o (symbol from plugin):
multiple definition of `vkGetInstanceProcAddr';
libvulkan.a(loader.c.o): first defined here
[...700+ more, one per Vulkan API entry point...]
This is exactly the failure lawrence reported on 2026-04-27 on his BtbN-style cross-toolchain glibc-2.28 static FFmpeg build when adding --enable-libvmaf with libvmaf compiled as default_library=static.
Decision¶
Rename volk's vk* symbols to vmaf_priv_vk* at the C preprocessor level, via a force-included header:
- The volk packagefile generates
volk_priv_remap.hfrom the bundledvolk.h(parses everyextern PFN_vkXxx vkXxx;declaration and emits#define vkXxx vmaf_priv_vkXxx). Generation is keyed onvolk.hso the remap tracks the wrap version automatically. - The packagefile compiles
volk.cwith-include <build>/volk_priv_remap.hso volk's PFN definitions come out asvmaf_priv_vkXxx. volk_depexports the same-includeflag in itscompile_args, propagating it to every libvmaf TU that callsvolk.h(via the meson dependency graph).
The PFN_vkXxx typedef name is a different identifier from vkXxx, so the C preprocessor leaves typedefs alone. Only the variable / call-site names change. volk's own dlsym(..., "vkGetInstanceProcAddr") lookups use string literals, not identifiers, so the runtime loader path is unaffected.
Net effect:
| symbol class | shared libvmaf.so.3.0.0 | static libvmaf.a |
|---|---|---|
vk* exported (before this ADR) | 0 (ADR-0185) | 700+ |
vk* exported (after this ADR) | 0 | 0 |
vmaf_priv_vk* internal-bound | 627 | 719 |
volk* (kept, unrenamed) | 0 / hidden | ~30 |
Alternatives considered¶
| Option | Pros | Cons | Why not chosen |
|---|---|---|---|
Force-include volk_priv_remap.h (chosen) | Fixes both shared and static cases identically; pure CPP — no objcopy / partial-link / linker-script gymnastics; tracks volk version automatically | Force-include is non-obvious to a casual reader; missing a vk* declaration in volk.h would silently leave it un-renamed | Cleanest solution; the volk header pattern is uniform enough that the regex catches every entry point (verified: 784 declarations → 784 remaps for volk-1.4.341) |
Partial-link + objcopy --localize-symbol for static builds only | Surgical to the static case | Requires bypassing meson's library() for static builds with a custom_target chain (ld -r → objcopy → ar); duplicates the link step; brittle in cross-compile lanes | Significant build-system restructuring; outcome equivalent to the chosen approach, with more failure modes |
Pass --allow-multiple-definition to consumer | Zero libvmaf changes | Punts the problem to every downstream user; "disabling the symptom is not a bugfix" — explicit user direction | Rejected on user direction 2026-04-27 |
Drop volk; use dlsym per-symbol in libvmaf | Removes the bundled loader entirely | Major refactor of core/src/vulkan/common.c's init path; loses volk's per-instance / per-device PFN tables | Out of scope for a build-system fix; revisit only if volk causes other issues |
Compile volk with -fvisibility=hidden | Standard ELF approach for shared libs | Visibility attributes have no effect on STB_GLOBAL symbols inside .a archives — they only matter at the shared-library link step that ADR-0185 already covers | Doesn't address the static case |
Consequences¶
- Positive: BtbN-style fully-static FFmpeg builds that link
libvmaf.anext to the Khronoslibvulkan.asucceed at link time. lawrence's repro (cross-toolchainglibc-2.28build) goes from ~700 multi-def errors to a clean link. - Positive: identical fix for shared and static — no conditional code paths in the volk packagefile, no per-build-mode meson branches.
- Positive: tracks the upstream wrap version. Bumping volk to a future SDK release re-runs the generator at configure time and picks up new entry points automatically.
- Negative: a build-system reader who greps for
vkCreateInstancein libvmaf's compiled object files won't find it under that name; they have to know the remap is active. A short note indocs/backends/vulkan.mdand the packagefile comment block point future maintainers at this ADR. - Negative: if a future volk drop introduces a declaration pattern not matching the regex (
^extern\s+PFN_vk\w+\s+(vk\w+)\s*;), those symbols stay un-renamed and would leak. Mitigation: the generator emits the rename count to stdout, and a CI check on the Linux Vulkan static lane greps libvmaf.a for any leftoverT vk[A-Z]symbols (follow-up). - Neutral / follow-ups:
- Add a static-archive lane assertion:
nm libvmaf.a | grep -E '^[0-9a-f]* (T|D|B) vk[A-Z]'must return zero matches when Vulkan is enabled. Lands in the existing Vulkan build job intests-and-quality-gates.yml. - Re-run the BtbN-style link simulation in
make test-fastso a regression in the rename mechanism fails locally.
References¶
- Source: lawrence's chat report 2026-04-27 — full cross-toolchain build log captured at
.workingdir2/stderr.loglines 1282-2000+ shows the multi-def cascade. - Pre-fix verification:
nm libvmaf.a | grep -cE '^[0-9a-f]* (T|D|B|R) vk[A-Z]'ondefault_library=static -Denable_vulkan=enabledreturned ~700 before this commit; after, returns0. - Post-fix verification:
$ gcc -static main_stub.c libvmaf.a libvulkan-stub.a \
-lstdc++ -lm -lpthread -ldl -o btbn_sim
$ echo $?
0
(See PR description for the full repro script.) - Vulkan smoke test (test_vulkan_smoke) still passes 10 / 10 on the renamed build — runtime-loaded entry points dispatch through vmaf_priv_vk* PFNs identically. - Parent: ADR-0185 — shared-library symbol hiding via -Wl,--exclude-libs,ALL. - User direction: 2026-04-27 — "fix it. disabling is not a bugfix" (paraphrased from chat).