diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,6 @@
+1.4.2
+  - Support GHC 9.4
+
 1.4.1
   - Support ShortByteString and ShortText
 
diff --git a/proto3-wire.cabal b/proto3-wire.cabal
--- a/proto3-wire.cabal
+++ b/proto3-wire.cabal
@@ -1,7 +1,7 @@
 cabal-version: >=1.10
 
 name:         proto3-wire
-version:      1.4.1
+version:      1.4.2
 synopsis:     A low-level implementation of the Protocol Buffers (version 3) wire format
 license:      Apache-2.0
 license-file: LICENSE
@@ -38,20 +38,19 @@
     , bytestring >=0.10.6.0 && <0.12.0
     , cereal >= 0.5.1 && <0.6
     , containers >=0.5 && < 0.7
-    , deepseq ==1.4.*
-    , ghc-prim >=0.5.3 && <0.9
+    , deepseq >=1.4 && <1.6
     , hashable <1.5
     , parameterized >=0.5.0.0 && <1
-    , primitive >=0.6.4 && <0.8
+    , primitive >=0.6.4 && <0.9
     , safe ==0.3.*
-    , template-haskell >= 2.15.0 && < 2.20
-    , text >= 0.2 && <1.3
+    , template-haskell >= 2.15.0 && < 2.22
+    , text >= 0.2 && <2.1
     , text-short ==0.1.*
     , transformers >=0.5.6.2 && <0.6
     , unordered-containers >= 0.1.0.0 && <0.3
     , vector >=0.12.0.2 && <0.14
     , QuickCheck >=2.8 && <3.0
-    , word-compat >= 0.0.2
+    , word-compat >= 0.0.6
 
   exposed-modules:
     Proto3.Wire
@@ -84,7 +83,7 @@
     , tasty >= 0.11 && <1.5
     , tasty-hunit >= 0.9 && <0.11
     , tasty-quickcheck >= 0.8.4 && <0.11
-    , text >= 0.2 && <1.3
+    , text >= 0.2 && <2.1
     , text-short ==0.1.*
     , transformers >=0.5.6.2 && <0.6
     , vector >=0.12.0.2 && <0.14
@@ -105,4 +104,3 @@
     , criterion
     , proto3-wire
     , random
-
diff --git a/src/Proto3/Wire/Encode.hs b/src/Proto3/Wire/Encode.hs
--- a/src/Proto3/Wire/Encode.hs
+++ b/src/Proto3/Wire/Encode.hs
@@ -535,9 +535,9 @@
 -- >>> packedVarints 1 [1, 2, 3]
 -- Proto3.Wire.Encode.unsafeFromLazyByteString "\n\ETX\SOH\STX\ETX"
 packedVarints :: Foldable f => FieldNumber -> f Word64 -> MessageBuilder
-packedVarints num =
-    etaMessageBuilder
-      (embedded num . foldMap (liftBoundedPrim . base128Varint64))
+packedVarints num = etaMessageBuilder (embedded num . payload)
+  where
+    payload = foldMap (liftBoundedPrim . base128Varint64)
 {-# INLINE packedVarints #-}
 
 -- | A faster but more specialized variant of:
@@ -548,8 +548,9 @@
 -- Proto3.Wire.Encode.unsafeFromLazyByteString "\n\ETX\SOH\STX\ETX"
 packedVarintsV ::
   Vector v a => (a -> Word64) -> FieldNumber -> v a -> MessageBuilder
-packedVarintsV f num =
-    embedded num . vectorMessageBuilder (liftBoundedPrim . base128Varint64 . f)
+packedVarintsV f num = embedded num . payload
+  where
+    payload = vectorMessageBuilder (liftBoundedPrim . base128Varint64 . f)
 {-# INLINE packedVarintsV #-}
 
 -- | A faster but more specialized variant of:
@@ -560,10 +561,9 @@
 -- Proto3.Wire.Encode.unsafeFromLazyByteString "\n\STX\SOH\NUL"
 packedBoolsV ::
   Vector v a => (a -> Bool) -> FieldNumber -> v a -> MessageBuilder
-packedBoolsV f num =
-    embedded num . MessageBuilder . Prim.vectorFixedPrim op
+packedBoolsV f num = embedded num . MessageBuilder . payload
   where
-    op = Prim.word8 . fromIntegral . fromEnum . f
+    payload = Prim.vectorFixedPrim (Prim.word8 . fromIntegral . fromEnum . f)
 {-# INLINE packedBoolsV #-}
 
 -- | Encode fixed-width Word32s in the space-efficient packed format.
@@ -574,8 +574,9 @@
 -- >>> packedFixed32 1 [1, 2, 3]
 -- Proto3.Wire.Encode.unsafeFromLazyByteString "\n\f\SOH\NUL\NUL\NUL\STX\NUL\NUL\NUL\ETX\NUL\NUL\NUL"
 packedFixed32 :: Foldable f => FieldNumber -> f Word32 -> MessageBuilder
-packedFixed32 num =
-    etaMessageBuilder (embedded num . foldMap (MessageBuilder . RB.word32LE))
+packedFixed32 num = etaMessageBuilder (embedded num . payload)
+  where
+    payload = foldMap (MessageBuilder . RB.word32LE)
 {-# INLINE packedFixed32 #-}
 
 -- | A faster but more specialized variant of:
@@ -586,8 +587,9 @@
 -- Proto3.Wire.Encode.unsafeFromLazyByteString "\n\f\SOH\NUL\NUL\NUL\STX\NUL\NUL\NUL\ETX\NUL\NUL\NUL"
 packedFixed32V ::
   Vector v a => (a -> Word32) -> FieldNumber -> v a -> MessageBuilder
-packedFixed32V f num =
-    embedded num . MessageBuilder . Prim.vectorFixedPrim (Prim.word32LE . f)
+packedFixed32V f num = etaMessageBuilder (embedded num . payload)
+  where
+    payload = MessageBuilder . Prim.vectorFixedPrim (Prim.word32LE . f)
 {-# INLINE packedFixed32V #-}
 
 -- | Encode fixed-width Word64s in the space-efficient packed format.
@@ -598,8 +600,9 @@
 -- >>> packedFixed64 1 [1, 2, 3]
 -- Proto3.Wire.Encode.unsafeFromLazyByteString "\n\CAN\SOH\NUL\NUL\NUL\NUL\NUL\NUL\NUL\STX\NUL\NUL\NUL\NUL\NUL\NUL\NUL\ETX\NUL\NUL\NUL\NUL\NUL\NUL\NUL"
 packedFixed64 :: Foldable f => FieldNumber -> f Word64 -> MessageBuilder
-packedFixed64 num =
-    etaMessageBuilder (embedded num . foldMap (MessageBuilder . RB.word64LE))
+packedFixed64 num = etaMessageBuilder (embedded num . payload)
+  where
+    payload = foldMap (MessageBuilder . RB.word64LE)
 {-# INLINE packedFixed64 #-}
 
 -- | A faster but more specialized variant of:
@@ -610,8 +613,9 @@
 -- Proto3.Wire.Encode.unsafeFromLazyByteString "\n\CAN\SOH\NUL\NUL\NUL\NUL\NUL\NUL\NUL\STX\NUL\NUL\NUL\NUL\NUL\NUL\NUL\ETX\NUL\NUL\NUL\NUL\NUL\NUL\NUL"
 packedFixed64V ::
   Vector v a => (a -> Word64) -> FieldNumber -> v a -> MessageBuilder
-packedFixed64V f num =
-    embedded num . MessageBuilder . Prim.vectorFixedPrim (Prim.word64LE . f)
+packedFixed64V f num = etaMessageBuilder (embedded num . payload)
+  where
+    payload = MessageBuilder . Prim.vectorFixedPrim (Prim.word64LE . f)
 {-# INLINE packedFixed64V #-}
 
 -- | Encode floats in the space-efficient packed format.
@@ -620,8 +624,9 @@
 -- >>> 1 `packedFloats` [1, 2, 3]
 -- Proto3.Wire.Encode.unsafeFromLazyByteString "\n\f\NUL\NUL\128?\NUL\NUL\NUL@\NUL\NUL@@"
 packedFloats :: Foldable f => FieldNumber -> f Float -> MessageBuilder
-packedFloats num =
-    etaMessageBuilder (embedded num . foldMap (MessageBuilder . RB.floatLE))
+packedFloats num = etaMessageBuilder (embedded num . payload)
+  where
+    payload = foldMap (MessageBuilder . RB.floatLE)
 {-# INLINE packedFloats #-}
 
 -- | A faster but more specialized variant of:
@@ -632,8 +637,9 @@
 -- Proto3.Wire.Encode.unsafeFromLazyByteString "\n\f\NUL\NUL\128?\NUL\NUL\NUL@\NUL\NUL@@"
 packedFloatsV ::
   Vector v a => (a -> Float) -> FieldNumber -> v a -> MessageBuilder
-packedFloatsV f num =
-    embedded num . MessageBuilder . Prim.vectorFixedPrim (Prim.floatLE . f)
+packedFloatsV f num = etaMessageBuilder (embedded num . payload)
+  where
+    payload = MessageBuilder . Prim.vectorFixedPrim (Prim.floatLE . f)
 {-# INLINE packedFloatsV #-}
 
 -- | Encode doubles in the space-efficient packed format.
@@ -642,8 +648,9 @@
 -- >>> 1 `packedDoubles` [1, 2, 3]
 -- Proto3.Wire.Encode.unsafeFromLazyByteString "\n\CAN\NUL\NUL\NUL\NUL\NUL\NUL\240?\NUL\NUL\NUL\NUL\NUL\NUL\NUL@\NUL\NUL\NUL\NUL\NUL\NUL\b@"
 packedDoubles :: Foldable f => FieldNumber -> f Double -> MessageBuilder
-packedDoubles num =
-    etaMessageBuilder (embedded num . foldMap (MessageBuilder . RB.doubleLE))
+packedDoubles num = etaMessageBuilder (embedded num . payload)
+  where
+    payload = foldMap (MessageBuilder . RB.doubleLE)
 {-# INLINE packedDoubles #-}
 
 -- | A faster but more specialized variant of:
@@ -654,8 +661,9 @@
 -- Proto3.Wire.Encode.unsafeFromLazyByteString "\n\CAN\NUL\NUL\NUL\NUL\NUL\NUL\240?\NUL\NUL\NUL\NUL\NUL\NUL\NUL@\NUL\NUL\NUL\NUL\NUL\NUL\b@"
 packedDoublesV ::
   Vector v a => (a -> Double) -> FieldNumber -> v a -> MessageBuilder
-packedDoublesV f num =
-    embedded num . MessageBuilder . Prim.vectorFixedPrim (Prim.doubleLE . f)
+packedDoublesV f num = etaMessageBuilder (embedded num . payload)
+  where
+    payload = MessageBuilder . Prim.vectorFixedPrim (Prim.doubleLE . f)
 {-# INLINE packedDoublesV #-}
 
 -- | Encode an embedded message.
diff --git a/src/Proto3/Wire/Reverse.hs b/src/Proto3/Wire/Reverse.hs
--- a/src/Proto3/Wire/Reverse.hs
+++ b/src/Proto3/Wire/Reverse.hs
@@ -25,6 +25,7 @@
 -- [6,0,0,0,42,206,187]
 
 {-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE CPP #-}
 
 module Proto3.Wire.Reverse
     ( -- * `BuildR` type
@@ -85,7 +86,6 @@
 
 import           Data.Bits                     ( (.&.) )
 import qualified Data.ByteString               as B
-import qualified Data.ByteString.Internal      as BI
 import qualified Data.ByteString.Lazy          as BL
 import qualified Data.ByteString.Lazy.Internal as BLI
 import qualified Data.ByteString.Short         as BS
@@ -95,15 +95,21 @@
 import           Data.Int                      ( Int8, Int16, Int32, Int64 )
 import qualified Data.Text                     as T
 import qualified Data.Text.Internal            as TI
-import qualified Data.Text.Internal.Fusion     as TIF
 import qualified Data.Text.Lazy                as TL
 import qualified Data.Text.Short               as TS
 import           Data.Vector.Generic           ( Vector )
 import           Data.Word                     ( Word8, Word16, Word32, Word64 )
-import           Foreign                       ( castPtr )
+import           Foreign                       ( castPtr, copyBytes )
 import           Proto3.Wire.Reverse.Internal
 import qualified Proto3.Wire.Reverse.Prim      as Prim
 
+#if MIN_VERSION_text(2,0,0)
+import           Control.Monad.ST.Unsafe       (unsafeSTToIO)
+import qualified Data.Text.Array               as TA
+#else
+import qualified Data.Text.Internal.Fusion     as TIF
+#endif
+
 -- $setup
 -- >>> :set -XOverloadedStrings
 -- >>> :module Proto3.Wire.Reverse
@@ -134,7 +140,7 @@
     then
       unsafeConsume len $ \dst ->
         BU.unsafeUseAsCString bs $ \src ->
-          BI.memcpy dst (castPtr src) len
+          copyBytes dst (castPtr src) len
     else
       prependChunk bs
 
@@ -170,7 +176,7 @@
           (prepend (ReverseChunks cs) <>) $
             unsafeConsume len $ \dst ->
               BU.unsafeUseAsCString c $ \src ->
-                BI.memcpy dst (castPtr src) len
+                copyBytes dst (castPtr src) len
         else
           prependReverseChunks (ReverseChunks(BLI.Chunk c cs))
 
@@ -508,7 +514,26 @@
 -- >>> textUtf8 "←↑→↓"
 -- Proto3.Wire.Reverse.lazyByteString "\226\134\144\226\134\145\226\134\146\226\134\147"
 textUtf8 :: T.Text -> BuildR
+#if MIN_VERSION_text(2,0,0)
+textUtf8 = etaBuildR $ \(TI.Text arr off word8Count) ->
+  -- For version 2 of the "text" package, the in-memory
+  -- representation is UTF-8.  We can just write it out.
+  withUnused $ \unused ->
+      if word8Count <= unused
+        then
+          writeChunk arr off word8Count
+        else
+          let rest = word8Count - unused in
+          writeChunk arr off rest <>
+            reallocate rest <> writeChunk arr (off + rest) unused
+    where
+      writeChunk src off len =
+        unsafeConsume len $ \dst -> unsafeSTToIO $
+          TA.copyToPointer src off dst len
+#else
 textUtf8 = etaBuildR $ \txt@(TI.Text _ _ word16Count) ->
+  -- For version 1 of the "text" package, the in-memory
+  -- representation is UTF-16.  We must transcode to UTF-8.
   case TIF.reverseStream txt of
     TIF.Stream next t0 _ -> ensure bound (go t0)
       where
@@ -533,6 +558,7 @@
           TIF.Skip t2 -> go t2
           TIF.Yield !ch t2 ->
             go t2 <> Prim.unsafeBuildBoundedPrim (Prim.charUtf8 ch)
+#endif
 
 -- | Convert a Unicode lazy `TL.Text` to a `BuildR` using a @UTF-8@ encoding
 --
diff --git a/src/Proto3/Wire/Reverse/Prim.hs b/src/Proto3/Wire/Reverse/Prim.hs
--- a/src/Proto3/Wire/Reverse/Prim.hs
+++ b/src/Proto3/Wire/Reverse/Prim.hs
@@ -711,8 +711,13 @@
 -- | The bounded primitive implementing
 -- `Proto3.Wire.Reverse.word32Base128LEVar`.
 word32Base128LEVar :: Word32 -> BoundedPrim 5
-word32Base128LEVar = word32Base128LEVar_inline
-{-# INLINE word32Base128LEVar #-}
+word32Base128LEVar (W32# x0) =
+  ( wordBase128LEVar_choose 1 wordBase128LE_p1 $
+    wordBase128LEVar_choose 2 wordBase128LE_p2 $
+    wordBase128LEVar_choose 3 wordBase128LE_p3 $
+    wordBase128LEVar_choose 4 wordBase128LE_p4 $
+    (\x -> liftFixedPrim (wordBase128LE_p5 0## x))
+  ) x0
 
 -- | Like 'word32Base128LEVar' but inlined, which currently means
 -- that it is just the same as 'word32Base128LEVar', which we inline.
@@ -798,14 +803,13 @@
     pif (W64# x <= fromIntegral (maxBound :: Word32))
           (word32Base128LEVar (fromIntegral (W64# x)))
           (word64Base128LEVar_big x)
-{-# INLINE word64Base128LEVar #-}
 
 -- | Like 'word64Base128LEVar' but inlined, possibly bloating your code.  On
 -- the other hand, inlining an application to a constant may shrink your code.
 word64Base128LEVar_inline :: Word64 -> BoundedPrim 10
 word64Base128LEVar_inline = \(W64# x) ->
     pif (W64# x <= fromIntegral (maxBound :: Word32))
-          (word32Base128LEVar (fromIntegral (W64# x)))
+          (word32Base128LEVar_inline (fromIntegral (W64# x)))
           (inline (word64Base128LEVar_big x))
 {-# INLINE word64Base128LEVar_inline #-}
 
