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:
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