device, tsasm/mips: add ChaCha20-Poly1305 assembly for GOARCH=mips, mipsle, and mips64#64
Open
bradfitz wants to merge 1 commit into
Open
device, tsasm/mips: add ChaCha20-Poly1305 assembly for GOARCH=mips, mipsle, and mips64#64bradfitz wants to merge 1 commit into
bradfitz wants to merge 1 commit into
Conversation
…ipsle, and mips64
golang.org/x/crypto ships no ChaCha20 or Poly1305 assembly for any
of the MIPS GOARCHes, so the WireGuard data-path AEAD on MIPS
boards (EdgeRouters, CI20-class dev boards, MikroTik / OpenWrt
routers, ...) falls back to a slow pure-Go path -- ~3-9 MB/s
depending on chip. This adds hand-written scalar mips32r2 /
mips64r2 inner loops for both halves and wires them into the
WireGuard data path via device/aead_mips{32,64}.go.
ChaCha20 (one .s per GOARCH endian-bucket; the two 32-bit
variants share a Go wrapper):
tsasm/mips64/chacha20/chacha20_mips64.s (mips64 BE)
tsasm/mips32/chacha20/chacha20_mips_be.s (mips BE)
tsasm/mips32/chacha20/chacha20_mips_le.s (mipsle LE)
All 16 state words live in registers across 20 rounds. Rotates
use MIPS r2 ROTR with the four left-rotate counts folded to
right-rotates (16, 20, 24, 25). On BE, input and output words
are byte-swapped with a WSBH+ROTR16 pair so the keystream comes
out in little-endian order; the LE variant drops those pairs.
Poly1305 follows openssl/Linux poly1305-mips.pl (Andy Polyakov,
dual-licensed BSD-3-Clause / OpenSSL):
tsasm/mips64/poly1305/poly1305_mips64.s (mips64 BE; 3x64 saturated)
tsasm/mips32/poly1305/poly1305_mips_be.s (mips BE; 5x32 saturated)
tsasm/mips32/poly1305/poly1305_mips_le.s (mipsle LE; 5x32 saturated)
Saturated limbs; precomputed s_i = r_i + (r_i >> 2) absorbs the
2^130 ≡ 5 reduction into the inner multiplications. Each partial
product is its own MULTU/DMULTU + MFLO + MFHI + manual ADDU/SGTU
carry chain -- no MADDU into the multiplier accumulator, which
sidesteps an HI/LO accumulator-chain hazard that turns up
intermittently on real Cavium Octeon II and Ingenic JZ4780
silicon. Upstream's modulo-scheduled reduction runs at block
start, so finalize() replays it once more before subtracting p.
The asm avoids R26 and R27 (k0/k1, kernel-clobbered on MIPS Linux
-- the kernel can wipe them at any interrupt or exception). The
usable Plan 9 MIPS register set is R2..R22 + R24..R25 only.
Setting TS_WG_ASM=0 in the environment forces the pure-Go
x/crypto implementation, as an escape hatch for hardware
regressions or asm bugs (matches the arm32 wiring).
Performance, 1420-byte payload (typical WireGuard packet),
median of three runs, go1.26.3:
Hardware pure-Go asm
EdgeRouter Pro 8 (mips64 / Cavium Octeon II Pass 1)
ChaCha20 alone 10.2 52.3 MB/s (5.1x)
Poly1305 alone 100.0 292.8 MB/s (2.9x)
AEAD Seal 9.0 40.2 MB/s (4.5x)
CI20 dev board (mipsle / Ingenic JZ4780)
ChaCha20 alone 11.3 46.0 MB/s (4.1x)
Poly1305 alone 23.1 104.0 MB/s (4.5x)
AEAD Seal 7.2 29.7 MB/s (4.1x)
TP-Link TL-WDR4300 v1 (mips / Atheros AR9344 / 74Kc; softfloat)
ChaCha20 alone 4.7 16.3 MB/s (3.5x)
Poly1305 alone 11.3 34.5 MB/s (3.0x)
AEAD Seal 3.1 9.7 MB/s (3.1x)
Tested / coverage:
GOARCH ChaCha20 Poly1305 Tested on
mips64 yes yes EdgeRouter Pro 8 (Octeon II Pass 1, kernel 4.9)
mipsle yes yes CI20 dev board (Ingenic JZ4780, kernel 3.18.3)
mips yes yes TP-Link TL-WDR4300 v1 (AR9344 / 74Kc, kernel 6.12)
mips64le no no falls back to x/crypto -- no LE 64-bit MIPS hardware
Build note for FPU-less MIPS SoCs (most OpenWrt-targeted Atheros
/ Qualcomm WiSoCs, including the WDR4300's AR9344): default
GOMIPS=hardfloat binaries SIGILL on first FP-using runtime
instruction. Use GOMIPS=softfloat. The asm itself is pure
integer; this is only a Go runtime/binary concern.
Toolchain note: Go 1.26.0 and 1.26.1 panic at first goroutine
wakeup on Linux <5.1 MIPS because of a wrong _ENOSYS constant in
the new futex_time64 -> futex_time32 fallback (MIPS ENOSYS is 89,
the patch used the asm-generic 38). Fixed in 1.26.2 / 1.26.3.
Updates tailscale/tailscale#7053
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
bradfitz
force-pushed
the
bradfitz/mips_asm_squash
branch
from
May 27, 2026 04:37
172200a to
700cacd
Compare
bradfitz
marked this pull request as ready for review
May 27, 2026 04:37
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
golang.org/x/crypto ships no ChaCha20 or Poly1305 assembly for any
of the MIPS GOARCHes, so the WireGuard data-path AEAD on MIPS
boards (EdgeRouters, CI20-class dev boards, MikroTik / OpenWrt
routers, ...) falls back to a slow pure-Go path -- ~3-9 MB/s
depending on chip. This adds hand-written scalar mips32r2 /
mips64r2 inner loops for both halves and wires them into the
WireGuard data path via device/aead_mips{32,64}.go.
ChaCha20 (one .s per GOARCH endian-bucket; the two 32-bit
variants share a Go wrapper):
All 16 state words live in registers across 20 rounds. Rotates
use MIPS r2 ROTR with the four left-rotate counts folded to
right-rotates (16, 20, 24, 25). On BE, input and output words
are byte-swapped with a WSBH+ROTR16 pair so the keystream comes
out in little-endian order; the LE variant drops those pairs.
Poly1305 follows openssl/Linux poly1305-mips.pl (Andy Polyakov,
dual-licensed BSD-3-Clause / OpenSSL):
Saturated limbs; precomputed s_i = r_i + (r_i >> 2) absorbs the
2^130 ≡ 5 reduction into the inner multiplications. Each partial
product is its own MULTU/DMULTU + MFLO + MFHI + manual ADDU/SGTU
carry chain -- no MADDU into the multiplier accumulator, which
sidesteps an HI/LO accumulator-chain hazard that turns up
intermittently on real Cavium Octeon II and Ingenic JZ4780
silicon. Upstream's modulo-scheduled reduction runs at block
start, so finalize() replays it once more before subtracting p.
The asm avoids R26 and R27 (k0/k1, kernel-clobbered on MIPS Linux
-- the kernel can wipe them at any interrupt or exception). The
usable Plan 9 MIPS register set is R2..R22 + R24..R25 only.
Setting TS_WG_ASM=0 in the environment forces the pure-Go
x/crypto implementation, as an escape hatch for hardware
regressions or asm bugs (matches the arm32 wiring).
Performance, 1420-byte payload (typical WireGuard packet),
median of three runs, go1.26.3:
Tested / coverage:
Build note for FPU-less MIPS SoCs (most OpenWrt-targeted Atheros
/ Qualcomm WiSoCs, including the WDR4300's AR9344): default
GOMIPS=hardfloat binaries SIGILL on first FP-using runtime
instruction. Use GOMIPS=softfloat. The asm itself is pure
integer; this is only a Go runtime/binary concern.
Toolchain note: Go 1.26.0 and 1.26.1 panic at first goroutine
wakeup on Linux <5.1 MIPS because of a wrong _ENOSYS constant in
the new futex_time64 -> futex_time32 fallback (MIPS ENOSYS is 89,
the patch used the asm-generic 38). Fixed in 1.26.2 / 1.26.3.
Updates tailscale/tailscale#7053