Cheng Shao pushed to branch wip/fast-binary at Glasgow Haskell Compiler / GHC Commits: 0deceee0 by Cheng Shao at 2025-10-12T21:13:45+02:00 compiler: implement fast get/put for Word16/Word32/Word64 Previously, `GHC.Utils.Binary` contains `get`/`put` functions for `Word16`/`Word32`/`Word64` which always loads and stores them as big-endian words at a potentially unaligned address. The previous implementation is based on loads/stores of individual bytes and concatenating bytes with bitwise operations, which currently cannot be fused to a single load/store operation by GHC. This patch implements fast `get`/`put` functions for `Word16`/`Word32`/`Word64` based on a single memory load/store, with an additional `byteSwap` operation on little-endian hosts. It is based on unaligned load/store primops added since GHC 9.10, and we already require booting with at least 9.10, so it's about time to switch to this faster path. - - - - - 1 changed file: - compiler/GHC/Utils/Binary.hs Changes: ===================================== compiler/GHC/Utils/Binary.hs ===================================== @@ -1,6 +1,7 @@ {-# LANGUAGE CPP #-} {-# LANGUAGE GADTs #-} +{-# LANGUAGE MagicHash #-} {-# LANGUAGE UnboxedTuples #-} {-# LANGUAGE DerivingVia #-} {-# LANGUAGE StandaloneDeriving #-} @@ -9,6 +10,8 @@ -- We always optimise this, otherwise performance of a non-optimised -- compiler is severely affected +#include <ghcautoconf.h> + -- -- (c) The University of Glasgow 2002-2006 -- @@ -162,7 +165,6 @@ import qualified Data.Set as Set import Data.Time import Data.List (unfoldr) import System.IO as IO -import System.IO.Unsafe ( unsafeInterleaveIO ) import System.IO.Error ( mkIOError, eofErrorType ) import Type.Reflection ( Typeable, SomeTypeRep(..) ) import qualified Type.Reflection as Refl @@ -170,6 +172,9 @@ import GHC.Real ( Ratio(..) ) import Data.IntMap (IntMap) import qualified Data.IntMap as IntMap import GHC.ForeignPtr ( unsafeWithForeignPtr ) +import GHC.Exts +import GHC.IO +import GHC.Word import Unsafe.Coerce (unsafeCoerce) @@ -653,71 +658,64 @@ getWord8 :: ReadBinHandle -> IO Word8 getWord8 h = getPrim h 1 peek putWord16 :: WriteBinHandle -> Word16 -> IO () -putWord16 h w = putPrim h 2 (\op -> do - pokeElemOff op 0 (fromIntegral (w `shiftR` 8)) - pokeElemOff op 1 (fromIntegral (w .&. 0xFF)) - ) +putWord16 h w = putPrim h 2 $ \(Ptr p#) -> + IO $ \s -> (# writeWord8OffAddrAsWord16# p# 0# x# s, () #) + where +#if defined(WORDS_BIGENDIAN) + !(W16# x#) = w +#else + !(W16# x#) = byteSwap16 w +#endif getWord16 :: ReadBinHandle -> IO Word16 -getWord16 h = getPrim h 2 (\op -> do - w0 <- fromIntegral <$> peekElemOff op 0 - w1 <- fromIntegral <$> peekElemOff op 1 - return $! w0 `shiftL` 8 .|. w1 - ) +getWord16 h = getPrim h 2 $ \(Ptr p#) -> + IO $ \s -> case readWord8OffAddrAsWord16# p# 0# s of + (# s', w16# #) -> +#if defined(WORDS_BIGENDIAN) + (# s', W16# w16# #) +#else + (# s', byteSwap16 $ W16# w16# #) +#endif putWord32 :: WriteBinHandle -> Word32 -> IO () -putWord32 h w = putPrim h 4 (\op -> do - pokeElemOff op 0 (fromIntegral (w `shiftR` 24)) - pokeElemOff op 1 (fromIntegral ((w `shiftR` 16) .&. 0xFF)) - pokeElemOff op 2 (fromIntegral ((w `shiftR` 8) .&. 0xFF)) - pokeElemOff op 3 (fromIntegral (w .&. 0xFF)) - ) +putWord32 h w = putPrim h 4 $ \(Ptr p#) -> + IO $ \s -> (# writeWord8OffAddrAsWord32# p# 0# x# s, () #) + where +#if defined(WORDS_BIGENDIAN) + !(W32# x#) = w +#else + !(W32# x#) = byteSwap32 w +#endif getWord32 :: ReadBinHandle -> IO Word32 -getWord32 h = getPrim h 4 (\op -> do - w0 <- fromIntegral <$> peekElemOff op 0 - w1 <- fromIntegral <$> peekElemOff op 1 - w2 <- fromIntegral <$> peekElemOff op 2 - w3 <- fromIntegral <$> peekElemOff op 3 - - return $! (w0 `shiftL` 24) .|. - (w1 `shiftL` 16) .|. - (w2 `shiftL` 8) .|. - w3 - ) +getWord32 h = getPrim h 4 $ \(Ptr p#) -> + IO $ \s -> case readWord8OffAddrAsWord32# p# 0# s of + (# s', w32# #) -> +#if defined(WORDS_BIGENDIAN) + (# s', W32# w32# #) +#else + (# s', byteSwap32 $ W32# w32# #) +#endif putWord64 :: WriteBinHandle -> Word64 -> IO () -putWord64 h w = putPrim h 8 (\op -> do - pokeElemOff op 0 (fromIntegral (w `shiftR` 56)) - pokeElemOff op 1 (fromIntegral ((w `shiftR` 48) .&. 0xFF)) - pokeElemOff op 2 (fromIntegral ((w `shiftR` 40) .&. 0xFF)) - pokeElemOff op 3 (fromIntegral ((w `shiftR` 32) .&. 0xFF)) - pokeElemOff op 4 (fromIntegral ((w `shiftR` 24) .&. 0xFF)) - pokeElemOff op 5 (fromIntegral ((w `shiftR` 16) .&. 0xFF)) - pokeElemOff op 6 (fromIntegral ((w `shiftR` 8) .&. 0xFF)) - pokeElemOff op 7 (fromIntegral (w .&. 0xFF)) - ) +putWord64 h w = putPrim h 8 $ \(Ptr p#) -> + IO $ \s -> (# writeWord8OffAddrAsWord64# p# 0# x# s, () #) + where +#if defined(WORDS_BIGENDIAN) + !(W64# x#) = w +#else + !(W64# x#) = byteSwap64 w +#endif getWord64 :: ReadBinHandle -> IO Word64 -getWord64 h = getPrim h 8 (\op -> do - w0 <- fromIntegral <$> peekElemOff op 0 - w1 <- fromIntegral <$> peekElemOff op 1 - w2 <- fromIntegral <$> peekElemOff op 2 - w3 <- fromIntegral <$> peekElemOff op 3 - w4 <- fromIntegral <$> peekElemOff op 4 - w5 <- fromIntegral <$> peekElemOff op 5 - w6 <- fromIntegral <$> peekElemOff op 6 - w7 <- fromIntegral <$> peekElemOff op 7 - - return $! (w0 `shiftL` 56) .|. - (w1 `shiftL` 48) .|. - (w2 `shiftL` 40) .|. - (w3 `shiftL` 32) .|. - (w4 `shiftL` 24) .|. - (w5 `shiftL` 16) .|. - (w6 `shiftL` 8) .|. - w7 - ) +getWord64 h = getPrim h 8 $ \(Ptr p#) -> + IO $ \s -> case readWord8OffAddrAsWord64# p# 0# s of + (# s', w64# #) -> +#if defined(WORDS_BIGENDIAN) + (# s', W64# w64# #) +#else + (# s', byteSwap64 $ W64# w64# #) +#endif putByte :: WriteBinHandle -> Word8 -> IO () putByte bh !w = putWord8 bh w View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/0deceee0755bc340cf9fc77c95e69baa... -- View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/0deceee0755bc340cf9fc77c95e69baa... You're receiving this email because of your account on gitlab.haskell.org.