Skip to content

celt: complex IMDCT/MDCT archsimd kernels (needs the Reverse64 op) - #488

Merged
thesyncim merged 5 commits into
simd-pkg-kernelsfrom
simd-complex-kernels
Jun 16, 2026
Merged

celt: complex IMDCT/MDCT archsimd kernels (needs the Reverse64 op)#488
thesyncim merged 5 commits into
simd-pkg-kernelsfrom
simd-complex-kernels

Conversation

@thesyncim

Copy link
Copy Markdown
Owner

Summary

Stacked on #487. Ports the 7 complex CELT float kernels — the IMDCT/MDCT
rotation, twiddle and fold kernels — from hand Plan9 asm to Go simd/archsimd,
gated goexperiment.simd && !purego:

imdct_pre_kiss, imdct_tdac, imdct_post_kiss, mdct_post_twiddle,
mdct_mid_fold, mdct_fold1, mdct_fold3.

These were the last CELT float kernels blocked on a missing op: a full 4-lane
lane-reverse. They use ConcatEven/ConcatOdd to deinterleave the kissCpx
re/im streams, a reverse4 (built from the new Reverse64/NEON VREV64) for the
descending streams, fused MulAdd + single-round Mul matching each kernel's
exact op shape, and InterleaveLo/Hi for the complex store. The bit-reversed
dst[bitrev[i]] scatter (fold kernels) is done via temps + a scalar loop, exactly
as the asm scatters it.

Honest state — needs the Reverse64 op (not upstream yet)

reverse4 needs Float32x4.Reverse64, which is not in upstream Go yet
submitted as golang/go#80032. Until it lands in gotip:

  • the default build is unchanged (the hand asm still runs; these .s/.go
    files retag !goexperiment.simd);
  • the goexperiment.simd build of these kernels needs the patched toolchain
    (thesyncim/go), so the non-blocking test-simd-experiment CI job (upstream
    gotip) will be red on the arm64 cross-build until #80032 merges
    — this is
    expected and the PR assumes that merge.

Locally on the patched gotip the full set (vanilla + widen + complex) builds and
every kernel's parity test passes on both the archsimd and asm paths.

What we win

archsimd matches the hand asm at parity on the complex family — the
deinterleave + reverse + scatter amortize to parity, not the regression you'd
expect from emulated VLD2:

kernel archsimd vs asm
imdct_post_kiss parity (37.6 vs 37.6 ns)
imdct_tdac beats asm (−15%)
imdct_pre_kiss +26% (the VLD2-deinterleave gap)
mdct_post_twiddle / mdct_mid_fold / mdct_fold1/3 bit-exact, codec-level full-suite green

With these merged, every CELT float kernel is portable to archsimd; the only
remaining hand asm is the genuinely-blocked classes (FFT butterfly gather,
indexed-lane FMLA, serial IIR, integer).

Test plan

…4 patch)

Port the IMDCT pre-rotation and TDAC windowing kernels to archsimd, gated
goexperiment.simd && !purego. Both rely on a full 4-lane reverse (reverse4) built on
Float32x4.Reverse64 — the arm64 VREV64 op added in github.com/thesyncim/go branch
arm64-simd-reverse64 — for their descending spectrum/window accesses; they will not
build on a vanilla gotip. Bit-exact vs the scalar references
(TestIMDCTPreRotateFMA32KissMatchesScalar, TestIMDCTTDACWindowFMA32MatchesScalar).

imdct_tdac (real-valued, reverse only) beats the hand asm on M4 Max (-15%); imdct_pre
(complex re/im deinterleave) is +26%, the cost of archsimd lacking a VLD2-style
deinterleaving load. The asm/purego paths stay the default for non-experiment builds.
Port the IMDCT post-rotation (imdctPostRotateF32FromKiss) from hand asm to
Go simd/archsimd, gated goexperiment.simd && !purego (needs the Reverse64 op
from the patched toolchain). Forward four come from a ConcatEven/Odd
deinterleave of the kissCpx scratch; the backward four walk down and reverse4
so descending lanes line up; products are single-round Muls and accumulates
fused MulAdds, matching mdctMulAddMix/mdctMulSubMix bit-for-bit. The asm
.go/.s retag !goexperiment.simd. Benchmarks at asm parity.
Port the forward-MDCT post-twiddle (mdctPostTwiddleNeon) from hand asm to Go
simd/archsimd, gated goexperiment.simd && !purego. Each block pairs a forward
run and its mirror j=n4-1-i; ConcatEven/Odd deinterleaves the kissCpx, the two
ends tile coeffs contiguously via reverse4 + InterleaveLo/Hi. Products are
single-round Muls and the combines plain Sub/Add (no fusion), matching mdctMul
and the scalar loop bit-for-bit. The asm .go/.s retag !goexperiment.simd.
Port the forward-MDCT middle fold (mdctMidFoldStoreNeon) from hand asm to Go
simd/archsimd, gated goexperiment.simd && !purego. The (re,im) compute is
vectorized four lanes at a time (ConcatEven deinterleave, descending re via
reverse4, fused MulAdd, scaling Mul) matching mdctStoreDirectStageFMALike
bit-for-bit; the bit-reversed dst[bitrev[i]] scatter stays scalar, exactly as
the asm does. The asm .go/.s retag !goexperiment.simd.
Port the two windowed forward-MDCT fold kernels (mdctFold1StoreNeon,
mdctFold3StoreNeon) from hand asm to Go simd/archsimd, gated
goexperiment.simd && !purego. Each deinterleaves six sample/window streams
(ConcatEven ascending, reverse4 descending) and combines them with fused
MulAdds + single-round Muls matching mdctMulAddMixEncode/mdctMulSubMixEncode/
mdctMulSubMixAlt; the shared twiddle/scale/bit-reversed scatter tail
(mdctFoldStore) mirrors mid_fold. The asm .go/.s retag !goexperiment.simd.
@thesyncim

Copy link
Copy Markdown
Owner Author

Consolidated into #487 — single PR for the whole archsimd kernel migration (vanilla + silk widen + complex, the complex path gated behind gopus_reverse64).

@thesyncim
thesyncim merged commit f43ba4c into simd-pkg-kernels Jun 16, 2026
26 of 28 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant