The .cargo/config.toml used [build] rustflags which applies globally
to every target. On aarch64-apple-darwin this injects
target-cpu=x86-64-v3 and +ssse3 as "ignored features", but the ring
crate's static CPU feature assertions detect the perturbed feature
set and fail to compile with:
error[E0080]: evaluation panicked:
assertion failed: CAPS_STATIC == MIN_STATIC_FEATURES
at ring-0.17.14/src/cpu/arm/darwin.rs:51
Move the flags under [target.'cfg(target_arch = "x86_64")'] so only
x86_64 builds see them. ARM/Apple Silicon targets get their own AES
hardware path automatically via the aes crate's armv8 dispatch — no
explicit flags needed.
cc
Previous commit tried to enable AES-NI via target-cpu=x86-64-v3 but
that microarchitecture level does NOT include AES-NI — it's a separate
ISA extension added on top. The aes crate kept falling back to software
decrypt (~10 MB/s), so the first live test still showed a 6s gap on
offline-cache gapless for a 58 MB track (~10 MB/s).
Fix: add target-feature=+aes,+ssse3 to rustflags. The aes crate's
runtime dispatch via cpufeatures only works if the AES-NI code path
was actually compiled in, which requires the build-time feature flag.
Expected: 58 MB decrypt drops from ~6s to ~150ms, fits inside the 5s
gapless pre-emptive window with headroom.
cc
CMAF decrypt was the bottleneck for offline-cache gapless. Live
testing showed a 208 MB HiRes bundle took ~22 seconds to decrypt end-
to-end, which made gapless for anything longer than a 3-minute track
impossible (the gapless pre-emptive window is ~5s before track end,
so a 22s decrypt can't land in time regardless of how early we start).
Two changes, complementary:
1. `.cargo/config.toml` with `target-cpu=x86-64-v3` (Haswell baseline).
The `aes` crate ships both a software implementation and an AES-NI
hardware path, runtime-dispatched via `cpufeatures` — but the
AES-NI path is only compiled in when the appropriate target-feature
is enabled at build time. Default dev builds compile ONLY the
software path, which hits ~10 MB/s on a modern CPU. Haswell+ (2013
and up, every realistic QBZ user) supports AES-NI and clears
400 MB/s+. For prebuilt distribution binaries we'll want runtime-
dispatch via subfunction build or dynamic linking, but for self-
built and release-source-from-tag scenarios this is the right
default.
2. `decrypt_segments_into` no longer allocates a Vec per frame. Was:
frame = seg_data[..].to_vec(); // alloc
decrypt_frame(&mut frame); // in-place
output.extend_from_slice(&frame); // copy back
Now:
let start = output.len();
output.extend_from_slice(&seg_data[..]); // single copy
decrypt_frame(&mut output[start..]); // in-place
For a HiRes FLAC with ~50K frames this saves 50K small allocs +
50K double-copies. Output Vec is already pre-sized via
with_capacity(total), so no reallocation growth during extend.
Combined expected impact: 22s decrypt on a 208 MB track → under 500ms,
which fits inside the 5s gapless window with headroom to spare.
cc