diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,9 @@
 # Revision history for byteslice
 
+## 0.2.5.1 -- 2021-02-22
+
+* Compatibility with GHC 9.0.
+
 ## 0.2.5.0 -- 2021-01-22
 
 * Add `Data.Bytes.Chunks.concatByteString`.
diff --git a/byteslice.cabal b/byteslice.cabal
--- a/byteslice.cabal
+++ b/byteslice.cabal
@@ -1,6 +1,6 @@
 cabal-version: 2.2
 name: byteslice
-version: 0.2.5.0
+version: 0.2.5.1
 synopsis: Slicing managed and unmanaged memory
 description:
   This library provides types that allow the user to talk about a slice of
@@ -28,7 +28,6 @@
     Data.Bytes.Mutable
     Data.Bytes.Types
   other-modules:
-    Data.Bytes.Compat
     Data.Bytes.Byte
     Data.Bytes.Pure
     Data.Bytes.IO
diff --git a/src-no-unlifted-newtypes/UnliftedBytes.hs b/src-no-unlifted-newtypes/UnliftedBytes.hs
--- a/src-no-unlifted-newtypes/UnliftedBytes.hs
+++ b/src-no-unlifted-newtypes/UnliftedBytes.hs
@@ -2,13 +2,20 @@
 {-# language TypeFamilies #-}
 {-# language TypeInType #-}
 {-# language UndecidableInstances #-}
+{-# language UnliftedFFITypes #-}
 
 module UnliftedBytes
   ( Bytes#
+  , cstringLength#
   ) where
 
 import GHC.TypeLits
-import GHC.Exts (RuntimeRep(..),TYPE)
+import GHC.Exts (Addr#,Int#,RuntimeRep(..),TYPE)
 
 type family Bytes# :: TYPE ('TupleRep '[ 'UnliftedRep,'IntRep,'IntRep]) where
   Bytes# = TypeError ('Text "Bytes# not available before GHC 8.10")
+
+foreign import ccall unsafe "strlen" c_strlen :: Addr# -> Int#
+
+cstringLength# :: Addr# -> Int#
+cstringLength# = c_strlen
diff --git a/src-unlifted-newtypes/UnliftedBytes.hs b/src-unlifted-newtypes/UnliftedBytes.hs
--- a/src-unlifted-newtypes/UnliftedBytes.hs
+++ b/src-unlifted-newtypes/UnliftedBytes.hs
@@ -7,9 +7,12 @@
 
 module UnliftedBytes
   ( Bytes#(..)
+  , cstringLength#
   ) where
 
 import GHC.Exts (ByteArray#,Int#,RuntimeRep(..),TYPE)
+import GHC.Exts (cstringLength#)
 
 newtype Bytes# :: TYPE ('TupleRep '[ 'UnliftedRep,'IntRep,'IntRep]) where
   Bytes# :: (# ByteArray#, Int#, Int# #) -> Bytes#
+
diff --git a/src/Data/Bytes.hs b/src/Data/Bytes.hs
--- a/src/Data/Bytes.hs
+++ b/src/Data/Bytes.hs
@@ -141,7 +141,6 @@
 import Control.Monad.ST.Run (runByteArrayST)
 import Data.ByteString (ByteString)
 import Data.ByteString.Short.Internal (ShortByteString(SBS))
-import Data.Bytes.Compat (cstringLength#)
 import Data.Bytes.Pure (length,fromByteArray)
 import Data.Bytes.Types (Bytes(Bytes,array,offset))
 import Data.Char (ord)
@@ -152,6 +151,7 @@
 import GHC.Exts (Addr#,Word#,Int#)
 import GHC.IO (unsafeIOToST)
 import GHC.Word (Word8(W8#))
+import UnliftedBytes (cstringLength#)
 
 import qualified Data.ByteString as ByteString
 import qualified Data.ByteString.Unsafe as ByteString
@@ -168,11 +168,13 @@
 
 -- | Is the byte sequence empty?
 null :: Bytes -> Bool
+{-# inline null #-}
 null (Bytes _ _ len) = len == 0
 
 -- | Extract the head and tail of the 'Bytes', returning 'Nothing' if
 -- it is empty.
 uncons :: Bytes -> Maybe (Word8, Bytes)
+{-# inline uncons #-}
 uncons b = case length b of
   0 -> Nothing
   _ -> Just (unsafeIndex b 0, unsafeDrop 1 b)
@@ -180,6 +182,7 @@
 -- | Extract the @init@ and @last@ of the 'Bytes', returning 'Nothing' if
 -- it is empty.
 unsnoc :: Bytes -> Maybe (Bytes, Word8)
+{-# inline unsnoc #-}
 unsnoc b@(Bytes arr off len) = case len of
   0 -> Nothing
   _ -> let !len' = len - 1 in
@@ -188,6 +191,7 @@
 -- | Does the byte sequence begin with the given byte? False if the
 -- byte sequence is empty.
 isBytePrefixOf :: Word8 -> Bytes -> Bool
+{-# inline isBytePrefixOf #-}
 isBytePrefixOf w b = case length b of
   0 -> False
   _ -> unsafeIndex b 0 == w
@@ -234,18 +238,22 @@
 
 -- | Create a byte sequence with one byte.
 singleton :: Word8 -> Bytes
+{-# inline singleton #-}
 singleton !a = Bytes (singletonU a) 0 1
 
 -- | Create a byte sequence with two bytes.
 doubleton :: Word8 -> Word8 -> Bytes
+{-# inline doubleton #-}
 doubleton !a !b = Bytes (doubletonU a b) 0 2
 
 -- | Create a byte sequence with three bytes.
 tripleton :: Word8 -> Word8 -> Word8 -> Bytes
+{-# inline tripleton #-}
 tripleton !a !b !c = Bytes (tripletonU a b c) 0 3
 
 -- | Create an unsliced byte sequence with one byte.
 singletonU :: Word8 -> ByteArray
+{-# inline singletonU #-}
 singletonU !a = runByteArrayST do
   arr <- PM.newByteArray 1
   PM.writeByteArray arr 0 a
@@ -253,6 +261,7 @@
 
 -- | Create an unsliced byte sequence with two bytes.
 doubletonU :: Word8 -> Word8 -> ByteArray
+{-# inline doubletonU #-}
 doubletonU !a !b = runByteArrayST do
   arr <- PM.newByteArray 2
   PM.writeByteArray arr 0 a
@@ -261,6 +270,7 @@
 
 -- | Create an unsliced byte sequence with three bytes.
 tripletonU :: Word8 -> Word8 -> Word8 -> ByteArray
+{-# inline tripletonU #-}
 tripletonU !a !b !c = runByteArrayST do
   arr <- PM.newByteArray 3
   PM.writeByteArray arr 0 a
@@ -336,6 +346,7 @@
 -- | Index into the byte sequence at the given position. This index
 -- must be less than the length.
 unsafeIndex :: Bytes -> Int -> Word8
+{-# inline unsafeIndex #-}
 unsafeIndex (Bytes arr off _) ix = PM.indexByteArray arr (off + ix)
 
 -- | /O(n)/ 'dropWhileEnd' @p@ @b@ returns the prefix remaining after
@@ -465,6 +476,7 @@
 -- | Is the byte sequence, when interpreted as ISO-8859-1-encoded text,
 -- a singleton whose element matches the character?
 equalsLatin1 :: Char -> Bytes -> Bool
+{-# inline equalsLatin1 #-}
 equalsLatin1 !c0 (Bytes arr off len) = case len of
   1 -> c0 == indexCharArray arr off
   _ -> False
@@ -706,6 +718,7 @@
 -- reuses the array backing the sliced 'Bytes' if the slicing metadata
 -- implies that all of the bytes are used. Otherwise, it makes a copy.
 toShortByteString :: Bytes -> ShortByteString
+{-# inline toShortByteString #-}
 toShortByteString !b = case Pure.toByteArray b of
   PM.ByteArray x -> SBS x
 
@@ -713,11 +726,13 @@
 -- the array backing the sliced 'Bytes' even if the original array
 -- could be reused. Prefer 'toShortByteString'.
 toShortByteStringClone :: Bytes -> ShortByteString
+{-# inline toShortByteStringClone #-}
 toShortByteStringClone !b = case Pure.toByteArrayClone b of
   PM.ByteArray x -> SBS x
 
 -- | /O(1)/ Create 'Bytes' from a 'ShortByteString'.
 fromShortByteString :: ShortByteString -> Bytes
+{-# inline fromShortByteString #-}
 fromShortByteString (SBS x) = fromByteArray (ByteArray x)
 
 -- | /O(n)/ Interpreting the bytes an ASCII-encoded characters, convert
diff --git a/src/Data/Bytes/Chunks.hs b/src/Data/Bytes/Chunks.hs
--- a/src/Data/Bytes/Chunks.hs
+++ b/src/Data/Bytes/Chunks.hs
@@ -208,9 +208,11 @@
   reverseOnto (ChunksCons y x) ys
 
 unI :: Int -> Int#
+{-# inline unI #-}
 unI (I# i) = i
 
 unBa :: ByteArray -> ByteArray#
+{-# inline unBa #-}
 unBa (ByteArray x) = x
 
 -- | Read a handle's entire contents strictly into chunks.
@@ -277,15 +279,15 @@
 
 -- | Hash byte sequence with 32-bit variant of FNV-1a.
 fnv1a32 :: Chunks -> Word32
-fnv1a32 = foldl'
+fnv1a32 !b = foldl'
   (\acc w -> (fromIntegral @Word8 @Word32 w `xor` acc) * 0x01000193
-  ) 0x811c9dc5
+  ) 0x811c9dc5 b
 
 -- | Hash byte sequence with 64-bit variant of FNV-1a.
 fnv1a64 :: Chunks -> Word64
-fnv1a64 = foldl'
+fnv1a64 !b = foldl'
   (\acc w -> (fromIntegral @Word8 @Word64 w `xor` acc) * 0x00000100000001B3
-  ) 0xcbf29ce484222325
+  ) 0xcbf29ce484222325 b
 
 -- | Outputs 'Chunks' to the specified 'Handle'. This is implemented
 -- with 'hPutBuf'.
diff --git a/src/Data/Bytes/Compat.hs b/src/Data/Bytes/Compat.hs
deleted file mode 100644
--- a/src/Data/Bytes/Compat.hs
+++ /dev/null
@@ -1,16 +0,0 @@
-{-# language MagicHash #-}
-{-# language UnliftedFFITypes #-}
-
-module Data.Bytes.Compat
-  ( cstringLength#
-  ) where
-
-import GHC.Exts
-
--- GHC 8.12 comes with a known-key implementation of strlen that supports
--- constant folding. Here we define a shim for older GHCs.
-
-foreign import ccall unsafe "strlen" c_strlen :: Addr# -> Int#
-
-cstringLength# :: Addr# -> Int#
-cstringLength# = c_strlen
diff --git a/src/Data/Bytes/Pure.hs b/src/Data/Bytes/Pure.hs
--- a/src/Data/Bytes/Pure.hs
+++ b/src/Data/Bytes/Pure.hs
@@ -75,6 +75,7 @@
 -- reuses the array backing the sliced 'Bytes' if the slicing metadata
 -- implies that all of the bytes are used. Otherwise, it makes a copy.
 toByteArray :: Bytes -> ByteArray
+{-# inline toByteArray #-}
 toByteArray b@(Bytes arr off len)
   | off == 0, PM.sizeofByteArray arr == len = arr
   | otherwise = toByteArrayClone b
@@ -83,6 +84,7 @@
 -- the array backing the sliced 'Bytes' even if the original array
 -- could be reused. Prefer 'toByteArray'.
 toByteArrayClone :: Bytes -> ByteArray
+{-# inline toByteArrayClone #-}
 toByteArrayClone (Bytes arr off len) = runByteArrayST $ do
   m <- PM.newByteArray len
   PM.copyByteArray m 0 arr off len
@@ -102,23 +104,25 @@
 
 -- | Create a slice of 'Bytes' that spans the entire argument array.
 fromByteArray :: ByteArray -> Bytes
+{-# inline fromByteArray #-}
 fromByteArray b = Bytes b 0 (PM.sizeofByteArray b)
 
 -- | The length of a slice of bytes.
 length :: Bytes -> Int
+{-# inline length #-}
 length (Bytes _ _ len) = len
 
 -- | Hash byte sequence with 32-bit variant of FNV-1a.
 fnv1a32 :: Bytes -> Word32
-fnv1a32 = foldl'
+fnv1a32 !b = foldl'
   (\acc w -> (fromIntegral @Word8 @Word32 w `xor` acc) * 0x01000193
-  ) 0x811c9dc5
+  ) 0x811c9dc5 b
 
 -- | Hash byte sequence with 64-bit variant of FNV-1a.
 fnv1a64 :: Bytes -> Word64
-fnv1a64 = foldl'
+fnv1a64 !b = foldl'
   (\acc w -> (fromIntegral @Word8 @Word64 w `xor` acc) * 0x00000100000001B3
-  ) 0xcbf29ce484222325
+  ) 0xcbf29ce484222325 b
 
 -- | Left fold over bytes, strict in the accumulator.
 foldl' :: (a -> Word8 -> a) -> a -> Bytes -> a
@@ -131,6 +135,7 @@
 -- | Yields a pointer to the beginning of the byte sequence. It is only safe
 -- to call this on a 'Bytes' backed by a pinned @ByteArray@.
 contents :: Bytes -> Ptr Word8
+{-# inline contents #-}
 contents (Bytes arr off _) = plusPtr (PM.byteArrayContents arr) off
 
 -- | Convert the sliced 'Bytes' to an unsliced 'ByteArray'. This
@@ -138,6 +143,7 @@
 -- implies that all of the bytes are used and they are already pinned.
 -- Otherwise, it makes a copy.
 toPinnedByteArray :: Bytes -> ByteArray
+{-# inline toPinnedByteArray #-}
 toPinnedByteArray b@(Bytes arr off len)
   | off == 0, PM.sizeofByteArray arr == len, PM.isByteArrayPinned arr = arr
   | otherwise = toPinnedByteArrayClone b
