$ a64s -O2 -march=armv8-a 04_dispatch.c | awk '/^resolve_dot:/,/\.Lfunc_end/'
what it printed on the machine that wrote this lab
resolve_dot:
adrp x8, dot_sve ; add x8, x8, :lo12:dot_sve
tst x0, #0x400000 // HWCAP_SVE is bit 22
adrp x9, dot_neon ; add x9, x9, :lo12:dot_neon
csel x0, x9, x8, eq // x0 = (hwcap & SVE) ? dot_sve : dot_neon
ret
$ llvm-readelf -s bin/04_dispatch | grep -E ' dot$| resolve_dot| dot_sve| dot_neon'
what it printed on the machine that wrote this lab
18: ... FUNC LOCAL DEFAULT 14 dot_sve
19: ... FUNC LOCAL DEFAULT 14 dot_neon
21: ... FUNC LOCAL DEFAULT 14 resolve_dot
40: ... IFUNC GLOBAL DEFAULT 14 dot
`dot` has symbol type IFUNC: the loader will call resolve_dot (whose
address is where a function would be) and patch the GOT/PLT slot with
the result. After that, calling dot() costs the same as any call
through the PLT. This is exactly how glibc picks memcpy/strlen variants.
ifunc VS FUNCTION POINTER:
ifunc: no visible dispatch code at call sites; resolved once; requires
glibc-style dynamic linking (static-PIE and musl need care), runs before
main with limited environment (no malloc, no TLS), and is invisible to
tests unless you also export the variants (as this lab does: dot_neon
and dot_sve are callable directly).
function pointer: explicit, testable (call every variant in your test
suite regardless of the host), works everywhere, and lets a config flag
or benchmark override the choice. One indirect call per use. Most
performance libraries (BLAS, codecs) use this or a per-object-file
variant table for the same reasons.
VALIDATING A DISPATCH DECISION (the part that matters in real work):
1. Correctness: every variant against a scalar reference, on inputs
whose size is not a multiple of any vector length, under qemu at
several VLs (lab 02) and on the hardware you have. This lab prints
the max abs difference; a real test uses a tolerance and many inputs.
2. Benefit: on the target hardware, the chosen variant must actually be
faster on the sizes the workload uses (small-n overhead, alignment,
the cost of svwhilelt per iteration). "Has SVE" is not "SVE is faster".
3. Stability: the choice must not flip between runs (pinned feature
detection), and must degrade safely (an unknown core gets the
baseline). Log the decision once at startup.
That is the checklist for "should this API be redirected to the SVE /
SME kernel": evidence at each step, not the presence of a feature bit.