diff --git a/CHANGELOG.markdown b/CHANGELOG.markdown
--- a/CHANGELOG.markdown
+++ b/CHANGELOG.markdown
@@ -1,3 +1,14 @@
+0.6 [2021.11.07]
+----------------
+* Allow building with GHC 9.2.1.
+* The `Data.Bits.Extras.oneBits` function now matches the implementation of the
+  `oneBits` function from `base`'s `Data.Bits` module. This is a breaking
+  change since the constraint has been strengthened from `Bits` to
+  `FiniteBits`. If you need to invoke `oneBits` on a data type that does not
+  have a `FiniteBits` instance (e.g., `Integer`), use the newly added
+  `unsafeOneBits` function instead.
+* Generalize `log2` to all `Integral` types with `base` >= 4.15 (GHC >= 9.0).
+
 0.5.3 [2021.02.17]
 ------------------
 * The build-type has been changed from `Custom` to `Simple`.
diff --git a/bits.cabal b/bits.cabal
--- a/bits.cabal
+++ b/bits.cabal
@@ -1,6 +1,6 @@
 name:          bits
 category:      Data, Serialization
-version:       0.5.3
+version:       0.6
 license:       BSD3
 cabal-version: >= 1.10
 license-file:  LICENSE
@@ -17,8 +17,10 @@
              , GHC == 8.2.2
              , GHC == 8.4.4
              , GHC == 8.6.5
-             , GHC == 8.8.3
-             , GHC == 8.10.1
+             , GHC == 8.8.4
+             , GHC == 8.10.7
+             , GHC == 9.0.1
+             , GHC == 9.2.1
 synopsis:      Various bit twiddling and bitwise serialization primitives
 description:   Various bit twiddling and bitwise serialization primitives.
 
diff --git a/src/Data/Bits/Coded.hs b/src/Data/Bits/Coded.hs
--- a/src/Data/Bits/Coded.hs
+++ b/src/Data/Bits/Coded.hs
@@ -46,7 +46,7 @@
 } deriving (Eq, Ord, Read, Show, Num, Integral, Real, Enum)
 
 ones :: Integer
-ones = oneBits `unsafeShiftL` 1
+ones = unsafeOneBits `unsafeShiftL` 1
 instance Integral n => Coded (Unary n) where
   encode (Unary n) = putBitsFrom (fromIntegral n) ones
   decode = do b <- getBit
diff --git a/src/Data/Bits/Extras.hs b/src/Data/Bits/Extras.hs
--- a/src/Data/Bits/Extras.hs
+++ b/src/Data/Bits/Extras.hs
@@ -20,6 +20,11 @@
 module Data.Bits.Extras
   ( Ranked(..)
   , log2
+#if __GLASGOW_HASKELL__ >= 900
+  , integerLog2
+  , wordLog2
+#endif
+  , word32Log2
   , msb
   , w8
   , w16
@@ -28,6 +33,7 @@
   , assignBit
   , zeroBits
   , oneBits
+  , unsafeOneBits
   , srl
   ) where
 
@@ -37,10 +43,49 @@
 import Foreign.Ptr
 import Foreign.Storable
 import GHC.Base
+#if __GLASGOW_HASKELL__ >= 900
+import Control.Exception
+import GHC.Integer.Logarithms
+#endif
 
--- TODO: generalize to 64 bits, etc.
+-- $setup
+-- >>> import Data.Bits (Bits(..))
+-- >>> import Data.Word (Word)
+
+#if __GLASGOW_HASKELL__ >= 900
+
+-- | Calculate the integer base 2 logarithm. The argument must be strictly
+-- positive. On GHC 9.0 or later, the argument type is generalized from
+-- 'Word32' to any 'Integral' type.
+log2 :: Integral a => a -> Int
+log2 = integerLog2 . toInteger
+{-# NOINLINE [1] log2 #-}
+{-# RULES
+"log2/Integer->Int" log2 = integerLog2 :: Integer -> Int
+"log2/Word->Int" log2 = wordLog2 :: Word -> Int
+"log2/Word32->Int" log2 = word32Log2 :: Word32 -> Int
+  #-}
+
+integerLog2 :: Integer -> Int
+integerLog2 n
+  | n > 0 = I# (integerLog2# n)
+  | otherwise = throw Overflow
+{-# INLINE integerLog2 #-}
+
+wordLog2 :: Word -> Int
+wordLog2 (W# n) = I# (wordLog2# n)
+{-# INLINE wordLog2 #-}
+
+#else
+
 log2 :: Word32 -> Int
-log2 !n0 = fromIntegral $ go (unsafeShiftR (n5 * 0x7C4ACDD) 27) where
+log2 = word32Log2
+{-# INLINE log2 #-}
+
+#endif
+
+word32Log2 :: Word32 -> Int
+word32Log2 !n0 = fromIntegral $ go (unsafeShiftR (n5 * 0x7C4ACDD) 27) where
   go :: Word32 -> Word8
   go !i = inlinePerformIO $ peekElemOff debruijn_log32 (fromIntegral i)
   !n1 = n0 .|. unsafeShiftR n0 1
@@ -48,7 +93,7 @@
   !n3 = n2 .|. unsafeShiftR n2 4
   !n4 = n3 .|. unsafeShiftR n3 8
   !n5 = n4 .|. unsafeShiftR n4 16
-{-# INLINE log2 #-}
+{-# INLINE word32Log2 #-}
 
 class (Num t, FiniteBits t) => Ranked t where
   -- | Calculate the least significant set bit using a debruijn multiplication table.
@@ -200,14 +245,38 @@
 assignBit b n False = b `clearBit` n
 {-# INLINE assignBit #-}
 
-oneBits :: Bits b => b
-oneBits  = complement zeroBits
+#if !(MIN_VERSION_base(4,16,0))
+-- | A more concise version of @complement zeroBits@.
+--
+-- >>> complement (zeroBits :: Word) == (oneBits :: Word)
+-- True
+--
+-- >>> complement (oneBits :: Word) == (zeroBits :: Word)
+-- True
+--
+-- = Note
+--
+-- The constraint on 'oneBits' is arguably too strong. However, as some types
+-- (such as 'Natural') have undefined 'complement', this is the only safe
+-- choice.
+oneBits :: FiniteBits b => b
+oneBits = unsafeOneBits
+#endif
 
+-- | A version of 'oneBits' that weakens the context from 'FiniteBits' to
+-- 'Bits'. This is unsafe because there are some data types with 'Bits'
+-- instances that have undefined 'complement', such as 'Natural'. Nevertheless,
+-- it is sometimes useful to call this function on data types without
+-- 'FiniteBits' instances (e.g., 'Integer'), so this function is provided as a
+-- convenience.
+unsafeOneBits :: Bits b => b
+unsafeOneBits = complement zeroBits
+
 -- | Shift Right Logical (i.e., without sign extension)
 --
 -- /NB:/ When used on negative 'Integer's, hilarity may ensue.
 srl :: Bits b => b -> Int -> b
-srl b n = (b `shiftR` n) .&. rotateR (oneBits `shiftL` n) n
+srl b n = (b `shiftR` n) .&. rotateR (unsafeOneBits `shiftL` n) n
 {-# INLINE srl #-}
 
 ------------------------------------------------------------------------------
