Skip to content

device, tsasm/mips: add ChaCha20-Poly1305 assembly for GOARCH=mips, mipsle, and mips64#64

Open
bradfitz wants to merge 1 commit into
tailscalefrom
bradfitz/mips_asm_squash
Open

device, tsasm/mips: add ChaCha20-Poly1305 assembly for GOARCH=mips, mipsle, and mips64#64
bradfitz wants to merge 1 commit into
tailscalefrom
bradfitz/mips_asm_squash

Conversation

@bradfitz

Copy link
Copy Markdown
Member

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

…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
bradfitz force-pushed the bradfitz/mips_asm_squash branch from 172200a to 700cacd Compare May 27, 2026 04:37
@bradfitz
bradfitz marked this pull request as ready for review May 27, 2026 04:37
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