diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,10 @@
 `small-bytearray-builder` is now just a compatibility shim
 to ease the migration process.
 
+## 0.3.12.0 -- 2022-12-01
+
+* Support GHC 9.4.
+
 ## 0.3.11.0 -- 2022-04-12
 
 * Support GHC 9.2
diff --git a/bytebuild.cabal b/bytebuild.cabal
--- a/bytebuild.cabal
+++ b/bytebuild.cabal
@@ -1,6 +1,6 @@
 cabal-version: 2.2
 name: bytebuild
-version: 0.3.11.0
+version: 0.3.12.0
 synopsis: Build byte arrays
 description:
   This is similar to the builder facilities provided by
@@ -45,10 +45,11 @@
     Data.Bytes.Builder.Bounded.Unsafe
   other-modules:
     Compat
+    Op
   reexported-modules:
     Data.Bytes.Chunks
   build-depends:
-    , base >=4.12.0.0 && <4.17
+    , base >=4.12.0.0 && <4.18
     , byteslice >=0.2.6 && <0.3
     , bytestring >=0.10.8.2 && <0.12
     , haskell-src-meta >=0.8
@@ -68,8 +69,10 @@
       hs-source-dirs: src-9.0
   if flag(checked)
     build-depends: primitive-checked >= 0.7 && <0.8
+    hs-source-dirs: src-checked
   else
     build-depends: primitive >= 0.7 && <0.8
+    hs-source-dirs: src-unchecked
   ghc-options: -Wall -O2
   hs-source-dirs: src
   default-language: Haskell2010
@@ -99,7 +102,7 @@
     , tasty >=1.2.3 && <1.5
     , tasty-hunit >=0.10.0.2 && <0.11
     , tasty-quickcheck >=0.10.1 && <0.11
-    , text >=1.2 && <1.3
+    , text >=1.2 && <2.2
     , vector
     , wide-word >=0.1.0.9 && <0.2
 
diff --git a/src-checked/Op.hs b/src-checked/Op.hs
new file mode 100644
--- /dev/null
+++ b/src-checked/Op.hs
@@ -0,0 +1,45 @@
+{-# language MagicHash #-}
+{-# language UnboxedTuples #-}
+
+module Op
+  ( writeCharArray#
+  , copyByteArray#
+  , copyMutableByteArray#
+  ) where
+
+import GHC.Exts ((<#),(>=#),State#,Int#,MutableByteArray#,ByteArray#,Char#)
+import GHC.Int (Int(I#))
+import qualified GHC.Exts as Exts
+
+writeCharArray# :: MutableByteArray# s -> Int# -> Char# -> State# s -> State# s
+writeCharArray# arr i v st = case i <# 0# of
+  1# -> error ("writeCharArray#: negative index " ++ show (I# i))
+  _ -> case Exts.getSizeofMutableByteArray# arr st of
+    (# st', sz #) -> case i >=# sz of
+      1# -> error ("writeCharArray#: index " ++ show (I# i) ++ " >= length " ++ show (I# sz))
+      _ -> Exts.writeCharArray# arr i v st'
+
+copyByteArray# :: ByteArray# -> Int# -> MutableByteArray# s -> Int# -> Int# -> State# s -> State# s
+copyByteArray# src soff dst doff len s0 =
+  case Exts.getSizeofMutableByteArray# dst s0 of
+    (# s1, sz #)
+      | I# soff >= 0
+      , I# doff >= 0
+      , I# len >= 0
+      , I# doff + I# len <= I# sz
+      , I# soff + I# len <= I# (Exts.sizeofByteArray# src)
+        -> Exts.copyByteArray# src soff dst doff len s1
+      | otherwise -> error "copyByteArray#: index range out of bounds"
+
+copyMutableByteArray# :: MutableByteArray# s -> Int# -> MutableByteArray# s -> Int# -> Int# -> State# s -> State# s
+copyMutableByteArray# src soff dst doff len s0 =
+  case Exts.getSizeofMutableByteArray# dst s0 of
+    (# s1, szDst #) -> case Exts.getSizeofMutableByteArray# src s1 of
+      (# s2, szSrc #)
+        | I# soff >= 0
+        , I# doff >= 0
+        , I# len >= 0
+        , I# doff + I# len <= I# szDst
+        , I# soff + I# len <= I# szSrc
+          -> Exts.copyMutableByteArray# src soff dst doff len s2
+        | otherwise -> error "copyMutableByteArray#: index range out of bounds"
diff --git a/src-unchecked/Op.hs b/src-unchecked/Op.hs
new file mode 100644
--- /dev/null
+++ b/src-unchecked/Op.hs
@@ -0,0 +1,9 @@
+{-# language MagicHash #-}
+
+module Op
+  ( writeCharArray#
+  , copyByteArray#
+  , copyMutableByteArray#
+  ) where
+
+import GHC.Exts (copyMutableByteArray#,writeCharArray#,copyByteArray#,copyMutableByteArray#)
diff --git a/src/Data/Bytes/Builder.hs b/src/Data/Bytes/Builder.hs
--- a/src/Data/Bytes/Builder.hs
+++ b/src/Data/Bytes/Builder.hs
@@ -182,6 +182,7 @@
 import qualified Data.Primitive as PM
 import qualified Data.Text.Short as TS
 import qualified GHC.Exts as Exts
+import qualified Op as Op
 
 -- | Run a builder.
 run ::
@@ -384,9 +385,9 @@
       1# -> case Exts.newByteArray# 0# s0 of
         (# s1, buf1 #) -> (# s1, buf1, 0#, 0#, Immutable src# soff# slen# (Mutable buf0 off0 cs0) #)
       _ -> case Exts.newByteArray# 4080# s0 of
-        (# s1, buf1 #) -> case Exts.copyByteArray# src# soff# buf1 0# slen# s1 of
+        (# s1, buf1 #) -> case Op.copyByteArray# src# soff# buf1 0# slen# s1 of
           s2 -> (# s2, buf1, slen#, 4080# -# slen#, Mutable buf0 off0 cs0 #)
-    _ -> let !s1 = Exts.copyByteArray# src# soff# buf0 off0 slen# s0 in
+    _ -> let s1 = Op.copyByteArray# src# soff# buf0 off0 slen# s0 in
       (# s1, buf0, off0 +# slen#, len0 -# slen#, cs0 #)
   )
 
@@ -397,9 +398,9 @@
 copy (Bytes (ByteArray src# ) (I# soff# ) (I# slen# )) = Builder
   (\buf0 off0 len0 cs0 s0 -> case len0 <# slen# of
     1# -> case Exts.newByteArray# newSz s0 of
-        (# s1, buf1 #) -> case Exts.copyByteArray# src# soff# buf1 0# slen# s1 of
+        (# s1, buf1 #) -> case Op.copyByteArray# src# soff# buf1 0# slen# s1 of
           s2 -> (# s2, buf1, slen#, newSz -# slen#, Mutable buf0 off0 cs0 #)
-    _ -> let !s1 = Exts.copyByteArray# src# soff# buf0 off0 slen# s0 in
+    _ -> let !s1 = Op.copyByteArray# src# soff# buf0 off0 slen# s0 in
       (# s1, buf0, off0 +# slen#, len0 -# slen#, cs0 #)
   )
   where
@@ -411,10 +412,10 @@
 copyCons (W8# w0) (Bytes (ByteArray src# ) (I# soff# ) (I# slen# )) = Builder
   (\buf0 off0 len0 cs0 s0 -> case len0 <# (slen# +# 1#) of
     1# -> case Exts.newByteArray# newSz s0 of
-        (# s1, buf1 #) -> case Exts.copyByteArray# src# soff# buf1 1# slen# s1 of
+        (# s1, buf1 #) -> case Op.copyByteArray# src# soff# buf1 1# slen# s1 of
           s2 -> case Exts.writeWord8Array# buf1 0# w0 s2 of
             s3 -> (# s3, buf1, slen# +# 1#, newSz -# (slen# +# 1#), Mutable buf0 off0 cs0 #)
-    _ -> let !s1 = Exts.copyByteArray# src# soff# buf0 (off0 +# 1#) slen# s0
+    _ -> let !s1 = Op.copyByteArray# src# soff# buf0 (off0 +# 1#) slen# s0
              !s2 = Exts.writeWord8Array# buf0 off0 w0 s1
           in (# s2, buf0, off0 +# (slen# +# 1#), len0 -# (slen# +# 1#), cs0 #)
   )
@@ -507,11 +508,11 @@
       (Bytes (ByteArray srcB# ) (I# soffB# ) (I# slenB# )) = Builder
   (\buf0 off0 len0 cs0 s0 -> case len0 <# slen# of
     1# -> case Exts.newByteArray# newSz s0 of
-        (# s1, buf1 #) -> case Exts.copyByteArray# srcA# soffA# buf1 0# slenA# s1 of
-          s2 -> case Exts.copyByteArray# srcB# soffB# buf1 slenA# slenB# s2 of
+        (# s1, buf1 #) -> case Op.copyByteArray# srcA# soffA# buf1 0# slenA# s1 of
+          s2 -> case Op.copyByteArray# srcB# soffB# buf1 slenA# slenB# s2 of
             s3 -> (# s3, buf1, slen#, newSz -# slen#, Mutable buf0 off0 cs0 #)
-    _ -> let !s1 = Exts.copyByteArray# srcA# soffA# buf0 off0 slenA# s0
-             !s2 = Exts.copyByteArray# srcB# soffB# buf0 (off0 +# slenA# ) slenB# s1 in
+    _ -> let !s1 = Op.copyByteArray# srcA# soffA# buf0 off0 slenA# s0
+             !s2 = Op.copyByteArray# srcB# soffB# buf0 (off0 +# slenA# ) slenB# s1 in
       (# s2, buf0, off0 +# slen#, len0 -# slen#, cs0 #)
   )
   where
diff --git a/src/Data/Bytes/Builder/Bounded.hs b/src/Data/Bytes/Builder/Bounded.hs
--- a/src/Data/Bytes/Builder/Bounded.hs
+++ b/src/Data/Bytes/Builder/Bounded.hs
@@ -1,3 +1,4 @@
+{-# language CPP #-}
 {-# language BangPatterns #-}
 {-# language BinaryLiterals #-}
 {-# language DataKinds #-}
@@ -227,7 +228,12 @@
 -- | Requires up to 19 bytes. Encodes an unsigned 64-bit integer as decimal.
 -- This encoding never starts with a zero unless the argument was zero.
 word64Dec :: Word64 -> Builder 19
-word64Dec (W64# w) = wordCommonDec# w
+word64Dec (W64# w) = wordCommonDec#
+#if MIN_VERSION_base(4,17,0)
+    (word64ToWord# w)
+#else
+    w
+#endif
 
 -- | Requires up to 10 bytes. Encodes an unsigned 32-bit integer as decimal.
 -- This encoding never starts with a zero unless the argument was zero.
@@ -262,7 +268,12 @@
 -- Negative numbers are preceded by a minus sign. Positive numbers
 -- are not preceded by anything.
 int64Dec :: Int64 -> Builder 20
-int64Dec (I64# w) = intCommonDec# w
+int64Dec (I64# w) = intCommonDec#
+#if MIN_VERSION_base(4,17,0)
+    (int64ToInt# w)
+#else
+    w
+#endif
 
 -- | Requires up to 11 bytes. Encodes a signed 32-bit integer as decimal.
 -- This encoding never starts with a zero unless the argument was zero.
@@ -318,7 +329,12 @@
     writeByteArray arr off0 (c2w '0')
     pure (off0 + 1)
   where
-  w = W64# w#
+  w = W64#
+#if MIN_VERSION_base(4,17,0)
+    (wordToWord64# w#)
+#else
+    w#
+#endif
 
 internalWordLoop :: MutableByteArray s -> Int -> Word -> ST s Int
 {-# inline internalWordLoop #-}
@@ -350,7 +366,12 @@
     writeByteArray arr off0 (c2w '-')
     internalWordLoop arr (off0 + 1) (fromIntegral (negate w))
   where
-  w = I64# w#
+  w = I64#
+#if MIN_VERSION_base(4,17,0)
+    (intToInt64# w#)
+#else
+    w#
+#endif
 
 -- Convert a number between 0 and 16 to the ASCII
 -- representation of its hexadecimal character.
@@ -422,14 +443,25 @@
 -- uppercase for the alphabetical digits. For example, this encodes the
 -- number 1022 as @00000000000003FE@.
 word64PaddedUpperHex :: Word64 -> Builder 16
-word64PaddedUpperHex (W64# w) = word64PaddedUpperHex# w
+word64PaddedUpperHex (W64# w) = word64PaddedUpperHex#
+#if MIN_VERSION_base(4,17,0)
+    (word64ToWord# w)
+#else
+    w
+#endif
 
+
 -- | Requires exactly 16 bytes. Encodes a 64-bit unsigned integer as
 -- hexadecimal, zero-padding the encoding to 16 digits. This uses
 -- lowercase for the alphabetical digits. For example, this encodes the
 -- number 1022 as @00000000000003fe@.
 word64PaddedLowerHex :: Word64 -> Builder 16
-word64PaddedLowerHex (W64# w) = word64PaddedLowerHex# w
+word64PaddedLowerHex (W64# w) = word64PaddedLowerHex#
+#if MIN_VERSION_base(4,17,0)
+    (word64ToWord# w)
+#else
+    w
+#endif
 
 -- | Requires exactly 12 bytes. Discards the upper 16 bits of a
 -- 64-bit unsigned integer and then encodes the lower 48 bits as
@@ -437,7 +469,12 @@
 -- lowercase for the alphabetical digits. For example, this encodes the
 -- number 1022 as @0000000003fe@.
 word48PaddedLowerHex :: Word64 -> Builder 12
-word48PaddedLowerHex (W64# w) = word48PaddedLowerHex# w
+word48PaddedLowerHex (W64# w) = word48PaddedLowerHex#
+#if MIN_VERSION_base(4,17,0)
+    (word64ToWord# w)
+#else
+    w
+#endif
 
 -- | Requires exactly 8 bytes. Encodes a 32-bit unsigned integer as
 -- hexadecimal, zero-padding the encoding to 8 digits. This uses
@@ -735,7 +772,7 @@
 wordPaddedDec4 :: Word -> Builder 4
 wordPaddedDec4 !w = Unsafe.construct $ \arr off -> do
   putRem10
-    (putRem10 $ putRem10 $ putRem10 
+    (putRem10 $ putRem10 $ putRem10
      (\_ _ _ -> pure ())
     ) arr (off + 3) w
   pure (off + 4)
@@ -862,7 +899,13 @@
 -- | Encode a 64-bit word with LEB-128.
 word64LEB128 :: Word64 -> Builder 10
 {-# inline word64LEB128 #-}
-word64LEB128 (W64# w) = lebCommon (W# w)
+word64LEB128 (W64# w) = lebCommon (W#
+#if MIN_VERSION_base(4,17,0)
+    (word64ToWord# w)
+#else
+    w
+#endif
+  )
 
 lebCommon :: Word -> Builder n
 lebCommon !w = case quotRem w 128 of
@@ -935,7 +978,12 @@
     byteFourFour w = (0b00111111 .&. w) .|. 0b10000000
 
 int64BE :: Int64 -> Builder 8
-int64BE (I64# i) = word64BE (W64# (int2Word# i))
+int64BE (I64# i) = word64BE (W64# (
+#if MIN_VERSION_base(4,17,0)
+  wordToWord64# (int2Word# (int64ToInt# i))))
+#else
+  int2Word# i))
+#endif
 
 int32BE :: Int32 -> Builder 4
 int32BE (I32# i) = word32BE (W32# (C.wordToWord32# (int2Word# (C.int32ToInt# i))))
@@ -944,7 +992,13 @@
 int16BE (I16# i) = word16BE (W16# (C.wordToWord16# (int2Word# (C.int16ToInt# i))))
 
 int64LE :: Int64 -> Builder 8
-int64LE (I64# i) = word64LE (W64# (int2Word# i))
+int64LE (I64# i) = word64LE (W64# (
+#if MIN_VERSION_base(4,17,0)
+  wordToWord64# (int2Word# (int64ToInt# i))))
+#else
+  int2Word# i))
+#endif
+
 
 int32LE :: Int32 -> Builder 4
 int32LE (I32# i) = word32LE (W32# (C.wordToWord32# (int2Word# (C.int32ToInt# i))))
diff --git a/src/Data/Bytes/Builder/Unsafe.hs b/src/Data/Bytes/Builder/Unsafe.hs
--- a/src/Data/Bytes/Builder/Unsafe.hs
+++ b/src/Data/Bytes/Builder/Unsafe.hs
@@ -53,6 +53,7 @@
 import qualified Data.Bytes.Builder.Bounded.Unsafe as UnsafeBounded
 import qualified Data.Primitive as PM
 import qualified GHC.Exts as Exts
+import qualified Op
 
 -- | An unmaterialized sequence of bytes that may be pasted
 -- into a mutable byte array.
@@ -203,11 +204,11 @@
 copyReverseCommits# _ off Initial s0 = (# s0, off #)
 copyReverseCommits# marr prevOff (Mutable arr sz cs) s0 =
   let !off = prevOff -# sz in
-  case Exts.copyMutableByteArray# arr 0# marr off sz s0 of
+  case Op.copyMutableByteArray# arr 0# marr off sz s0 of
     s1 -> copyReverseCommits# marr off cs s1
 copyReverseCommits# marr prevOff (Immutable arr soff sz cs) s0 =
   let !off = prevOff -# sz in
-  case Exts.copyByteArray# arr soff marr off sz s0 of
+  case Op.copyByteArray# arr soff marr off sz s0 of
     s1 -> copyReverseCommits# marr off cs s1
 
 -- | Create a builder from a cons-list of 'Char'. These
diff --git a/test/Main.hs b/test/Main.hs
--- a/test/Main.hs
+++ b/test/Main.hs
@@ -260,6 +260,10 @@
           Chunks.concat (run 16 (word64LEB128 16))
           @=?
           Latin1.fromString "\x10"
+      , THU.testCase "1000000" $
+          Chunks.concat (run 16 (word64LEB128 1000000))
+          @=?
+          Exts.fromList [0xc0,0x84,0x3d]
       , THU.testCase "deadbeef-smile" $ do
         let inp = Latin1.fromString "\xDE\xAD\xBE\xEF"
         (Chunks.concat . run 16) (sevenEightSmile inp)
