base64

Fast Haskell base64 encoding/decoding (docs.ppad.tech/base64).
git clone git://git.ppad.tech/base64.git
Log | Files | Refs | README | LICENSE

commit e01f8d10d9bafcab783a6a3bce9ae1b31d6223b6
parent 72fa80fdb1438d0d10e0f536558afd2ddbd593c8
Author: Jared Tobin <jared@jtobin.io>
Date:   Sat, 16 May 2026 13:00:26 -0230

lib: dispatch encode/decode to ARM NEON when available

Wire 'Data.ByteString.Base64.encode' and 'decode' to the NEON
implementation added in the previous commit, with the pure Haskell
scalar loop kept as a fallback.

Mirrors the dispatch pattern in ppad-base16 / ppad-sha256:

    encode bs
      | Arm.base64_arm_available = Arm.encode bs
      | otherwise                = encode_scalar bs

No behavioural change beyond dispatch: on aarch64 the NEON path is
taken, on every other arch the C stubs return availability = 0 and
the scalar bodies run.

Existing tasty suite (5000 QuickCheck cases × 3 properties + the
RFC 4648 §10 unit vectors) passes through the dispatched path,
including under 'cabal test -fllvm -fsanitize' which exercises the
C kernel under AddressSanitizer + UndefinedBehaviorSanitizer.

Performance on 1 KiB inputs, M4 MacBook Air, GHC 9.10.3 + LLVM 19,
-fllvm:

  encode time:   270 ns -> 102 ns   (~2.6×)
  decode time:   273 ns -> 160 ns   (~1.7×)

Diffstat:
Mlib/Data/ByteString/Base64.hs | 17+++++++++++++----
1 file changed, 13 insertions(+), 4 deletions(-)

diff --git a/lib/Data/ByteString/Base64.hs b/lib/Data/ByteString/Base64.hs @@ -18,6 +18,7 @@ module Data.ByteString.Base64 ( import qualified Data.Bits as B import Data.Bits ((.&.), (.|.)) import qualified Data.ByteString as BS +import qualified Data.ByteString.Base64.Arm as Arm import qualified Data.ByteString.Internal as BI import Data.Word (Word8) import Foreign.ForeignPtr (withForeignPtr) @@ -75,23 +76,31 @@ dec_tab = -- | Encode a base256 'ByteString' as base64. -- +-- Uses ARM NEON extensions when available, otherwise a pure +-- Haskell scalar loop. +-- -- >>> encode "hello world" -- "aGVsbG8gd29ybGQ=" encode :: BS.ByteString -> BS.ByteString -encode = encode_scalar +encode bs + | Arm.base64_arm_available = Arm.encode bs + | otherwise = encode_scalar bs {-# INLINABLE encode #-} -- | Decode a base64 'ByteString' to base256. -- --- Invalid inputs (including incorrectly-padded or non-canonical --- inputs) will produce 'Nothing'. +-- Uses ARM NEON extensions when available, otherwise a pure +-- Haskell scalar loop. Invalid inputs (including incorrectly- +-- padded or non-canonical inputs) will produce 'Nothing'. -- -- >>> decode "aGVsbG8gd29ybGQ=" -- Just "hello world" -- >>> decode "aGVsbG8gd29ybGQ" -- missing padding -- Nothing decode :: BS.ByteString -> Maybe BS.ByteString -decode = decode_scalar +decode bs + | Arm.base64_arm_available = Arm.decode bs + | otherwise = decode_scalar bs {-# INLINABLE decode #-} encode_scalar :: BS.ByteString -> BS.ByteString