diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,12 @@
 # Revision history for byteslice
 
+## 0.2.14.0 -- 2024-02-26
+
+* Add functions to `Data.Bytes.Text.AsciiExt`: `split(1|2|3|4)`,
+  `splitTetragram1`, `anyEq`, `takeWhileNotEq`, `dropWhileNotEq`,
+  `takeWhileEndNotEq`, dropWhileEndEq`.
+* Increment upper bound on `primitive-unlifted`
+
 ## 0.2.13.2 -- 2024-02-06
 
 * Restore `Data.Bytes.Text.Utf8.toText`.
diff --git a/byteslice.cabal b/byteslice.cabal
--- a/byteslice.cabal
+++ b/byteslice.cabal
@@ -1,6 +1,6 @@
 cabal-version:   2.4
 name:            byteslice
-version:         0.2.13.2
+version:         0.2.14.0
 synopsis:        Slicing managed and unmanaged memory
 description:
   This library provides types that allow the user to talk about a slice of
@@ -61,7 +61,7 @@
     , natural-arithmetic  >=0.1.4
     , primitive           >=0.7.4  && <0.10
     , primitive-addr      >=0.1    && <0.2
-    , primitive-unlifted  >=0.1.2  && <2.2
+    , primitive-unlifted  >=0.1.2  && <2.3
     , run-st              >=0.1.1  && <0.2
     , text                >=1.2.5
     , text-short          >=0.1.3  && <0.2
diff --git a/src/Data/Bytes.hs b/src/Data/Bytes.hs
--- a/src/Data/Bytes.hs
+++ b/src/Data/Bytes.hs
@@ -34,8 +34,8 @@
   , unsnoc
 
     -- * Predicates
-  , any
-  , all
+  , Pure.any
+  , Pure.all
 
     -- * Create
 
@@ -89,7 +89,7 @@
 
     -- ** Fixed from Beginning
   , Byte.split1
-  , splitTetragram1
+  , Pure.splitTetragram1
   , Byte.split2
   , Byte.split3
   , Byte.split4
@@ -106,7 +106,7 @@
     -- * Searching
   , replace
   , findIndices
-  , findTetragramIndex
+  , Pure.findTetragramIndex
 
     -- * Counting
   , Byte.count
@@ -207,9 +207,8 @@
 import Control.Monad.Primitive (PrimMonad, primitive_, unsafeIOToPrim)
 import Control.Monad.ST.Run (runByteArrayST)
 import Cstrlen (cstringLength#)
-import Data.Bits (unsafeShiftL, (.|.))
 import Data.ByteString.Short.Internal (ShortByteString (SBS))
-import Data.Bytes.Pure (foldr, fromByteArray, length, toShortByteString, unsafeDrop, unsafeIndex)
+import Data.Bytes.Pure (fromByteArray, length, toShortByteString, unsafeDrop, unsafeIndex)
 import Data.Bytes.Search (findIndices, isInfixOf, replace)
 import Data.Bytes.Types (ByteArrayN (ByteArrayN), Bytes (Bytes, array, offset), BytesN (BytesN))
 import Data.Primitive (Array, ByteArray (ByteArray))
@@ -217,7 +216,7 @@
 import Foreign.C.String (CString)
 import Foreign.Ptr (Ptr, castPtr, plusPtr)
 import GHC.Exts (Addr#, Int (I#), Int#, Ptr (Ptr), Word#)
-import GHC.Word (Word32, Word8 (W8#))
+import GHC.Word (Word8 (W8#))
 import Reps (Bytes# (..), word8ToWord#)
 
 import qualified Arithmetic.Nat as Nat
@@ -402,12 +401,12 @@
 -- | Take bytes while the predicate is true.
 takeWhile :: (Word8 -> Bool) -> Bytes -> Bytes
 {-# INLINE takeWhile #-}
-takeWhile k b = Pure.unsafeTake (countWhile k b) b
+takeWhile k b = Pure.unsafeTake (Pure.countWhile k b) b
 
 -- | Drop bytes while the predicate is true.
 dropWhile :: (Word8 -> Bool) -> Bytes -> Bytes
 {-# INLINE dropWhile #-}
-dropWhile k b = Pure.unsafeDrop (countWhile k b) b
+dropWhile k b = Pure.unsafeDrop (Pure.countWhile k b) b
 
 {- | /O(n)/ 'dropWhileEnd' @p@ @b@ returns the prefix remaining after
 dropping characters that satisfy the predicate @p@ from the end of
@@ -415,7 +414,7 @@
 -}
 dropWhileEnd :: (Word8 -> Bool) -> Bytes -> Bytes
 {-# INLINE dropWhileEnd #-}
-dropWhileEnd k !b = Pure.unsafeTake (length b - countWhileEnd k b) b
+dropWhileEnd k !b = Pure.unsafeTake (length b - Pure.countWhileEnd k b) b
 
 {- | /O(n)/ 'takeWhileEnd' @p@ @b@ returns the longest suffix of
 elements that satisfy predicate @p@.
@@ -423,39 +422,9 @@
 takeWhileEnd :: (Word8 -> Bool) -> Bytes -> Bytes
 {-# INLINE takeWhileEnd #-}
 takeWhileEnd k !b =
-  let n = countWhileEnd k b
+  let n = Pure.countWhileEnd k b
    in Bytes (array b) (offset b + length b - n) n
 
--- Internal. The returns the number of bytes that match the
--- predicate until the first non-match occurs. If all bytes
--- match the predicate, this will return the length originally
--- provided.
-countWhile :: (Word8 -> Bool) -> Bytes -> Int
-{-# INLINE countWhile #-}
-countWhile k (Bytes arr off0 len0) = go off0 len0 0
- where
-  go !off !len !n =
-    if len > 0
-      then
-        if k (PM.indexByteArray arr off)
-          then go (off + 1) (len - 1) (n + 1)
-          else n
-      else n
-
--- Internal. Variant of countWhile that starts from the end
--- of the string instead of the beginning.
-countWhileEnd :: (Word8 -> Bool) -> Bytes -> Int
-{-# INLINE countWhileEnd #-}
-countWhileEnd k (Bytes arr off0 len0) = go (off0 + len0 - 1) (len0 - 1) 0
- where
-  go !off !len !n =
-    if len >= 0
-      then
-        if k (PM.indexByteArray arr off)
-          then go (off - 1) (len - 1) (n + 1)
-          else n
-      else n
-
 {- | Convert a 'String' consisting of only characters in the ASCII block
 to a byte sequence. Any character with a codepoint above @U+007F@ is
 replaced by @U+0000@.
@@ -694,16 +663,6 @@
  where
   len = length a + length b + 1
 
--- | /O(n)/ Returns true if any byte in the sequence satisfies the predicate.
-any :: (Word8 -> Bool) -> Bytes -> Bool
-{-# INLINE any #-}
-any f = foldr (\b r -> f b || r) False
-
--- | /O(n)/ Returns true if all bytes in the sequence satisfy the predicate.
-all :: (Word8 -> Bool) -> Bytes -> Bool
-{-# INLINE all #-}
-all f = foldr (\b r -> f b && r) True
-
 {- | Variant of 'toShortByteString' that unconditionally makes a copy of
 the array backing the sliced 'Bytes' even if the original array
 could be reused. Prefer 'toShortByteString'.
@@ -786,52 +745,3 @@
   Nat.with
     (PM.sizeofByteArray arr)
     (\n -> f n (ByteArrayN arr))
-
-findTetragramIndex ::
-  Word8 ->
-  Word8 ->
-  Word8 ->
-  Word8 ->
-  Bytes ->
-  Maybe Int
-findTetragramIndex !w0 !w1 !w2 !w3 (Bytes arr off len) =
-  if len < 4
-    then Nothing
-    else
-      let !target =
-            unsafeShiftL (fromIntegral w0 :: Word32) 24
-              .|. unsafeShiftL (fromIntegral w1 :: Word32) 16
-              .|. unsafeShiftL (fromIntegral w2 :: Word32) 8
-              .|. unsafeShiftL (fromIntegral w3 :: Word32) 0
-          !end = off + len
-          go !ix !acc =
-            if acc == target
-              then
-                let n = ix - off
-                 in Just (n - 4)
-              else
-                if ix < end
-                  then
-                    let !w = PM.indexByteArray arr ix :: Word8
-                        acc' =
-                          (fromIntegral w :: Word32)
-                            .|. unsafeShiftL acc 8
-                     in go (ix + 1) acc'
-                  else Nothing
-          !acc0 =
-            unsafeShiftL (fromIntegral (PM.indexByteArray arr 0 :: Word8) :: Word32) 24
-              .|. unsafeShiftL (fromIntegral (PM.indexByteArray arr 1 :: Word8) :: Word32) 16
-              .|. unsafeShiftL (fromIntegral (PM.indexByteArray arr 2 :: Word8) :: Word32) 8
-              .|. unsafeShiftL (fromIntegral (PM.indexByteArray arr 3 :: Word8) :: Word32) 0
-       in go 4 acc0
-
-splitTetragram1 ::
-  Word8 ->
-  Word8 ->
-  Word8 ->
-  Word8 ->
-  Bytes ->
-  Maybe (Bytes, Bytes)
-splitTetragram1 !w0 !w1 !w2 !w3 !b = case findTetragramIndex w0 w1 w2 w3 b of
-  Nothing -> Nothing
-  Just n -> Just (Pure.unsafeTake n b, Pure.unsafeDrop (n + 4) b)
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
@@ -40,12 +40,19 @@
   , toShortByteString
   , replicate
   , replicateU
+  , splitTetragram1
+  , findTetragramIndex
+  , countWhile
+  , countWhileEnd
+  , any
+  , all
   ) where
 
-import Prelude hiding (Foldable (..), map, replicate)
+import Prelude hiding (Foldable (..), map, replicate, any, all)
 
 import Control.Monad.Primitive (PrimMonad, PrimState)
 import Control.Monad.ST.Run (runByteArrayST)
+import Data.Bits (unsafeShiftL, (.|.))
 import Data.Bits (xor)
 import Data.ByteString (ByteString)
 import Data.ByteString.Short.Internal (ShortByteString (SBS))
@@ -402,3 +409,93 @@
   arr <- PM.newByteArray n
   PM.setByteArray arr 0 n w
   PM.unsafeFreezeByteArray arr
+
+splitTetragram1 ::
+  Word8 ->
+  Word8 ->
+  Word8 ->
+  Word8 ->
+  Bytes ->
+  Maybe (Bytes, Bytes)
+splitTetragram1 !w0 !w1 !w2 !w3 !b = case findTetragramIndex w0 w1 w2 w3 b of
+  Nothing -> Nothing
+  Just n -> Just (unsafeTake n b, unsafeDrop (n + 4) b)
+
+findTetragramIndex ::
+  Word8 ->
+  Word8 ->
+  Word8 ->
+  Word8 ->
+  Bytes ->
+  Maybe Int
+findTetragramIndex !w0 !w1 !w2 !w3 (Bytes arr off len) =
+  if len < 4
+    then Nothing
+    else
+      let !target =
+            unsafeShiftL (fromIntegral w0 :: Word32) 24
+              .|. unsafeShiftL (fromIntegral w1 :: Word32) 16
+              .|. unsafeShiftL (fromIntegral w2 :: Word32) 8
+              .|. unsafeShiftL (fromIntegral w3 :: Word32) 0
+          !end = off + len
+          go !ix !acc =
+            if acc == target
+              then
+                let n = ix - off
+                 in Just (n - 4)
+              else
+                if ix < end
+                  then
+                    let !w = PM.indexByteArray arr ix :: Word8
+                        acc' =
+                          (fromIntegral w :: Word32)
+                            .|. unsafeShiftL acc 8
+                     in go (ix + 1) acc'
+                  else Nothing
+          !acc0 =
+            unsafeShiftL (fromIntegral (PM.indexByteArray arr 0 :: Word8) :: Word32) 24
+              .|. unsafeShiftL (fromIntegral (PM.indexByteArray arr 1 :: Word8) :: Word32) 16
+              .|. unsafeShiftL (fromIntegral (PM.indexByteArray arr 2 :: Word8) :: Word32) 8
+              .|. unsafeShiftL (fromIntegral (PM.indexByteArray arr 3 :: Word8) :: Word32) 0
+       in go 4 acc0
+
+-- Internal. The returns the number of bytes that match the
+-- predicate until the first non-match occurs. If all bytes
+-- match the predicate, this will return the length originally
+-- provided.
+countWhile :: (Word8 -> Bool) -> Bytes -> Int
+{-# INLINE countWhile #-}
+countWhile k (Bytes arr off0 len0) = go off0 len0 0
+ where
+  go !off !len !n =
+    if len > 0
+      then
+        if k (PM.indexByteArray arr off)
+          then go (off + 1) (len - 1) (n + 1)
+          else n
+      else n
+
+-- Internal. Variant of countWhile that starts from the end
+-- of the string instead of the beginning.
+countWhileEnd :: (Word8 -> Bool) -> Bytes -> Int
+{-# INLINE countWhileEnd #-}
+countWhileEnd k (Bytes arr off0 len0) = go (off0 + len0 - 1) (len0 - 1) 0
+ where
+  go !off !len !n =
+    if len >= 0
+      then
+        if k (PM.indexByteArray arr off)
+          then go (off - 1) (len - 1) (n + 1)
+          else n
+      else n
+
+-- | /O(n)/ Returns true if any byte in the sequence satisfies the predicate.
+any :: (Word8 -> Bool) -> Bytes -> Bool
+{-# INLINE any #-}
+any f = foldr (\b r -> f b || r) False
+
+-- | /O(n)/ Returns true if all bytes in the sequence satisfy the predicate.
+all :: (Word8 -> Bool) -> Bytes -> Bool
+{-# INLINE all #-}
+all f = foldr (\b r -> f b && r) True
+
diff --git a/src/Data/Bytes/Text/AsciiExt.hs b/src/Data/Bytes/Text/AsciiExt.hs
--- a/src/Data/Bytes/Text/AsciiExt.hs
+++ b/src/Data/Bytes/Text/AsciiExt.hs
@@ -1,6 +1,7 @@
 {-# LANGUAGE BangPatterns #-}
 {-# LANGUAGE LambdaCase #-}
 {-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE TypeApplications #-}
 
 -- For functions that can fail for bytes outside the ASCII range, see
 -- 'Data.Bytes.Ascii'. For functions that can inspect bytes outside ASCII, see
@@ -19,6 +20,23 @@
   , forLines_
   , foldLines
 
+    -- * Predicates
+  , anyEq
+
+    -- * Filtering
+  , takeWhileNotEq
+  , dropWhileNotEq
+  , takeWhileEndNotEq
+  , dropWhileEndEq
+
+    -- * Splitting
+    -- ** Fixed from Beginning
+  , split1
+  , splitTetragram1
+  , split2
+  , split3
+  , split4
+
     -- * Text Manipulation
   , toLowerU
   ) where
@@ -26,12 +44,14 @@
 import Control.Monad.ST (ST)
 import Control.Monad.ST.Run (runByteArrayST)
 import Data.Bytes.Types (Bytes (..))
+import Data.Char (ord)
 import Data.Primitive (ByteArray)
 import Data.Word (Word8)
 import System.IO (Handle, hIsEOF, stdin)
 
 import qualified Data.ByteString.Char8 as BC8
 import qualified Data.Bytes.Pure as Bytes
+import qualified Data.Bytes.Byte as Byte
 import qualified Data.Primitive as PM
 
 -- | `hForLines_` over `stdin`
@@ -104,3 +124,85 @@
               go (off + 1) (ix + 1) (len - 1)
     go off0 0 len0
     PM.unsafeFreezeByteArray dst
+
+-- | Throws an exception the 'Char' argument is non-ascii.
+split1 :: Char -> Bytes -> Maybe (Bytes, Bytes)
+{-# INLINE split1 #-}
+split1 !c !b
+  | c > '\DEL' = errorWithoutStackTrace "Data.Bytes.Text.AsciiExt.split1: argument not in ASCII range"
+  | otherwise = Byte.split1 (c2w c) b
+
+-- | Throws an exception the 'Char' argument is non-ascii.
+split2 :: Char -> Bytes -> Maybe (Bytes, Bytes, Bytes)
+{-# INLINE split2 #-}
+split2 !c !b
+  | c > '\DEL' = errorWithoutStackTrace "Data.Bytes.Text.AsciiExt.split2: argument not in ASCII range"
+  | otherwise = Byte.split2 (c2w c) b
+
+-- | Throws an exception the 'Char' argument is non-ascii.
+split3 :: Char -> Bytes -> Maybe (Bytes, Bytes, Bytes, Bytes)
+{-# INLINE split3 #-}
+split3 !c !b
+  | c > '\DEL' = errorWithoutStackTrace "Data.Bytes.Text.AsciiExt.split3: argument not in ASCII range"
+  | otherwise = Byte.split3 (c2w c) b
+
+-- | Throws an exception the 'Char' argument is non-ascii.
+split4 :: Char -> Bytes -> Maybe (Bytes, Bytes, Bytes, Bytes, Bytes)
+{-# INLINE split4 #-}
+split4 !c !b
+  | c > '\DEL' = errorWithoutStackTrace "Data.Bytes.Text.AsciiExt.split4: argument not in ASCII range"
+  | otherwise = Byte.split4 (c2w c) b
+
+-- | Throws an exception if any of the 'Char' arguments are non-ascii.
+splitTetragram1 :: Char -> Char -> Char -> Char -> Bytes -> Maybe (Bytes, Bytes)
+{-# inline splitTetragram1 #-}
+splitTetragram1 !c0 !c1 !c2 !c3 !b
+  | c0 > '\DEL' || c1 > '\DEL' || c2 > '\DEL' || c3 > '\DEL' =
+      errorWithoutStackTrace "Data.Bytes.Text.AsciiExt.splitTetragram1: one of the characters is not in ASCII range"
+  | otherwise = Bytes.splitTetragram1 (c2w c0) (c2w c1) (c2w c2) (c2w c3) b
+
+c2w :: Char -> Word8
+{-# inline c2w #-}
+c2w !c = fromIntegral @Int @Word8 (ord c)
+
+-- | Throws an exception the 'Char' argument is non-ascii.
+dropWhileNotEq :: Char -> Bytes -> Bytes
+dropWhileNotEq !c !b
+  | c > '\DEL' = errorWithoutStackTrace "Data.Bytes.Text.AsciiExt.dropWhileNotEq: argument not in ASCII range"
+  | otherwise =
+      let !w = c2w c
+       in Bytes.unsafeDrop (Bytes.countWhile (/= w) b) b
+
+-- | Throws an exception the 'Char' argument is non-ascii.
+takeWhileNotEq :: Char -> Bytes -> Bytes
+takeWhileNotEq !c !b
+  | c > '\DEL' = errorWithoutStackTrace "Data.Bytes.Text.AsciiExt.takeWhileNotEq: argument not in ASCII range"
+  | otherwise =
+      let !w = c2w c
+       in Bytes.unsafeTake (Bytes.countWhile (/= w) b) b
+  
+-- | Throws an exception the 'Char' argument is non-ascii.
+takeWhileEndNotEq :: Char -> Bytes -> Bytes
+takeWhileEndNotEq !c !b
+  | c > '\DEL' = errorWithoutStackTrace "Data.Bytes.Text.AsciiExt.takeWhileEndNotEq: argument not in ASCII range"
+  | otherwise =
+      let !w = c2w c
+          !n = Bytes.countWhileEnd (/=w) b
+       in Bytes (array b) (offset b + Bytes.length b - n) n
+
+-- | Throws an exception the 'Char' argument is non-ascii.
+dropWhileEndEq :: Char -> Bytes -> Bytes
+dropWhileEndEq !c !b
+  | c > '\DEL' = errorWithoutStackTrace "Data.Bytes.Text.AsciiExt.dropWhileEndEq: argument not in ASCII range"
+  | otherwise =
+      let !w = c2w c
+          !n = Bytes.countWhileEnd (==w) b
+       in Bytes.unsafeTake (Bytes.length b - n) b
+
+-- | Throws an exception the 'Char' argument is non-ascii.
+anyEq :: Char -> Bytes -> Bool
+anyEq !c !b
+  | c > '\DEL' = errorWithoutStackTrace "Data.Bytes.Text.AsciiExt.takeWhileNotEq: argument not in ASCII range"
+  | otherwise =
+      let !w = c2w c
+       in Bytes.any (==w) b
