diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,5 +1,10 @@
 # ChangeLog for warp
 
+## 3.3.18
+
+* Tidy up HashMap and MultiMap [#864](https://github.com/yesodweb/wai/pull/864)
+* Support GHC 9.2 [#863](https://github.com/yesodweb/wai/pull/863)
+
 ## 3.3.17
 
 * Modify exception handling to swallow async exceptions in forked thread [#850](https://github.com/yesodweb/wai/issues/850)
diff --git a/Network/Wai/Handler/Warp/HashMap.hs b/Network/Wai/Handler/Warp/HashMap.hs
--- a/Network/Wai/Handler/Warp/HashMap.hs
+++ b/Network/Wai/Handler/Warp/HashMap.hs
@@ -30,13 +30,8 @@
 ----------------------------------------------------------------
 
 insert :: FilePath -> v -> HashMap v -> HashMap v
-insert path v (HashMap hm) = HashMap $ I.insertWith f h m hm
-  where
-    !h = hash path
-    !m = M.singleton path v
-    f = M.union -- fimxe
+insert path v (HashMap hm) = HashMap
+  $ I.insertWith M.union (hash path) (M.singleton path v) hm
 
 lookup :: FilePath -> HashMap v -> Maybe v
-lookup path (HashMap hm) = I.lookup h hm >>= M.lookup path
-  where
-    !h = hash path
+lookup path (HashMap hm) = I.lookup (hash path) hm >>= M.lookup path
diff --git a/Network/Wai/Handler/Warp/MultiMap.hs b/Network/Wai/Handler/Warp/MultiMap.hs
--- a/Network/Wai/Handler/Warp/MultiMap.hs
+++ b/Network/Wai/Handler/Warp/MultiMap.hs
@@ -12,6 +12,7 @@
   , merge
   ) where
 
+import Control.Monad (filterM)
 import Data.Hashable (hash)
 import Data.IntMap.Strict (IntMap)
 import qualified Data.IntMap.Strict as I
@@ -20,14 +21,14 @@
 
 ----------------------------------------------------------------
 
--- | 'MultiMap' is used for cache of file descriptors.
---   Since multiple threads would open file descriptors for
---   the same file simultaneously, multiple entries must
---   be contained for the file.
---   Since hash values of file pathes are used as outer keys,
---   collison would happen for multiple file pathes.
---   Becase only positive entries are contained,
---   a bad guy cannot be cause the hash collision intentinally.
+-- | 'MultiMap' is used as a cache of file descriptors.
+--   Since multiple threads could open file descriptors for
+--   the same file simultaneously, there could be multiple entries
+--   for one file.
+--   Since hash values of file paths are used as outer keys,
+--   collison would happen for multiple file paths.
+--   Because only positive entries are stored,
+--   Malicious attack cannot cause the inner list to blow up.
 --   So, lists are good enough.
 newtype MultiMap v = MultiMap (IntMap [(FilePath,v)])
 
@@ -35,7 +36,7 @@
 
 -- | O(1)
 empty :: MultiMap v
-empty = MultiMap $ I.empty
+empty = MultiMap I.empty
 
 -- | O(1)
 isEmpty :: MultiMap v -> Bool
@@ -45,29 +46,22 @@
 
 -- | O(1)
 singleton :: FilePath -> v -> MultiMap v
-singleton path v = MultiMap mm
-  where
-    !h = hash path
-    !mm = I.singleton h [(path,v)]
+singleton path v = MultiMap $ I.singleton (hash path) [(path,v)]
 
 ----------------------------------------------------------------
 
--- | O(N)
+-- | O(M) where M is the number of entries per file
 lookup :: FilePath -> MultiMap v -> Maybe v
-lookup path (MultiMap mm) = case I.lookup h mm of
+lookup path (MultiMap mm) = case I.lookup (hash path) mm of
     Nothing -> Nothing
     Just s  -> Prelude.lookup path s
-  where
-    !h = hash path
 
 ----------------------------------------------------------------
 
 -- | O(log n)
 insert :: FilePath -> v -> MultiMap v -> MultiMap v
-insert path v (MultiMap mm) = MultiMap mm'
-  where
-    !h = hash path
-    !mm' = I.insertWith (<>) h [(path,v)] mm
+insert path v (MultiMap mm) = MultiMap
+  $ I.insertWith (<>) (hash path) [(path,v)] mm
 
 ----------------------------------------------------------------
 
@@ -81,31 +75,17 @@
 pruneWith :: MultiMap v
           -> ((FilePath,v) -> IO Bool)
           -> IO (MultiMap v)
-pruneWith (MultiMap mm) action = MultiMap <$> mm'
+pruneWith (MultiMap mm) action
+  = I.foldrWithKey go (pure . MultiMap) mm I.empty
   where
-    !mm' = I.fromAscList <$> go (I.toDescList mm) []
-    go []          !acc = return acc
-    go ((h,s):kss) !acc = do
-        rs <- prune action s
-        case rs of
-            [] -> go kss acc
-            _  -> go kss ((h,rs) : acc)
+    go h s cont acc = do
+      rs <- filterM action s
+      case rs of
+        [] -> cont acc
+        _  -> cont $! I.insert h rs acc
 
 ----------------------------------------------------------------
 
 -- O(n + m) where N is the size of the second argument
 merge :: MultiMap v -> MultiMap v -> MultiMap v
-merge (MultiMap m1) (MultiMap m2) = MultiMap mm
-  where
-    !mm = I.unionWith (<>) m1 m2
-
-----------------------------------------------------------------
-
-prune :: ((FilePath,v) -> IO Bool) -> [(FilePath,v)] -> IO [(FilePath,v)]
-prune action xs0 = go xs0
-  where
-    go []     = return []
-    go (x:xs) = do
-        keep <- action x
-        rs <- go xs
-        return $ if keep then x:rs else rs
+merge (MultiMap m1) (MultiMap m2) = MultiMap $ I.unionWith (<>) m1 m2
diff --git a/Network/Wai/Handler/Warp/ReadInt.hs b/Network/Wai/Handler/Warp/ReadInt.hs
--- a/Network/Wai/Handler/Warp/ReadInt.hs
+++ b/Network/Wai/Handler/Warp/ReadInt.hs
@@ -1,5 +1,4 @@
 {-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE MagicHash #-}
 {-# LANGUAGE BangPatterns #-}
 
 -- Copyright     : Erik de Castro Lopo <erikd@mega-nerd.com>
@@ -10,13 +9,7 @@
   , readInt64
   ) where
 
--- This function lives in its own file because the MagicHash pragma interacts
--- poorly with the CPP pragma.
-
 import qualified Data.ByteString as S
-import GHC.Prim
-import GHC.Types
-import GHC.Word
 
 import Network.Wai.Handler.Warp.Imports hiding (readInt)
 
@@ -34,34 +27,8 @@
 
 {-# NOINLINE readInt64 #-}
 readInt64 :: ByteString -> Int64
-readInt64 bs = S.foldl' (\ !i !c -> i * 10 + fromIntegral (mhDigitToInt c)) 0
+readInt64 bs = S.foldl' (\ !i !c -> i * 10 + fromIntegral (c - 48)) 0
              $ S.takeWhile isDigit bs
-
-data Table = Table !Addr#
-
-{-# NOINLINE mhDigitToInt #-}
-mhDigitToInt :: Word8 -> Int
-mhDigitToInt (W8# i) = I# (word2Int# (indexWord8OffAddr# addr (word2Int# i)))
-  where
-    !(Table addr) = table
-    table :: Table
-    table = Table
-        "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
-        \\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
-        \\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
-        \\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x00\x00\x00\x00\x00\x00\
-        \\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
-        \\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
-        \\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
-        \\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
-        \\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
-        \\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
-        \\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
-        \\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
-        \\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
-        \\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
-        \\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
-        \\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"#
 
 isDigit :: Word8 -> Bool
 isDigit w = w >= 48 && w <= 57
diff --git a/warp.cabal b/warp.cabal
--- a/warp.cabal
+++ b/warp.cabal
@@ -1,5 +1,5 @@
 Name:                warp
-Version:             3.3.17
+Version:             3.3.18
 Synopsis:            A fast, light-weight web server for WAI applications.
 License:             MIT
 License-file:        LICENSE
